Home > Software engineering >  Modifying user input in Swift
Modifying user input in Swift

Time:01-28

I'm trying to learn Swift and have gone through several tutorials, however, I seem to be going in circles and need some direction on how to solve this problem.

My goal is to create a decrementing timer, given a user-input (in seconds), in this case I've chosen a Stepper to -/ the value. Then begin decrementing the timer on a button press, in this case "Decrement". The counter is displayed on the label.

This problem is super easy if I hard code the starting value, but what purpose would that serve for a UI Test. So, this was the "challenging" task I was able to think of to help understand how SwiftUI works.

The problem I'm encountering is the variable passed by the user is immutable. I have tried making copies of it or assigning it to other variables to manipulate but seem to be going in circles. A nudge in the right direction or a potential solution would go a long way.

struct ContentView: View {
    @State private var timeInput: Int = 0
    var timer = Timer()
    var timeInputCopy: Int {
        timeInput
    }
    var body: some View {
        Stepper("Input", value: $timeInput, in: 0...150)
        Button("Decrement", action: decrementFunction)
        Label(String(timeInputCopy), image: "")
            .labelStyle(TitleOnlyLabelStyle())
    }
func decrementFunction() {
    timer.invalidate()
    timer = Timer.schedulerTimer(timeInterval: 1, 
                  target: self, 
                  selector: #selector(ContentView.timerClass), 
                  userInfo: nil, 
                  repeats: true)
}
func timerClass() {
    timeInputCopy -= timeInputCopy
    if (timeInputCopy == 0) {
        timer.invalidate()
    }
}
> Cannot assign to property: 'self' is immutable
> Mark method 'mutating' to make 'self' mutable

Attempting to auto-fix as Xcode recommends does not lead to a productive solution. I feel I am missing a core principle here.

CodePudding user response:

As mentioned in my comments above:

  1. timeInputCopy doesn't have a point -- it's not really a copy, it's just a computed property that returns timeInput

  2. You won't have much luck with that form of Timer in SwiftUI with a selector. Instead, look at the Timer publisher.

Here's one solution:

import Combine
import SwiftUI

class TimerManager : ObservableObject {
    @Published var timeRemaining = 0
    
    private var cancellable : AnyCancellable?
    
    func startTimer(initial: Int) {
        timeRemaining = initial
        cancellable = Timer.publish(every: 1, on: .main, in: .common)
            .autoconnect()
            .sink { _ in
                self.timeRemaining -= 1
                if self.timeRemaining == 0 {
                    self.cancellable?.cancel()
                }
            }
    }
}

struct ContentView: View {
    @StateObject private var timerManager = TimerManager()
    @State private var stepperValue = 60
    
    
    var body: some View {
        Stepper("Input \(stepperValue)", value: $stepperValue, in: 0...150)
        Button("Start") {
            timerManager.startTimer(initial: stepperValue)
        }
        Label("\(timerManager.timeRemaining)", image: "")
            .labelStyle(TitleOnlyLabelStyle())
    }
}

This could all be done in the View, but using the ObservableObject gives a nice separation of managing the state of the timer vs the state of the UI.

  •  Tags:  
  • Related