【问题标题】:How to implement a custom property wrapper which would publish the changes for SwiftUI to re-render it's view如何实现一个自定义属性包装器,它将发布 SwiftUI 的更改以重新呈现它的视图
【发布时间】: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 的更改。

任何帮助将不胜感激。

【问题讨论】:

标签: swift swiftui property-wrapper


【解决方案1】:

https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#referencing-the-enclosing-self-in-a-wrapper-type 实施之前,我想出了下面的解决方案。

通常,我使用反射将MySettingsobjectWillChange 引用传递给所有带有@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 中不起作用(因为这会延迟执行时间)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-28
  • 2016-07-21
  • 2017-09-19
  • 1970-01-01
相关资源
最近更新 更多