繁体   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