Home > Software engineering >  SwiftUI - picker view after timer expires not working as expected-seems to have a pulsating effect
SwiftUI - picker view after timer expires not working as expected-seems to have a pulsating effect

Time:05-23

I'm building a very simple timer app, and what I want it to do is that I want to show a picker view once the timer expires to add more time and go back to the timer. However, although it compiles, it gives me a really weird functionality shown below.

Error Recording

I do believe the secondView struct is the culprit here, but I'm not sure the exact problem. The code is attached below:

import SwiftUI

struct ContentView: View {
    @State var secondScreenShown = false
    @State var timerVal = 1
    
    var body: some View {
        VStack{
            Picker(selection: $timerVal, label: Text("Set Half Time")) {
                Text("1").tag(60)
                Text("2").tag(120)
                Text("20").tag(1200)
                Text("25").tag(1500)
                Text("30").tag(1800)
                Text("35").tag(2100)
                Text("40").tag(2400)
                Text("45").tag(2700)
                
            }
            NavigationLink(destination: SecondView(secondScreenShown: $secondScreenShown, timerVal: timerVal), isActive: $secondScreenShown) {
                Text("Set Half!")
            }
        }
    }
}
struct SecondView: View {
    @Binding var secondScreenShown: Bool
    @State var timerVal: Int
    
    var body: some View {
            VStack{
                if timerVal > 0 {
                    Text("Time Remaining in Half")
                        .font(.system(size: 14))
                    HStack(spacing: 33){
                        Text("\(timerVal / 60)")
                            .font(.system(size: 40))
                        Text("\(timerVal % 60)")
                            .font(.system(size: 40))
                            .onAppear(){
                                Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
                                    if self.timerVal > 0 {
                                        self.timerVal -= 1
                                    }
                                }
                            }
                        
                    }
                    HStack{
                        Text("Minutes")
                            .font(.system(size: 14))
                        Text("seconds")
                            .font(.system(size: 14))
                    }
                    Button(action: {
                        self.secondScreenShown = false
                    }) {
                        Text("Cancel")
                            .foregroundColor(.red)
                    }
    
                } else {
                    // This is the code I believe has the error
                    VStack{
                        Picker(selection: $timerVal, label: Text("Add Stoppage Time?")) {
                            Text("1").tag(60)
                            Text("2").tag(120)
                            Text("3").tag(180)
                            Text("4").tag(240)
                            Text("5").tag(300)
                            Text("6").tag(360)
                            Text("7").tag(420)
                            Text("8").tag(480)
                            Text("9").tag(540)
                        }
                        NavigationLink(destination: SecondView(secondScreenShown: $secondScreenShown, timerVal: timerVal), isActive: $secondScreenShown) {
                            Text("Add Stoppage")
                                .foregroundColor(.green)
                        }
                        Button(action: {
                            self.secondScreenShown = false
                        }) {
                            Text("Done")
                                .foregroundColor(.red)
                        }
                        
                    }
                    
            }

            }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
            ContentView()
        }
    }
}

Can you help me?

CodePudding user response:

Your issue is that you are using secondScreenShown to show the third screen as well. Since that value is true, it automatically takes the link and you continuously push additional screens.

You don't really need to push another screen. It is enough if the Add Stoppage just updates the time, but the picker needs to use a new variable so that it doesn't update timerVal until the button is pressed.

I added another state var called @State var overtime: Bool. This is used to avoid repeatedly showing the Add Stoppage every time the timer reaches zero.

Also, you should .invalidate() the Timer when it reaches 0 so that it can be freed and you don't end up with multiple timers running.

Here is the updated code:

import SwiftUI

struct ContentView: View {
    @State var secondScreenShown = false
    @State var timerVal = 60
    
    var body: some View {
        VStack{
            Picker(selection: $timerVal, label: Text("Set Half Time")) {
                Text("1").tag(60)
                Text("2").tag(120)
                Text("20").tag(1200)
                Text("25").tag(1500)
                Text("30").tag(1800)
                Text("35").tag(2100)
                Text("40").tag(2400)
                Text("45").tag(2700)
                
            }
            NavigationLink(destination: SecondView(secondScreenShown: $secondScreenShown, timerVal: timerVal), isActive: $secondScreenShown) {
                Text("Set Half!")
            }
        }
    }
}
struct SecondView: View {
    @Binding var secondScreenShown: Bool
    @State var timerVal: Int
    @State var stoppage: Int = 60
    @State var overtime: Bool = false
    
    var body: some View {
            VStack{
                if timerVal > 0 || overtime {
                    Text("Time Remaining in Half")
                        .font(.system(size: 14))
                    HStack(spacing: 33){
                        Text("\(timerVal / 60)")
                            .font(.system(size: 40))
                        Text("\(timerVal % 60)")
                            .font(.system(size: 40))
                            .onAppear(){
                                Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { tim in
                                    if self.timerVal > 0 {
                                        self.timerVal -= 1
                                        if self.timerVal == 0 {
                                            tim.invalidate()
                                        }
                                    }
                                }
                            }
                        
                    }
                    HStack{
                        Text("Minutes")
                            .font(.system(size: 14))
                        Text("seconds")
                            .font(.system(size: 14))
                    }
                    Button(action: {
                        self.secondScreenShown = false
                    }) {
                        Text("Cancel")
                            .foregroundColor(.red)
                    }
    
                } else {
                    // This is the code I believe has the error
                    VStack{
                        Picker(selection: $stoppage, label: Text("Add Stoppage Time?")) {
                            Text("1").tag(60)
                            Text("2").tag(120)
                            Text("3").tag(180)
                            Text("4").tag(240)
                            Text("5").tag(300)
                            Text("6").tag(360)
                            Text("7").tag(420)
                            Text("8").tag(480)
                            Text("9").tag(540)
                        }
                        Button(action: {
                            self.timerVal = self.stoppage
                            self.overtime = true
                        }) {
                            Text("Add Stoppage")
                                .foregroundColor(.green)
                        }
                        Button(action: {
                            self.secondScreenShown = false
                        }) {
                            Text("Done")
                                .foregroundColor(.red)
                        }
                        
                    }
                    
            }

            }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
            ContentView()
        }
    }
}
  • Related