Arturo
Arturo

Reputation: 4200

Timer onReceive not working inside NavigationView

I have the following timer:

struct ContentView: View {
    @State var timeRemaining = 10
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

    var body: some View {
        NavigationView{
            VStack {
                if(self.timeRemaining > 0) {
                    Text("\(timeRemaining)")
                        .onReceive(timer) { _ in
                            if self.timeRemaining > 0 {
                                self.timeRemaining -= 1
                            }
                    }
                } else {
                    Text("Time is up!")
                }
            }
        }
    }
}

If I remove the NavigationView view, the timer updates and works, but like that it doesn't, what's going on here and how can I update it while in the NavigationView? Or is there a better practice?

Thanks

Upvotes: 3

Views: 1161

Answers (2)

Engin Çelik
Engin Çelik

Reputation: 11

İ had same problem but not one-to-one. I Solved like this : my ContentView has .onApper {binancemanager.fetch()} —-> i call here but i moved it to @main struct MyApp:{WindowsGroup.Vstack.Content.

Upvotes: 0

Asperi
Asperi

Reputation: 258237

It is better to attach observers to non-conditional views, like

    var body: some View {
        NavigationView{
            VStack {
                if(self.timeRemaining > 0) {
                    Text("\(timeRemaining)")
                } else {
                    Text("Time is up!")
                }
            }
            .onReceive(timer) { _ in        // << to VStack
                if self.timeRemaining > 0 {
                    self.timeRemaining -= 1
                }
            }
        }
    }

Update: some thoughts added (of course SwiftUI internals are known only for Apple).

.onReceive must be attached to persistently present view in NavigationView, the reason is most probably in conditional ViewBuilder and internal of NavigationView wrapper around UIKit UINavigationControler.

if you remove the condition and have a single Text view with onReceive inside the VStack inside the NavigationView, nothing is ever received

If after condition removed it is attached to Text("\(timeRemaining)") then all works (tested with Xcode 11.4), because there is state dependency in body.

If it is attached to constant Text then there is nothing changed in body, ie. dependent on changed state - timeRemaining, so SwiftUI rendering engine interprets body as static.

Upvotes: 3

Related Questions