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

问题描述 投票:0回答:4

我正在尝试创建 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 版本中继续使用此功能吗?

swift swiftui property-wrapper
4个回答
17
投票

好的...这是获得类似内容的替代方法...但仅作为结构

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
            }
        }
    }
}

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
本身并不强制执行任何更新。


1
投票

您可以创建并使用您的

@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()
  }
}

0
投票

是的,属性包装器在包装在

DynamicProperty
结构内时将起作用。

您甚至可以实现一个

update
函数来使用自定义
DynamicProperty
结构初始化时使用的新参数,以将它们设置在任何属性包装器上。

例如

@FetchRequest
可能实现
update
以在
@StateObject
获取控制器上设置新的排序描述符或谓词。

© www.soinside.com 2019 - 2024. All rights reserved.