SwiftUI可选环境对象

siv3szwd  于 2023-04-28  发布在  Swift
关注(0)|答案(5)|浏览(138)

我使用@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的参数。例如,这些不起作用:

@EnvironmentObject var object: MyObject = MyObject()

@EnvironmentObject(MyObject()) var object: MyObject

我尝试用自己的属性 Package 器 Package @EnvironmentObject,但根本不起作用。
我也尝试过 Package 对object属性的访问,但它不会抛出一个可以被捕获的异常,它会抛出一个fatalError
是我遗漏了什么,还是我只是在尝试不可能的事情?

irlmq6kh

irlmq6kh1#

它不是一个非常优雅的,如果EnvironmentObject中的任何更改(以及其他警告),它很容易被破坏,但是如果你在SwiftUI 1 / Xcode 11中打印EnvironmentObject。3.1您将获得:
EnvironmentObject<X>(_store: nil, _seed: 1)
那么这样如何:

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

kninwzqo2#

通过遵循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
}

好好享受吧

zdwk9cvp

zdwk9cvp3#

我知道你告诉过你不能把你的对象放到一个 Package 器里,但是我认为这个解决方案是一个很好的方法来实现你想要的。
你唯一要做的就是创建一个非可选的 Package 器,但它将包含你的可选对象:

class MyObjectWrapper: ObservableObject {

  @Published var object: MyObject?

}

然后,创建视图并将 Package 器分配给环境:

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

}
tktrz96b

tktrz96b4#

我做了一个基于StateObject的 Package 器,并通过@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 }
    }
}
w9apscun

w9apscun5#

这个简短的属性 Package 器将跟踪保存在Environment中的ObservableObject的更改。它使用DynamicProperty.update()开始跟踪通过Environment传入的对象。
原来的问题是关于nil对象的。如果需要,可以将var object: Object更改为可选。

@propertyWrapper
public struct EnvironmentModel<Object: ObservableObject>: DynamicProperty {
    
    @Environment var object: Object
    @StateObject private var observer = Observer()
    
    public init(_ keyPath: KeyPath<EnvironmentValues, Object>) {
        self._object = Environment(keyPath)
    }
    
    public var wrappedValue: Object {
        self.object
    }
    
    public func update() {
        self.observer.cancellable = self.object.objectWillChange.sink { [weak observer] _ in
            observer?.objectWillChange.send()
        }
    }
    
    private class Observer: ObservableObject {
        var cancellable: AnyCancellable?
    }
    
}

相关问题