WWDC24 SALE: Save 50% on all my Swift books and bundles! >>

SOLVED: day 28 / project 4 changing interface

Forums > 100 Days of SwiftUI

"Change the user interface so that it always shows their recommended bedtime using a nice and large font. You should be able to remove the “Calculate” button entirely."

Can somebody please send me in a good direction? I really want to resolve it myself but I don't even understand where to begin and start feeling a bit lost. I get the idea of having the text in the main screen, removing the button.
But I really don't understand where to start with making the bedtime show on the screen by default. Maybe I'm missing out on something. I was thinking about the weSplitt app. There the user can add/adjust stuff. Just like in this app and the amount that one person needs to pay is visible instantly with the use of the computed property. If I do that now I still need to use the do/try/catch I think (or with trying that I already did something wrong)? Do I need to use a computed property again?

sorry I feel really stuppid not understanding this. I think I just missing out on something maybe really simple.

2      

I have something working. But I really don't understand why it works :/. What I did:

var bedTime: String? {
        let components = Calendar.current.dateComponents([.hour, .minute], from: wakeUp)
        let hour = (components.hour ?? 0) * 60 * 60
        let minute = (components.minute ?? 0) * 60

        let sleepTime = try? wakeUp - ((SleepCalculator(configuration: MLModelConfiguration())).prediction(wake: Double(hour + minute), estimatedSleep: sleepAmount, coffee: Double(coffeeAmount)).actualSleep)

        return (sleepTime?.formatted(date: .omitted, time: .shortened))
    }

and in the View

Section {
                    Text(bedTime ?? "\(wakeUp)")
                        .font(.largeTitle)
                } header: {
                    Text("Time to go to bed:")
                }

I see a time. It goes up/down when drinking more/less coffee, or change anything else. But I do thinnk there is a much more cleaner/ better way to do this.

3      

Suzanne finds herself in a very common situation:

I have something working. But I really don't understand why it works

What's really helped me is using the Rubber Duck technique. See -> Rubber Duck Technique 🐤

If you search the forums for my handle, you'll see that I use this technique in most of my answers. This helped me tremendously along my 100 Day path. I started by commenting each and every line as if I were explaining to a new developer. Then I started writing answers to forum posts by trying to explain the answer to myself. Along the way, others pitched in to clarify my directions and understandings. But this is second nature to me now. I encourage you to follow along.

If you're rankled because you just don't get it, I'd say don't worry. Get used to stubbing your toes on the code. But keep explaining what you're doing with comments in your code.

// 🐤 bedtime is a calculated variable. 
// This variable is calculated WHENEVER I want to use it in a view.
// I want it to return a String, but errors may prevent this.
// In case of errors, this variable will be nil.
var bedTime: String? {
    // 🐤 break out my wakeUp variable into hour and minute parts.
        let components = Calendar.current.dateComponents([.hour, .minute], from: wakeUp)
        let hour       = (components.hour ?? 0) * 60 * 60  // turn seconds into hours
        let minute     = (components.minute ?? 0) * 60 // turn seconds into minutes

    // 🐤 Ask Swift's ML mechanism to give me a prediction based on my parameters.
    // Swift's ML may not be able to provide a prediction, instead it will return nil.
    // NOTE: This calculation uses the variables: wakeUp, sleepAmount, and coffeeAmount
    // If ANY of these variables change in your application, 
    // then Swift will RECALCULATE bedTime!
    // If bedTime is RECALCULATED, then Swift REDRAWS any component using bedTime.
        let sleepTime = try? wakeUp - ((SleepCalculator(configuration: MLModelConfiguration())).prediction(wake: Double(hour + minute), estimatedSleep: sleepAmount, coffee: Double(coffeeAmount)).actualSleep)

    // 🐤 Check sleepTime. Does it have value? If so, format it to look like a shortened time as a String
    // If sleepTime is nil, SwiftML wasn't able to generate a prediction. Therefore, return nil.
        return (sleepTime?.formatted(date: .omitted, time: .shortened))
}

Where do you use bedTime variable?

Now how are you using this calculated variable? Let's find out!

Here you have a Section to display the calculated bedTime. Explain this to yourself using Rubber Duck method. Explain your actions if Swift ML could not calculate a bedTime. What do you want to display?

// 🐤 Create a section in my view with an informative header.
// Display the time I should be in bed.
// NOTE: This Section is REDRAWN anytime bedTime is RECALCULATED. (see above).
// NOTE: What should you display if bedTime returns nil?
Section {
        // 🐤 Calculate the value of bedTime. If it has a value, DISPLAY the string.
        // If bedTime returns nil (see above), then DISPLAY the wakeUp string.  <-- WAIT?! Is this correct?
        Text(bedTime ?? "\(wakeUp)").font(.largeTitle)  // Maybe you don't want to display wakeUp time?!
    } header: {
        Text("Time to go to bed:")  // <-- The header is static.
}

Perhaps you want to display a message saying "Cannot calculate bed time." or something similar?

