如何实现自定义属性包装器,它将发布 SwiftUI 的更改以重新呈现其视图

Pav*_*ský 5 swift swiftui property-wrapper

尝试实现一个自定义属性包装器,它也会以相同的方式发布其更改@Publish。例如,允许我的 SwiftUI 使用我的自定义包装器接收对我的属性的更改。

我的工作代码:

import SwiftUI

@propertyWrapper
struct MyWrapper<Value> {
    var value: Value

    init(wrappedValue: Value) { value = wrappedValue }

    var wrappedValue: Value {
        get { value }
        set { value = newValue }
    }
}

class MySettings: ObservableObject {
    @MyWrapper
    public var interval: Double = 50 {
        willSet { objectWillChange.send() }
    }
}

struct MyView: View {
    @EnvironmentObject var settings: MySettings

    var body: some View {
        VStack() {
            Text("\(settings.interval, specifier: "%.0f")").font(.title)
            Slider(value: $settings.interval, in: 0...100, step: 10)
        }
    }
}

struct MyView_Previews: PreviewProvider {
    static var previews: some View {
        MyView().environmentObject(MySettings())
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,我不喜欢需要objectWillChange.send()MySettings课堂上调用每个属性。

@Published包装的效果很好,所以我想实现它的一部分@MyWrapper,但我没有成功。

我发现的一个很好的灵感是https://github.com/broadwaylamb/OpenCombine,但即使尝试使用那里的代码也失败了。

当与实施挣扎,我意识到,为了得到@MyWrapper工作,我需要精确地了解@EnvironmentObject@ObservedObject订阅的变化@Published

任何帮助,将不胜感激。

Pav*_*ský 8

直到https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#referencing-the-enending-self-in-a-wrapper-type得到实现,我来了使用下面的解决方案。

一般来说,我将 的objectWillChange引用传递MySettings@MyWrapper使用反射注释的所有属性。

import Cocoa
import Combine
import SwiftUI

protocol PublishedWrapper: class {
    var objectWillChange: ObservableObjectPublisher? { get set }
}

@propertyWrapper
class MyWrapper<Value>: PublishedWrapper {
    var value: Value
    weak var objectWillChange: ObservableObjectPublisher?

    init(wrappedValue: Value) { value = wrappedValue }

    var wrappedValue: Value {
        get { value }
        set {
            value = newValue
            objectWillChange?.send()
        }
    }
}

class MySettings: ObservableObject {
    @MyWrapper
    public var interval1: Double = 10

    @MyWrapper
    public var interval2: Double = 20

    /// Pass our `ObservableObjectPublisher` to the property wrappers so that they can announce changes
    init() {
        let mirror = Mirror(reflecting: self)
        mirror.children.forEach { child in
            if let observedProperty = child.value as? PublishedWrapper {
                observedProperty.objectWillChange = self.objectWillChange
            }
        }
    }
}

struct MyView: View {
    @EnvironmentObject
    private var settings: MySettings

    var body: some View {
        VStack() {
            Text("\(settings.interval1, specifier: "%.0f")").font(.title)
            Slider(value: $settings.interval1, in: 0...100, step: 10)

            Text("\(settings.interval2, specifier: "%.0f")").font(.title)
            Slider(value: $settings.interval2, in: 0...100, step: 10)
        }
    }
}

struct MyView_Previews: PreviewProvider {
    static var previews: some View {
        MyView().environmentObject(MySettings())
    }
}
Run Code Online (Sandbox Code Playgroud)