期望SwiftUI DynamicProperty属性包装器的内部更新触发视图更新是否正确?

时间:2020-01-03 14:14:28

标签: swift swiftui property-wrapper

我正在尝试创建SwiftUI支持的自定义属性包装器,这意味着对相应属性值的更改将导致对SwiftUI视图的更新。这是我所拥有的简化版本:

@propertyWrapper
public struct Foo: DynamicProperty {
    @ObservedObject var observed: SomeObservedObject

    public var wrappedValue: [SomeValue] {
        return observed.value
    }
}

我看到,即使我的ObservedObject包含在自定义属性包装器中,只要有以下条件,SwiftUI仍会捕获对SomeObservedObject的更改:

  • 我的属性包装器是一个结构
  • 我的属性包装器符合DynamicProperty

不幸的是,文档稀疏,我很难说这是否只能通过当前的SwiftUI实现运气。

DynamicProperty的文档(在Xcode内,不在线)似乎表明该属性是从外部更改的属性,导致视图重绘,但是无法保证当您执行此操作时会发生什么情况。使您自己的类型符合该协议。

我可以期望它在以后的SwiftUI版本中继续工作吗?

3 个答案:

答案 0 :(得分:10)

好吧...这是获取类似内容的另一种方法...但是仅将DynamicProperty封装在@State周围(以强制刷新视图)。

这是一个简单的包装器,但可以通过以下视图刷新来封装任何自定义计算...并使用仅值类型进行封装。

这里是演示(已通过Xcode 11.2 / iOS 13.2测试):

DynamicProperty as wrapper on @State

这是代码:

import SwiftUI

@propertyWrapper
struct Refreshing<Value> : DynamicProperty {
    let storage: State<Value>

    init(wrappedValue value: Value) {
        self.storage = State<Value>(initialValue: value)
    }

    public var wrappedValue: Value {
        get { storage.wrappedValue }

        nonmutating set { self.process(newValue) }
    }

    public var projectedValue: Binding<Value> {
        storage.projectedValue
    }

    private func process(_ value: Value) {
        // do some something here or in background queue
        DispatchQueue.main.async {
            self.storage.wrappedValue = value
        }
    }

}


struct TestPropertyWrapper: View {

    @Refreshing var counter: Int = 1
    var body: some View {
        VStack {
            Text("Value: \(counter)")
            Divider()
            Button("Increase") {
                self.counter += 1
            }
        }
    }
}

struct TestPropertyWrapper_Previews: PreviewProvider {
    static var previews: some View {
        TestPropertyWrapper()
    }
}

答案 1 :(得分:0)

是的,这是正确的,下面是一个示例:

class SomeObservedObject : ObservableObject {
    @Published var counter = 0
}

@propertyWrapper struct Foo: DynamicProperty {

    @StateObject var object = SomeObservedObject()

    public var wrappedValue: Int {
        get {
            object.counter
        }
        nonmutating set {
            object.counter = newValue
        }
    }
}

重新创建使用@Foo的View时,SwiftUI将Foo结构传递给与上次相同的对象,因此具有相同的计数器值。设置foo var时,这是在ObservableObject的{​​{1}}上设置的,SwiftUI将其检测为更改并导致重新计算主体。

尝试一下!

@Published

点击第二个按钮时,存储在struct ContentView: View { @State var counter = 0 var body: some View { VStack { Text("\(counter) Hello, world!") Button("Increment") { counter = counter + 1 } ContentView2() } .padding() } } struct ContentView2: View { @Foo var foo var body: some View { VStack { Text("\(foo) Hello, world!") Button("Increment") { foo = foo + 1 } } .padding() } } 中的计数器将更新。轻按第一个按钮时,将调用Foo的主体,并重新创建ContentView,但保持与上次相同的计数器。现在ContentView2()可以是SomeObservedObject并实现一个NSObject协议。

答案 2 :(得分:0)

对于你标题中的问题,没有。例如,下面是一些不起作用的代码:

class Storage {
    var value = 0
}

@propertyWrapper
struct MyState: DynamicProperty {
    var storage = Storage()

    var wrappedValue: Int {
        get { storage.value }

        nonmutating set {
            storage.value = newValue // This doesn't work
        }
    }
}

因此显然您仍然需要在 State 中放入 ObservedObjectDynamicProperty 等以触发更新,就像 Asperi 所做的那样,DynamicProperty 本身不会强制执行任何更新。