Search code examples
swiftstaticswiftuivar

updating a view with changing variables


I have this program with SwiftUI. The program is for calculating the bedtime using machine learning based on 3 user inputs. I have a Text("") showing users their updated bedtime.

I want the program to update the bedtime automatically and display it on my Text(""). I tried many methods and none seems to work. What I tried so far

  1. onAppear - only updates once bedtime when the program first runs
  2. onTapGesture - only updates the bedtime when tapping on the picker (scrolling the picker doesn't work), and it somehow hinders updating the stepper (clicking +/- doesn't change the hours)
  3. using didSet with class conforming to observableObject, @Pulished vars in the class and @ObservedObject in the view struct. Didn't work as well but I tried it only when the class has default values
  4. using didSet in the struct - it didn't update bedtime

Does anyone know if there's an easier way to have the bedtime updated however the user scrolls the picker and whenever a variable changes?

UI looks for detail

struct ContentView: View {

    static var defaultWakeUpTime : Date {
        var defaultTime = DateComponents()
        defaultTime.hour = 7
        defaultTime.minute = 0
        return Calendar.current.date(from: defaultTime) ?? Date()
    }

    @State private var wakeUp = defaultWakeUpTime
    @State private var sleepAmount = 8.0
    @State private var coffeeAmount = 0 {
        didSet {
            calculateSleepTime()
        }
    }

    @State private var showTime : String = " "

    func calculateSleepTime() {**CONTENT**}

    var body: some View {
        NavigationView {
                VStack {
                    Spacer(minLength: 20)

                    Text("Your optimum sleep time is \(showTime)")

                    Spacer(minLength: 10)

                    Section {
                    Text("When do you want to wake up?")
                        .font(.headline)
                        DatePicker("Please choose a time", selection: $wakeUp, displayedComponents: .hourAndMinute)
                        .labelsHidden()
                        .datePickerStyle(WheelDatePickerStyle())
                    }

                Spacer()

                    Form {
                        Text("How many hours would you like to sleep?")
                            .font(.headline)
                        Stepper(value: $sleepAmount, in: 4...12, step: 0.25) {
                            Text("\(sleepAmount, specifier: "%g" ) hours")
                        }

                    }

                Spacer()

                    Section {
                        Text("How many cups of coffee do you drink?")
                            .font(.headline)

                        Picker("Coffee Selector", selection: $coffeeAmount) {
                            ForEach (1..<21) {
                                Text("\($0) " + "Cup")
                            }
                        }
                        .labelsHidden()
                    }
                }
                .navigationBarTitle(Text("BetterSleep"))
                .onAppear(perform: calculateSleepTime)
        }
    }
}

Solution

  • I would use a viewModel and use subscriptions to track values and calculate sleep time.

    Change your ContentView at the top to this

    struct ContentView: View {
        @ObservedObject var viewModel = ViewModel()
    

    Now precede any variables with viewModel.

    Create a new .swift file I just called it ViewModel but you don't have to.

    import Combine
    
    final class ViewModel: ObservableObject {
        @Published private(set) var bedTime: String = ""
        @Published var wakeUp: Date = Date()
        @Published var sleepAmount: Double = 8.0
        @Published var coffeeAmount = 0
        private var cancellables = Set<AnyCancellable>()
    
        init() {
            $wakeUp
                .receive(on: RunLoop.main)
                .sink { [weak self] _ in
                    self?.calculateSleepTime()
            }.store(in: &cancellables)
    
            $sleepAmount
                .receive(on: RunLoop.main)
                .sink { [weak self] _ in
                    self?.calculateSleepTime()
            }.store(in: &cancellables)
    
            $coffeeAmount
                .receive(on: RunLoop.main)
                .sink { [weak self] _ in
                    self?.calculateSleepTime()
            }.store(in: &cancellables)
    }
    
        private func calculateSleepTime() {
            // Your Logic
            self.bedTime = 
        }
    }
    

    Now anytime one of the values changes the suggested bedtime will update. Remember to add one to the coffeeAmount as it starts at 0.