[英]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.