Reputation: 43
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
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
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