0

I have created a gravityPublisher (to receive updates in the current gravity vector) and subscribe to it by calling the following function:

private func enableGravityDetection(_ gravityEnabled: Bool) {
    if gravityEnabled {
        if cancellable == nil {
            cancellable = gravityProvider
                .gravityPublisher
                .receive(on: RunLoop.main)
                .print()
                .assign(to: \.gravity, on: self)
        }
    } else {
        cancellable?.cancel()
    }
}

The gravityPublisher itself is just a CurrentValueSubject

private var gravitySubject = 
    CurrentValueSubject<CMAcceleration, Never>(CMAcceleration())

erased to AnyPublisher<CMAcceleration, Never>. The cancellable is stored as a state property as follows:

@State private var cancellable: Cancellable?

I need to call this function from within a SwiftUI view. When I call it from an onAppear block everything works as expected and I continuously receive gravity vector updates:

struct CustomView: View {
    var body: some View {
        MyView()
            .onAppear {
                enableGravityDetection(true)
            }
    }
}

However, when I call the same (unmodified) function from the view's initializer, I don't receive any gravity updates as the subscription is immediately canceled:

struct CustomView: View {
    init() {
        enableGravityDetection(true)
    }

    var body: some View {
        MyView()
    }
}

The print() statement in the stream above prints the following output in this case:

receive subscription: (ReceiveOn)
request unlimited
receive cancel // ← Why?

Why is that and how can I fix this?

I need to call this function in the initializer as I sometimes need to stop receiving gravity updates and need to decide this every time the view is recreated. (onAppear is only called once.)

Mischa
  • 15,816
  • 8
  • 59
  • 117
  • It is not clear where did you store `cancellable` - it must be kept until end of publisher usage. – Asperi Apr 11 '22 at 16:45
  • True. Updated my question with that information. – Mischa Apr 11 '22 at 16:47
  • 1
    There is no `State` storage in `init` yet, so your cancellable just *released* on the stack and publisher is auto-cancelled as there is no any subscription. – Asperi Apr 11 '22 at 17:20
  • Ah, that makes sense. I didn’t know that, thanks! So if the state storage isn’t available yet in the init, is there any other way to store the subscription then? Or is that just a completely wrong approach? (And if so, what would be a better solution to dynamically enable/disabled published updates on a SwiftUI view?) – Mischa Apr 11 '22 at 17:39
  • 1
    View.init is not appropriate place for any activity actually (except simple properties initialization), because any view is a struct and it can re-created many times by SwiftUI internals. It is better to do that somewhere in engine, or view model, and inject into view either already being configured and do that somehow async. – Asperi Apr 11 '22 at 17:50

0 Answers0