Home > Mobile >  How can I pass Binding<Timer> in SwiftUI?
How can I pass Binding<Timer> in SwiftUI?

Time:08-10

I would like to pass a timer from ContentView to SecondView, but I don't know how to manage it because I never used it before.

Can someone figure this out for me?

ContentView

struct ContentView: View {
    @State private var timer = Timer.publish(every: 1, tolerance: 0.5, on: .main, in: .common).autoconnect()
    @State private var timeRemaining = 10
    
    
    var body: some View {
        NavigationView {
            VStack {
                Text("\(timeRemaining)")
                    .onReceive(timer) { _ in
                        if timeRemaining > 0 {
                            timeRemaining -= 1
                        }
                    }
                
                NavigationLink {
                    SecondView(timer: ???) // <-- What should i pass here?
                } label: {
                    Text("Change View")
                }
            }
        }
    }
}

SecondView

struct SecondView: View {
    @Binding var timer: ??? // <-- What type?
    @State private var timeRemaining = 5
    
    var body: some View {
        Text("Hello")
            .onReceive(timer) { _ in
                if timeRemaining > 0 {
                    timeRemaining -= 1
                }
            }
    }
}

struct SecondView_Previews: PreviewProvider {
    static var previews: some View {
        SecondView(timer: ???) // <-- Same thing here in SecondView preview
    }
}

CodePudding user response:

With this timer declaration you are in the Combine world. Combine is the reactive framework from Apple.

First you would need to import it:

import Combine

If commented the code but Combine is a far field and it probably would be best to read the documentation about it, read some tutorials and try some things out.

documentation

struct ContentView: View {
    // The typ here is Publishers.Autoconnect<Timer.TimerPublisher>
    // But we can erase it and the result will be a Publisher that emits a date and never throws an error: AnyPublisher<Date,Never>
    @State private var timer = Timer.publish(every: 1, tolerance: 0.5, on: .main, in: .common)
        .autoconnect()
        .eraseToAnyPublisher()
    @State private var timeRemaining = 10
    
    
    var body: some View {
        NavigationView {
            VStack {
                Text("\(timeRemaining)")
                    .onReceive(timer) { _ in
                        if timeRemaining > 0 {
                            timeRemaining -= 1
                        }
                    }
                
                NavigationLink {
                    // pass the publisher on
                    SecondView(timer: timer)
                } label: {
                    Text("Change View")
                }
            }
        }
    }
}

struct SecondView: View {
    //You don´t need binding here as this view never manipulates this publisher
    var timer: AnyPublisher<Date,Never>
    @State private var timeRemaining = 5
    
    var body: some View {
        Text("Hello")
            .onReceive(timer) { _ in
                if timeRemaining > 0 {
                    timeRemaining -= 1
                    print(timeRemaining)
                }
            }
    }
}

struct SecondView_Previews: PreviewProvider {
    // Creating a static private var should work here !not tested!
    @State static private var timer = Timer.publish(every: 1, tolerance: 0.5, on: .main, in: .common)
        .autoconnect()
        .eraseToAnyPublisher()
    static var previews: some View {
        SecondView(timer: timer)
    }
}

CodePudding user response:

You could simply inject the timer publisher, as suggested above, but there may be an even simpler solution:

FirstView is already updating with every tick of the timer. You could simply pass a timeRemaning binding to your second view and then it too would just update with every tick of the timer (because timeRemaining changes on each tick). You can then observe and react to changes of timeRemaining using .onChange(of:):

struct SecondView: View {
    @Binding var timeRemaining: TimeInterval

    var body: some View {
        Text("Hello")
           .onChange(of: timeRemaining) {
               if $0 < 0 { 
                   timeRemaining = -1
               }
           }
    }
}
               
  • Related