songbuster
songbuster

Reputation: 43

SwiftUI sharing timer through views in navigation view

I'm pretty new at SwiftUI. I would like to make an app that navigates through different views in a Navigation View and running a timer in background presenting the time on each of the views. The problem is when I navigate to a second level of navigation. When the timer is on, the app returns automatically to the previous navigation view. Here's is a screenshot video of what I mean: https://youtu.be/eXbK9jpluvk

Here is my code:

import SwiftUI

struct ContentView: View {

@State var timer = Timer.publish(every: 1,  on: .main, in: .common).autoconnect()
@State var seconds : Int = 0
@State var paused : Bool = true

var body: some View {
    
    
    VStack {
        Text("\(seconds)")
        Button(action: {
            paused.toggle()
        }) {
            Image(systemName: paused ? "play.fill" : "stop.fill")
        }
        NavigationLink(destination: FirstView( timerSeconds: $seconds, timerPaused: $paused)) {
            Text("Navigate 1")
        }.padding()
    }
    .onReceive(self.timer) { currentTime in
        if !paused {
            seconds = seconds + 1
            print(currentTime)
        }
    }
    .navigationViewStyle(.stack)
    
}

}

struct FirstView: View {

@Binding var timerSeconds : Int
@Binding var timerPaused : Bool

var body: some View {
    
        VStack {
            Text("First View")
            Text("\(timerSeconds)")
            Button(action: {
                timerPaused.toggle()
            }) {
                Image(systemName: timerPaused ? "play.fill" : "stop.fill")
            }
            NavigationLink(destination: SecondView(seconds: $timerSeconds)) {
                Text("Navigate 2")
            }
        }
    
}

}

struct SecondView: View {

@Binding var seconds : Int

var body: some View {
    Text("\(seconds)")
}

}

Any help will be welcome!! Thanks a lot

Upvotes: 0

Views: 621

Answers (2)

Artemis Shlesberg
Artemis Shlesberg

Reputation: 308

In my case .isDetailLink(false) didn't solve the issue, but I found another solution. Want to post in case someone would look for a similar case.

I had a button with timer that leads to the multilevel navigation. To solve the issue I moved the timer updates from higher-vew level to the level of button itself.

struct LinkSentView: View {
    
    
    @ObservedObject var viewModel: LinkSentViewModel
    
    init(viewModel: LinkSentViewModel) {
        self.viewModel = viewModel
    }
    
    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            NavigationLink(destination: nameDestination(), isActive: $viewModel.fakeTrigger) { EmptyView() }
                .isDetailLink(false)
        ResendTimerButtonView(viewModel: ResendTimerButtonViewModel(resendLinkAction: viewModel.resendLink))
    enter code here
    }

class ResendTimerButtonViewModel: ObservableObject {
    
    private static let timeLimit = 30
    @Published private var timer: Timer?
    @Published private var timeRemaining = ResendTimerButtonViewModel.timeLimit
    
    var resendButtonText: String {
        timeRemaining > 0 ? "\(timeRemaining)s \(LinkSentView.Constant.resendButtonTitle)" : "\(LinkSentView.Constant.resendButtonTitle)"
    }
    
    var resendLinkAction: () -> ()
    
    init(resendLinkAction: @escaping () -> ()) {
        self.resendLinkAction = resendLinkAction
    }
    
    func startTimer() {
        timer?.invalidate()
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {[weak self] tempTimer in
            guard let self = self else { return }
            if self.timeRemaining > 0 {
                self.timeRemaining -= 1
            } else {
                tempTimer.invalidate()
            }
        }
    }
    
    func resendLink() {

    }
}

struct ResendTimerButtonView: View {
    @ObservedObject var viewModel: ResendTimerButtonViewModel
    var body: some View {
        Button(title: viewModel.resendButtonText, action: {
            viewModel.resendLink()
        })
        .onAppear {
            viewModel.startTimer()
        }
    }
}

Upvotes: 0

malhal
malhal

Reputation: 30729

NavigationView can only push on one detail NavigationLink so to have more levels you need to set .isDetailLink(false) on the link. Alternatively, if you don't expect to run in landscape split view, you could set .navigationViewStyle(.stack) on the navigation.

Upvotes: 1

Related Questions