green ceramic mug on wooden desk

100 Days of SwiftUI: Project 4 – BetterRest

Our next project makes use of the current tech hotness, artificial intelligence – or machine learning as Apple calls it.

The main takeaway from project 4 is how to use a tool called CreateML to make an algorithm that predicts your ideal bedtime when given the input of a preferred wake time and amount of coffee drunk. The process of generating a model seems very simple, but the required input data is pretty massive – a CSV of 10,000 entries – so I’m not sure how likely I am to make use of this kind of technology in real life.

We also learn the basics of date handling – although I know from bitter experience that this is one of the hardest things to deal with in programming so this project is literally just scraping the surface. We also learn about another input control called Stepper.

Challenge

As usual, we have 3 challenges to complete to extend or modify the app.

Replace VStack with Section

This isn’t much of a challenge, more of style preference. In any case it’s a relatively simple change. I comment out the VStack and add in a Section instead.

//VStack(alignment: .leading, spacing: 0) {
    //Text("When do you want to wake up?")
        //.font(.headline)
        
Section("When do you want to wake up?") {

I think I prefer the look of the Section but, for the time picker especially, neither that nor the original VStack layout looks great to my eye.

Replace a Stepper with a Picker

This throws up an interesting little snag. Initially my code has an off-by-one error when using the picker, so I pick 1 cup but the calculation is done with 2. I then modify the label to be x-1 cups but that then gives me a 0 cups option. I haven’t yet solved this but I may revisit it when I have some time.

Picker("Number of cups", selection: $coffeeAmount) {
    ForEach(1..<21) {
        Text("^[\($0 - 1) cup](inflect: true)")
    }
}

Always display the bedtime

I comment out all the alert stuff and modify calculateBedtime to return a Date. After that it’s just a case of making a Text view that calls the function and formats the output.

// New section
Section("Your ideal bedtime is...") {
  Text(calculateBedtime().formatted(date: .omitted, time: .shortened))
    .font(.title.bold())
}

// New calculateBedtime
func calculateBedtime() -> Date {
    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: Int64((hour + minute)), estimatedSleep: sleepAmount, coffee: Int64(coffeeAmount))
        
        let sleepTime = wakeUp - prediction.actualSleep
        
        return sleepTime
    } catch {

    }
    return Date.now
}

I feel like return Date.now should maybe be within the catch block, but I’m not 100% sure. I might go back and have a look at that.

Result

BetterRest on GitHub