我正在尝试创建 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 版本中继续使用此功能吗?
好的...这是获得类似内容的替代方法...但仅作为结构
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
}
}
}
}
对于你标题中的问题,没有。例如,下面是一些不起作用的代码:
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
}
}
}
所以显然你仍然需要像 Asperi 那样在
State
中放置 ObservedObject
或 DynamicProperty
等来触发更新,DynamicProperty
本身并不强制执行任何更新。
您可以创建并使用您的
@propertyWrapper
发布者,就像 SwiftUI 中的 @Published
对象一样。
通过将
@porpertyWrapper
的发布者传递给 projectedValue
,您将拥有一个自定义的合并发布者,您可以在 SwiftUI 视图中使用它,并调用 $
来跟踪值随时间的变化。
在 SwiftUI 视图或视图模型中使用:
@Foo(defaultValue: "foo") var value: String
// For your view model or SwiftUI View
$value
您的完全自定义组合发布商作为
@propertyWrapper
:
import Combine
@propertyWrapper
struct Foo<Value> {
var defaultValue: Value
// Combine publisher to project value over time.
private let publisher = PassthroughSubject<Value, Never>()
var wrappedValue: Value {
get {
return defaultValue
}
set {
defaultValue = newValue
publisher.send(newValue)
}
}
// Project the updated value using the Combine publisher.
var projectedValue: AnyPublisher<Value, Never> {
publisher.eraseToAnyPublisher()
}
}
是的,属性包装器在包装在
DynamicProperty
结构内时将起作用。
您甚至可以实现一个
update
函数来使用自定义 DynamicProperty
结构初始化时使用的新参数,以将它们设置在任何属性包装器上。
例如
@FetchRequest
可能实现 update
以在 @StateObject
获取控制器上设置新的排序描述符或谓词。