Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI optional environment object

I'm using @EnvironmentObject like this:

struct MyView: View {
  @EnvironmentObject var object: MyObject

  ...
}

but my code doesn't need there to be a value for object.

Just making this optional doesn't work (doesn't even compile - Property type 'MyObject?' does not match that of the 'wrappedValue' property of its wrapper type 'EnvironmentObject')

You also can't pass in a default object (that would solve my problem too) - either as an initial value to the property, or as a parameter to @EnvironmentObject. e.i. these don't work:

@EnvironmentObject var object: MyObject = MyObject()

@EnvironmentObject(MyObject()) var object: MyObject

I've tried to wrap the @EnvironmentObject in my own property wrapper, but that just doesn't work at all.

I've also tried wrapping accesses to the object property, but it doesn't throw an exception which can be caught, it throws a fatalError.

Is there anything I'm missing, or am I just trying the impossible?

like image 710
deanWombourne Avatar asked Jan 20 '20 11:01

deanWombourne


2 Answers

It's not a very elegant and could easily break if anything in EnvironmentObject changes (and other caveats), but if you print EnvironmentObject in SwiftUI 1 / Xcode 11.3.1 you get:

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

so how about:

extension EnvironmentObject {
    var hasValue: Bool {
        !String(describing: self).contains("_store: nil")
    }
}
like image 125
atomoil Avatar answered Nov 12 '22 19:11

atomoil


By conforming to EnvironmentKey you basically can provide a default value that SwiftUI can safely fallback to in case of missing. Additionally, you can also leverage EnvironmentValues to access the object via key path based API.

You can combine both with something like this:

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

Now to access your new object from a view:

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

Enjoy!

like image 33
Mamouneyya Avatar answered Nov 12 '22 19:11

Mamouneyya