2      

About the more comments; totally agree. A lot of times I start with the comments. But then things don't work, so try something different, the comments doesn't fit the different thing. So I end up without any comments.

About the usage of "wakeUp". That is wrong indeed. I just needed something there while testing (see if calculated variable works). But it really needs to go there indeed.

But what I made, was it really a solution? It just feels strange tbh

2      

I think you nailed it.

Creating computed variables is a key Swift concept. Nicely done.

I pulled up my code from a year ago. I had a function named calculateBedtime(). Instead of using a button to execute this function, I attached the function to each of the form's controls.

For example if the user changed the cups of coffee, or a desired amount of sleep, I called the function using the .onChange() modifier. However, I needed this modifier on each view in the application where the user could change parameters. This was way more code to keep updated.

I think your approach is much better.

// When the user changes coffeeAmount, then recalculate the Bedtime.
.onChange(of: coffeeAmount) { _ in calculateBedtime() }

3      

Thank you for you solution. In the version before the challange it was a function that run when clicking the button. But the button needed to go. But everything I tried said that I couldn't run the function. Don't know the exact error anymore. Searching online give me the idea that is was not possible to use the function. So that is why changed to this.

2      

@tomn  

(solved thanks to the comments here)

My learning: as I understood the challenge, "idealBedTime" could be a computed property or a value from a function call (.onAppear and .onChange).

The only thing that worked right away for me was .onAppear on the Text(idealBedTime).

Text(idealBedTime)
                        .onAppear() {
                            calculateBedtime() // execute the function when view loads first time
                        }

But I couldn't write a computed property by simply moving over code from calculateBedTime function, I get "Errors thrown from here are not handled". But suziecode has already successfully used a computed property above, so I guess I just need to figure out the "try" or "try?" and handle optionals, etc. I need to spend sometime figuring this code out.

I also tried the onChange (on wakeUp, sleepAmount, coffeeAmount) by myself, but was not able to get the proper syntax, until I saw Obelix's code. Many thanks!!

Below is my functional code (not with the computed property), but with onChange.

import CoreML
import SwiftUI

struct ContentView: View {
    // set default wakeup time - currently initialized to 7am
    static var defaultWakeTime: Date {
        var components = DateComponents()
        components.hour = 7
        components.minute = 0
        return Calendar.current.date(from: components) ?? Date.now
    }

    // set defaults
    @State private var wakeUp = defaultWakeTime
    @State private var sleepAmount = 8.0
    @State private var coffeeAmount = 1

    // for alert box triggered by a button
    // no longer used after rewriting with .onChange
    @State private var alertTitle = ""
    @State private var alertMessage = ""
    @State private var showingAlert = false

    // string variable to hold ideal bed time calculated inside function
    @State private var idealBedTime = ""

    var body: some View {

        NavigationView {
            Form {

                Section(header: Text("When do you want to wake up?")) {

                    DatePicker("Please enter a time", selection: $wakeUp, displayedComponents: .hourAndMinute)
                        .labelsHidden()
                }

                Section(header: Text("Desired amount of sleep")) {
                    Stepper("\(sleepAmount.formatted()) hours", value: $sleepAmount, in: 4...12, step: 0.25)
                }

                Section(header: Text("Daily coffee intake")) {
                    Picker("Select number of cups", selection: $coffeeAmount) {
                        ForEach((1...20), id: \.self) {
                            Text($0 == 1 ? "\($0) cup" : "\($0) cups")
                        }
                    }
                    .labelsHidden()
                }

                Section(header: Text("Your ideal bedtime")) {
                    Text(idealBedTime)
                        .onAppear() {
                            calculateBedtime()
                        }
                        .font(.largeTitle.bold())
                }

                // everytime these State variables change, execute function
                // which in turn changes idealBedTime, causing view to refresh
                .onChange(of: wakeUp) { _ in calculateBedtime() }
                .onChange(of: sleepAmount) { _ in calculateBedtime() }
                .onChange(of: coffeeAmount) { _ in calculateBedtime() }

                    .alert(alertTitle, isPresented: $showingAlert) {
                        Button("OK") { }
                    } message: {
                        Text(alertMessage)
                    }

            }
            .headerProminence(.increased) // apply to all sections
            .navigationTitle("BetterRest")

            // button removed, no longer needed
            // .toolbar {
            //      Button("Calculate", action: calculateBedtime)
            // }
        }

    }

    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
            idealBedTime = sleepTime.formatted(date: .omitted, time: .shortened)
            // alert related stuff removed
            // alertTitle = "Your ideal bedtime is..."
            // alertMessage = sleepTime.formatted(date: .omitted, time: .shortened)

        } catch {
            // something went wrong!
            // alert related stuff removed
            // alertTitle = "Error"
            // alertMessage = "Sorry, there was a problem calculating your bedtime."
        }

        // showingAlert = true

    }

}

2      

Save 50% in my WWDC sale.

SAVE 50% To celebrate WWDC24, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

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.