Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI: observe @Environment property changes

Tags:

ios

swift

swiftui

I was trying to use the SwiftUI @Environment property wrapper, but I can't manage to make it work as I expected. Please, help me understanding what I'm doing wrong.

As an example I have an object that produces an integer once per second:

class IntGenerator: ObservableObject {
    @Published var newValue = 0 {
        didSet {
            print(newValue)
        }
    }

    private var toCanc: AnyCancellable?

    init() {
        toCanc = Timer.TimerPublisher(interval: 1, runLoop: .main, mode: .default)
            .autoconnect()
            .map { _ in Int.random(in: 0..<1000) }
            .assign(to: \.newValue, on: self)
    }
}

This object works as expected since I can see all the integers generated on the console log. Now, let's say we want this object to be an environment object accessible from all over the app and from whoever. Let's create the related environment key:

struct IntGeneratorKey: EnvironmentKey {
    static let defaultValue = IntGenerator()
}

extension EnvironmentValues {
    var intGenerator: IntGenerator {
        get {
            return self[IntGeneratorKey.self]
        }
        set {
            self[IntGeneratorKey.self] = newValue
        }
    }
}

Now I can access this object like this (for example from a view):

struct TestView: View {
    @Environment(\.intGenerator) var intGenerator: IntGenerator

    var body: some View {
        Text("\(intGenerator.newValue)")
    }
}

Unfortunately, despite the newValue being a @Published property I'm not receiving any update on that property and the Text always shows 0. I'm sure I'm missing something here, what's going on? Thanks.

like image 935
matteopuc Avatar asked Jan 16 '20 15:01

matteopuc


People also ask

How do you declare an environment object in SwiftUI?

Although declared in the same way as observable objects, environment object bindings are declared in SwiftUI View files using the @EnvironmentObject property wrapper. Before becoming accessible to child views, the environment object must also be initialized before being inserted into the view hierarchy using the environmentObject () modifier.

What are state properties in SwiftUI?

SwiftUI offers four options for implementing this behavior in the form of state properties, observable objects, state objects and environment objects, all of which provide the state that drives the way the user interface appears and behaves. In SwiftUI, the views that make up a user interface layout are never updated directly within code.

How to access system-wide settings in SwiftUI?

In SwiftUI, most of these system-wide settings and variables are accessible via the @Environment property wrapper. You can see all the available options in EnvironmentValues. To access environment values, you create a @Environment variable specifying a keypath to value you want to read and write.

How are views updated in SwiftUI?

In SwiftUI, the views that make up a user interface layout are never updated directly within code. Instead, the views are updated automatically based on the state objects to which they have been bound as they change over time. This chapter will describe these four options and outline when they should be used.


Video Answer


1 Answers

Environment gives you access to what is stored under EnvironmentKey but does not generate observer for its internals (ie. you would be notified if value of EnvironmentKey changed itself, but in your case it is instance and its reference stored under key is not changed). So it needs to do observing manually, is you have publisher there, like below

@Environment(\.intGenerator) var intGenerator: IntGenerator

@State private var value = 0
var body: some View {
    Text("\(value)")
        .onReceive(intGenerator.$newValue) { self.value = $0 }
}

and all works... tested with Xcode 11.2 / iOS 13.2

like image 133
Asperi Avatar answered Nov 15 '22 18:11

Asperi