我正在尝试创建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版本中继续工作吗?
答案 0 :(得分:10)
好吧...这是获取类似内容的另一种方法...但是仅将DynamicProperty
封装在@State
周围(以强制刷新视图)。
这是一个简单的包装器,但可以通过以下视图刷新来封装任何自定义计算...并使用仅值类型进行封装。
这里是演示(已通过Xcode 11.2 / iOS 13.2测试):
这是代码:
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
中放入 ObservedObject
或 DynamicProperty
等以触发更新,就像 Asperi 所做的那样,DynamicProperty
本身不会强制执行任何更新。