为 Swiftui 视图转换 @State 的计算属性

Posted

技术标签:

【中文标题】为 Swiftui 视图转换 @State 的计算属性【英文标题】:Convert computed property for @State for Swiftui Views 【发布时间】:2021-05-11 17:40:48 【问题描述】:

我是 SwiftUI 的新手,我想基本上转换我的计算属性,以便在 SwiftUI 视图中使用 combine 等。我不能那样使用它,因为“设置”不适用于我的 SwiftUI 视图,而且我在这里有点挣扎。 也许有人有一个很好的解决方案,我如何将它与 combine 转换为在 swift ui 中使用。

存储服务将 Authdata 保存到 userdefaults 中。

var currentAuthData: AuthData? 
    get 
        return self.storageService.get(AuthData.self, forKey: authDataStorageKey)
    
    set 
        if let value = newValue 
            self.storageService.store(value, forKey: authDataStorageKey)
        
    

【问题讨论】:

"get set 不适用于我的 SwiftUI 视图" -- 怎么回事?从语法上讲,这里看起来不一定有什么问题。您能否为minimal reproducible example 提供足够的代码并解释您看到的结果与您的预期有何不同? 很难在没有更多信息的情况下提供帮助。一个常见的错误是将状态放在视图中。在 SwiftUI 中,视图是结构,每帧重新实例化一次(每秒 100 次)。确保将状态保存在 ObservableObject 中并将其传递给视图:developer.apple.com/documentation/swiftui/…。 【参考方案1】:

这就是在 SwiftUI 中转换计算属性的方法,方法是将其设为 @propertyWrapper。如果您需要,我添加了一个使用组合读取数据的解决方案。我还让你的属性是可选的。

我认为这是您要保存的模型。
struct AuthData 

  var name: String
  var email: String

为您的属性包装器准备协议,以便能够使用 Optional 将其设置为 nil。
public protocol AnyOptional 
  var isNil: Bool  get 


extension Optional: AnyOptional 
  public var isNil: Bool  self == nil 

扩展 UserDefaults 以符合此协议并且是可选的。
extension UserDefault where Value: ExpressibleByNilLiteral 

  init(key: String, _ container: UserDefaults = .standard) 
    self.init(key: key, defaultValue: nil, container: container)
  

创建与 SwiftUI 视图的 getter 和 setter 相同的属性包装器。这是一个通用的,可以用于任何类型。您可以在此代码块之后的 UserDefaults 扩展中设置它们。
import Combine

@propertyWrapper
struct UserDefault<Value> 
  let key: String
  let defaultValue: Value
  var container: UserDefaults = .standard

  // Set a Combine publisher for your new value to
  // always read its changes when using Combine.
  private let publisher = PassthroughSubject<Value, Never>()

  var wrappedValue: Value 
    get 
      // Get the new value or nil if any.
      container.object(forKey: key) as? Value ?? defaultValue
    
    set 
      // Check if the value is nil and remove it from your object.
      if let optional = newValue as? AnyOptional, optional.isNil 
        container.removeObject(forKey: key)
      
      else 
        // Set your new value inside UserDefaults.
        container.set(newValue, forKey: key)
      
      // Add the newValue to your combine publisher
      publisher.send(newValue)
    
  
  var projectedValue: AnyPublisher<Value, Never> 
    publisher.eraseToAnyPublisher()
  

在 UserDefaults 中创建扩展以在代码中使用您的属性包装器。
extension UserDefaults 

  @UserDefault(key: "authDataStorageKey", defaultValue: nil)
  static var savedAuthData: AuthData?

  // You can create as many @propertyWrapper as you want that fits your need

使用示例

// Set a new value
UserDefaults.savedAuthData = AuthData(name: "Muli", email: "muli@***.com")

// Read the saved value
print(UserDefaults.savedAuthData as Any)

// When using combine
var subscriptions = Set<AnyCancellable>()

UserDefaults.$savedAuthData
  .sink  savedValue in
    print(savedValue as Any)  // Yours saved value that changes over time.
  .store(in: &subscriptions)

【讨论】:

以上是关于为 Swiftui 视图转换 @State 的计算属性的主要内容,如果未能解决你的问题,请参考以下文章

如何将我的 SwiftUI 视图的 @State 交换为我的视图模型 @Published 变量?

一旦新的 SwiftUI 视图完全加载,@State 变量就会被清除

如何根据 SwiftUI 中的 @State 更改导航视图的导航视图样式?

SwiftUI - 更新@State 不会改变视图

SwiftUI,为啥部分视图没有刷新? @State 变量未更新

深度解析:为何在 SwiftUI 视图的 init 初始化器里无法更改 @State 的值?