簡體   English   中英

SwiftUI 可選環境對象

[英]SwiftUI optional environment object

我像這樣使用@EnvironmentObject

struct MyView: View {
  @EnvironmentObject var object: MyObject

  ...
}

但我的代碼不需要object的值。

只是使這個可選不起作用(甚至不編譯 - Property type 'MyObject?' does not match that of the 'wrappedValue' property of its wrapper type 'EnvironmentObject'

您也不能傳入默認對象(這也可以解決我的問題) - 作為屬性的初始值,或作為@EnvironmentObject的參數。 ei 這些不起作用:

@EnvironmentObject var object: MyObject = MyObject()

@EnvironmentObject(MyObject()) var object: MyObject

我試圖將@EnvironmentObject包裝在我自己的屬性包裝器中,但這根本不起作用。

我也試過包裝對 object 屬性的訪問,但它不會拋出一個可以被捕獲的異常,它會拋出一個fatalError

有什么我遺漏的嗎,或者我只是在嘗試不可能的事情?

如果 EnvironmentObject 中的任何內容發生變化(和其他警告),它不是很優雅並且很容易中斷,但是如果您在 SwiftUI 1 / Xcode 11.3.1 中打印 EnvironmentObject,您會得到:

EnvironmentObject<X>(_store: nil, _seed: 1)

那么怎么樣:

extension EnvironmentObject {
    var hasValue: Bool {
        !String(describing: self).contains("_store: nil")
    }
}

通過符合EnvironmentKey您基本上可以提供一個默認值,SwiftUI 可以在丟失的情況下安全地回退到該默認值。 此外,您還可以利用EnvironmentValues通過基於密鑰路徑的 API 訪問對象。

您可以將兩者與以下內容結合使用:

public struct ObjectEnvironmentKey: EnvironmentKey {
    // this is the default value that SwiftUI will fallback to if you don't pass the object
    public static var defaultValue: Object = .init()
}

public extension EnvironmentValues {
    // the new key path to access your object (\.object)
    var object: Object {
        get { self[ObjectEnvironmentKey.self] }
        set { self[ObjectEnvironmentKey.self] = newValue }
    }
}

public extension View {
    // this is just an elegant wrapper to set your object into the environment
    func object(_ value: Object) -> some View {
        environment(\.object, value)
    }
}

現在從視圖訪問您的新對象:

struct MyView: View {
    @Environment(\.object) var object
}

享受!

我知道您告訴過您無法將對象放入包裝器中,但是我認為此解決方案是實現您想要的目標的好方法。

您唯一要做的就是創建一個非可選的包裝器,但它將包含您的可選對象:

class MyObjectWrapper: ObservableObject {

  @Published var object: MyObject?

}

然后,您創建視圖並將包裝器分配給環境:

let wrapper = MyObjectWrapper()
// You can try to load your object here, and set it to nil if needed.
let view = MyView().environmentObject(wrapper)

在您看來,您現在可以檢查對象是否存在:

struct MyView: View {
  
  @EnvironmentObject var objectWrapper: MyObjectWrapper
  
  var body: some View {
    if objectWrapper.object != nil {
      Text("Not nil")
    } else {
      Text("Nil")
    }
  }
  
}

如果任何視圖更改objectWrapper.object ,視圖將被重新加載。

您可以輕松模擬您的視圖,甚至在幾秒鍾后觸發更改以檢查轉換:

struct MyView_Previews: PreviewProvider {

  static var previews: some View {
    let objectWrapper = MyObjectWrapper()
    DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
      objectWrapper.object = MyObject()
    }
    return MyView().environmentObject(objectWrapper)
  }

}

我制作了一個基於StateObject的包裝器,並通過@autoscaping閉包延遲初始化默認值。

@EnvironmentModel var object = Object() //default value

此外,如果您通過環境傳遞對象,則不必將其存儲在某處

yourView.environmentModel(Object())

代碼

@propertyWrapper
public struct EnvironmentModel<Model: ObservableObject>: DynamicProperty {

    @StateObject private var object = Object()
    @Environment(\.environmentModel) private var environment
    private let defaultValue: () -> Model
    private let id: AnyHashable

    public var wrappedValue: Model {
        createModel()
        return object.model
    }

    public var projectedValue: Binding<Model> {
        createModel()
        return $object.model
    }

    public init(wrappedValue: @escaping @autoclosure () -> Model) {
        defaultValue = wrappedValue
        id = String(reflecting: Model.self)
    }

    public init<ID: Hashable>(wrappedValue: @escaping @autoclosure () -> Model, _ id: ID) {
        defaultValue = wrappedValue
        self.id = id
    }

    @inline(__always) private func createModel() {
        guard object.model == nil else { return }
        object.model = (environment[id] as? () -> Model)?() ?? defaultValue()
    }

    private final class Object: ObservableObject {
        var model: Model! {
            didSet {
                model.objectWillChange.subscribe(objectWillChange).store(in: &bag)
            }
        }
        var bag: Set<AnyCancellable> = []
        let objectWillChange = PassthroughSubject<Model.ObjectWillChangePublisher.Output, Model.ObjectWillChangePublisher.Failure>()
    
        init() {}
    }
}

extension View {
    public func environmentModel<M: ObservableObject>(_ model: @escaping @autoclosure () -> M) -> some View {
        modifier(EnvironmentModelModifier(model: model, key: String(reflecting: M.self)))
    }

    public func environmentModel<M: ObservableObject, ID: Hashable>(id: ID, _ model: @escaping @autoclosure () -> M) -> some View {
        modifier(EnvironmentModelModifier(model: model, key: id))
    }
}

private struct EnvironmentModelModifier<Model>: ViewModifier {
    @State private var object = Object()
    private let create: () -> Model
    let key: AnyHashable

    var model: Model {
        createModel()
        return object.model
    }

    init(model: @escaping () -> Model, key: AnyHashable) {
        create = model
        self.key = key
    }

    @inline(__always) private func createModel() {
        guard object.model == nil else { return }
        object.model = create()
    }

    func body(content: Content) -> some View {
        let value: () -> Model = { self.model }
        return content.environment(\.environmentModel[key], value)
    }

    private final class Object {
        var model: Model!
    
        init() {}
    }
}

private enum EnvironmentModelKey: EnvironmentKey {
    static var defaultValue: [AnyHashable: Any] { [:] }
}

extension EnvironmentValues {
    fileprivate var environmentModel: [AnyHashable: Any] {
        get { self[EnvironmentModelKey.self]  }
        set { self[EnvironmentModelKey.self] = newValue }
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM