如何实现一个自定义属性包装器,它将发布 SwiftUI 的更改以重新呈现它的视图
Posted
技术标签:
【中文标题】如何实现一个自定义属性包装器,它将发布 SwiftUI 的更改以重新呈现它的视图【英文标题】:How to implement a custom property wrapper which would publish the changes for SwiftUI to re-render it's view 【发布时间】:2020-01-25 18:54:12 【问题描述】:尝试实现一个自定义属性包装器,它也会像@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())
但是,我不喜欢为 MySettings
类中的每个属性调用 objectWillChange.send()
。
@Published
包装器运行良好,因此我尝试将其作为@MyWrapper
的一部分来实现,但没有成功。
我发现的一个很好的灵感是https://github.com/broadwaylamb/OpenCombine,但即使尝试使用那里的代码我也失败了。
在为实施而苦苦挣扎时,
我意识到,为了让@MyWrapper
正常工作,我需要准确了解@EnvironmentObject
和@ObservedObject
如何订阅@Published
的更改。
任何帮助将不胜感激。
【问题讨论】:
这是一个你可能感兴趣的话题Is it correct to expect internal updates of a SwiftUI DynamicProperty property wrapper to trigger a view update? 谢谢!然而,这种方法似乎不适用于ObservableObject
类:(
【参考方案1】:
在https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#referencing-the-enclosing-self-in-a-wrapper-type 实施之前,我想出了下面的解决方案。
通常,我使用反射将MySettings
的objectWillChange
引用传递给所有带有@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())
【讨论】:
这给了我来自后台线程警告的发布更改。 用.receive(on: RunLoop.main)
怎么样?
感谢您的快速回复!但是,我不明白在哪里使用 .receive。你能给我解释一下吗?
查看此处,其中解释了发布者结果更新 UI 时应执行的操作。 developer.apple.com/documentation/combine/…
谢谢,我学到了很多。但是,我仍然不确定在哪里使用.receive
。另外,动画也有问题。如果您使用withAnimation
,则动画更改将无法与该属性包装器一起使用。那是因为当set
运行时,objectWillChange?.send()
不能再监听变化,因为变量已经设置好了。在willSet
的value
中触发objectWillChange?.send()
对我有用(带有动画),但给了我警告。将其包装在 DispatchQueue.main.async
中不起作用(因为这会延迟执行时间)。以上是关于如何实现一个自定义属性包装器,它将发布 SwiftUI 的更改以重新呈现它的视图的主要内容,如果未能解决你的问题,请参考以下文章
如何在包装器 Swift View 中使用自定义 UIView 的扩展功能?