NEW: Learn SwiftData for free with my all-new book! >>

SOLVED: BetterRest Challenge #3

Forums > 100 Days of SwiftUI

I'm trying to figure out BetterRest app challenge #3.

Goal is to do away with the calculate button and have the app always display the recommended bedtime.

My solution was to add an action to the cups per day Picker that calls the calculate bedtime function.

When I run the app and change the cups of coffee from the default the action to call the calculate bedtime function doesn't work.

Any assistance would be appreciated.

import CoreML
import SwiftUI

struct ContentView: View {
    static var defaultWakeTime: Date {
        var components = DateComponents()
        components.hour = 7
        components.minute = 0
        return components) ??
    static var defaultBedTime: Date {
        var components = DateComponents()
        components.hour = 19
        components.minute = 0
        return components) ??
    @State private var wakeUp = defaultWakeTime
    @State private var sleepAmount = 8.0
    @State private var coffeeAmount = 1
    @State private var alertTitle = ""
    @State private var alertMessage = ""
    @State private var showingAlert = false
    @State private var recommendedBedTime = defaultBedTime

    var body: some View {
        NavigationView {
            Form {
                Section {
                    Text("When do you want to wake up?")

                    DatePicker("Please enter a time", selection: $wakeUp, displayedComponents: .hourAndMinute)
                Section {
                    Text("Desired amount of sleep")
                    Stepper("\(sleepAmount.formatted()) hours", value: $sleepAmount, in: 4...12, step: 0.25)
                Section {
                    Text("Daily coffee intake")
                    Picker("Cups per day", selection: $coffeeAmount) {
                        ForEach(1..<21) { number in
                            Button("\(number)", action: calculateBedTime)
                Section {
                    Text("Your ideal bedtime is: \(recommendedBedTime.formatted(date: .omitted, time: .shortened))")
                .toolbar {
                    Button("Calculate", action: calculateBedTime)
            .alert(alertTitle, isPresented: $showingAlert) {
                Button("OK") { }
            } message: {
    func calculateBedTime() {
        do {
            let config = MLModelConfiguration()
            let model = try SleepCalculator(configuration: config)
            let components = Calendar.current.dateComponents([.hour, .minute], from: wakeUp)
            let hour = (components.hour ?? 0) * 60 * 60
            let minute = (components.minute ?? 0) * 60
            let prediction = try model.prediction(wake: Double(hour + minute), estimatedSleep: sleepAmount, coffee: Double(coffeeAmount))
            let sleepTime = wakeUp - prediction.actualSleep
            recommendedBedTime = sleepTime
            alertTitle = "Your ideal bedtime is…"
            alertMessage = sleepTime.formatted(date: .omitted, time: .shortened)

        } catch {
            alertTitle = "Error"
            alertMessage = "Sorry, there was a problem calculating your bedtime."
        showingAlert = true

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {


You might want to look at

.onChange(of: /* Property */ ) { _ in

but you have to have one for each different property that you want to re-calculate


Thanks for the tip.

I looked up the method in the Swift documentation and reviewed example there. I thought I could attach the method to the app Form but get an error message that value of type Form has no member 'onChange(of)'. I tried attaching to all the views, i.e., Section, Form and Navigation with the same result.

Surely I'm doing something wrong but not sure what...


Try to move most of the calculateBedTime function into a computed property and then pass that along into a Text view somewhere in your app.

Think about it this way... You have @State variables. Whenever ANY @State variable is changed in your view, the view recognizes that change and as a result it destroys the view and re-creates it. This means that any computed properties inside the view also get re-calcuted!


I have attached it to a Form the .onChange(ofThis need to be Equatable)

What error message do you get?


Here is the error on all attempts to attach...type Form has no member 'onChange(of)'


So...I attached the method again to my new Section with the text required by the challenge and got no error this time.

The onChange did the trick. Not sure why I was getting the errors before exactly. Will have to look into that.

Thanks for your help.


Hacking with Swift is sponsored by Swiftable

SPONSORED An iOS conference hosted in Buenos Aires, Argentina – join us for the third edition from November 29th to December 1st!

Get your ticket

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

Unknown user

You are not logged in

Log in or create account

Link copied to your pasteboard.