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!
100 Days of SwiftUI – Day 27 – Machine Learning