100 Days of SwiftUI Day 27

100 Days of SwiftUI – Day 27 – Machine Learning

It’s day 27 of the 100 Days of SwiftUI! Yesterday, we started with our fourth project: the BetterRest app. Today, we’re finishing the app by implementing our machine learning model. Let’s dive in!

Machine Learning in SwiftUI

With the help of Create ML, we created a machine learning model by providing a good chunk of data for it to train with. With our model finished, it is now time to implement it into our app. First things first: drag the .mlmodel file, in this case named SleepCalculator.mlmodel, into the Project View in Xcode. This allows Swift to set up a class for our model automatically.

Next, we had to add an import for Core ML, because it isn’t a part of SwiftUI. I’ll include the full in code right away, so we can then break down the machine learning part afterwards.

import CoreML
import SwiftUI

struct ContentView: View {
    
    @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
    
    static var defaultWakeTime: Date {
        var components = DateComponents()
        components.hour = 7
        components.minute = 0
        return Calendar.current.date(from: components) ?? Date.now
    }
    
    var body: some View {
        NavigationView {
            Form {
                VStack(alignment: .leading, spacing: 0) {
                Text("When do you want to wake up?")
                    .font(.headline)
                    
                
                DatePicker("Please enter a time", selection: $wakeUp, displayedComponents: .hourAndMinute)
                    .labelsHidden()
                }
                
                VStack(alignment: .leading, spacing: 0) {
                Text("Desired amount of sleep")
                    .font(.headline)
                
                Stepper("\(sleepAmount.formatted()) hours", value: $sleepAmount, in: 4...12, step: 0.25)
                
                }
                
                VStack(alignment: .leading, spacing: 0){
                    Text("Daily coffee intake")
                        .font(.headline)
                    
                    Stepper(coffeeAmount == 1 ? "1 cup" : "\(coffeeAmount) cups", value: $coffeeAmount, in: 1...20)
                }
                
            }
            
            .navigationTitle("BetterRest")
            .toolbar {
                Button("Calculate", action: calculateBedtime)
            }
            .alert(alertTitle, isPresented: $showingAlert) {
                Button("OK") { }
                
            } message: {
                Text(alertMessage)
            }
        }
    }
    
    func calculateBedtime() {
        do {
            let config = MLModelConfiguration()
            let model = try SleepCalculator(configuration: config)
            
            let components = Calendar.current.dateComponents([.hour, .minute], from: wakeUp)
            let hours = (components.hour ?? 0) * 60 * 60
            let minutes = (components.minute ?? 0) * 60
            
            let prediction = try model.prediction(wake: Double(hours + minutes), estimatedSleep: sleepAmount, coffee: Double(coffeeAmount))
            
            let sleepTime = wakeUp - prediction.actualSleep
            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 {
        ContentView()
    }
}

Implementing the model

The magic in our app happens in the calculateBedtime function. This is where we implement our machine learning model and pull predictions from it.

We start our function by creating an instance of our SleepCalculator class. We have to create our instance using do/catch blocks, because Core ML can throw errors in two places: either when the model is loaded or when we get a prediction from it. It’s rare, but it can happen.

do {
    let config = MLModelConfiguration()
    let model = try SleepCalculator(configuration: config)

    // more code here
} catch {
    // error handling code here
}

The model instance is used to read our data and output a prediction. We trained our model with a CSV file provided by Paul that contains:

  • “wake”: when the user wants to wake up. This is expressed as the number of seconds from midnight, so 8am would be 8 hours multiplied by 60 multiplied by 60, giving 28800.
  • “estimatedSleep”: roughly how much sleep the user wants to have, stored as values from 4 through 12 in quarter increments.
  • “coffee”: roughly how many cups of coffee the user drinks per day.

In order to get a prediction from our model, we need to fill these values. As you may have noticed, we already have sleepAmount and coffeeAmount properties in our code, which can be used to fill the estimatedSleep and coffee values in our model. We just need to convert our coffeeAmount to a Double, because the coffee value in the model has been declared as a Double as well.

Filling the wake value is a bit more tricky, because it uses a date. Remember, a date in SwiftUI isn’t only used for a literal date, but also for minutes, hours and seconds. However, a date is not a Double, which means we need to convert it to a Double before we can use it in our model. We accomplish this by creating a DateComponent and extracting the necessary values, in this case hours and minutes, from it and storing them in a new variable.

let components = Calendar.current.dateComponents([.hour, .minute], from: wakeUp)
let hour = (components.hour ?? 0) * 60 * 60 // convert to seconds
let minute = (components.minute ?? 0) * 60 // convert to seconds

Now that we have all the necessary values, what remains is feeding them to our model.

let prediction = try model.prediction(wake: Double(hour + minute), estimatedSleep: sleepAmount, coffee: Double(coffeeAmount))

We’re nearing the end! Our prediction property now contains how much sleep we actually need. All that’s left is deducting this prediction from the time we want to wake up, and we’ll have the time we need to go to sleep.

<strong>let</strong> sleepTime = wakeUp - prediction.actualSleep

The rest of our code is mainly setting up the alert and giving our app a nicer layout. So, that’s it for day 27! If you want to learn more about machine learning in SwiftUI, check out this video by Paul. We’ll be back tomorrow with a review of this project, as well as a few challenges. Time to recharge!

Darryl

Hi! My name is Darryl and this is my personal blog where I write about my journey as I learn programming! You'll also find articles about other things that interest me including games, tech and anime.

Post navigation

Leave a Comment

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

100 Days of SwiftUI – Day 9 – Closures

100 Days of SwiftUI – Day 66

100 Days of SwiftUI – Day 14 – Optionals

100 Days of SwiftUI – Day 57