NEW: Start my new Ultimate Portfolio App course with a free Hacking with Swift+ trial! >>

Connecting SwiftUI to Core ML

Paul Hudson    @twostraws   

In the same way that SwiftUI makes user interface development easy, Core ML makes machine learning easy. How easy? Well, once you have a trained model you can get predictions in just two lines of code – you just need to send in the values that should be used as input, then read what comes back.

In our case, we already made a Core ML model using Xcode’s Create ML app, so we’re going to use that. You should have saved it on your desktop, so please now drag it into the project navigator in Xcode – just below Info.plist should do the trick.

When you add an .mlmodel file to Xcode, it will automatically create a Swift class of the same name. You can’t see the class, and don’t need to – it’s generated automatically as part of the build process. However, it does mean that if your model file is named oddly then the auto-generated class name will also be named oddly.

In my case, I had a file called “BetterRest 1.mlmodel”, which meant Xcode would generate a Swift class called BetterRest_1. No matter what name your model file has, please rename it to be “SleepCalculator.mlmodel”, thus making the auto-generated class be called SleepCalculator.

How can we be sure? Well, just select the model file itself and Xcode will show you more information. You’ll see it knows our author and description, the name of the Swift class that gets made, plus a list of inputs and their types, and an output plus type too – these were encoded in the model file, which is why it was (comparatively!) so big.

Let’s start filling in calculateBedtime(). First, we need to create an instance of the SleepCalculator class, like this:

let model = SleepCalculator()

That’s the thing that reads in all our data, and will output a prediction. We trained our model with a CSV file containing the following fields:

  • “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.

So, in order to get a prediction out of our model, we need to fill in those values.

We already have two of them, because our sleepAmount and coffeeAmount properties are mostly good enough – we just need to convert coffeeAmount from an integer to a Double so that Swift is happy.

But figuring out the wake time requires more thinking, because our wakeUp property is a Date not a Double representing the number of seconds. Helpfully, this is where Swift’s DateComponents type comes in: it stores all the parts required to represent a date as individual values, meaning that we can read the hour and minute components and ignore the rest. All we then need to do is multiply the minute by 60 (to get seconds rather than minutes), and the hour by 60 and 60 (to get seconds rather than hours).

We can get a DateComponents instance from a Date with a very specific method call: Calendar.current.dateComponents(). We can then request the hour and minute components, and pass in our wake up date. The DateComponents instance that comes back has properties for all its components – year, month, day, timezone, etc – but most of them won’t be set. The ones we asked for – hour and minute – will be set, but will be optional, so we need to unwrap them carefully.

So, put this directly below the previous line in calculateBedtime():

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

That code uses 0 if either the hour or minute can’t be read, but realistically that’s never going to happen so it will result in hour and minute being set to those values in seconds.

The next step is to feed our values into Core ML and see what comes out. This might fail if Core ML hits some sort of problem, so we need to use do and catch. Honestly, I can’t think I’ve ever had a prediction fail in my life, but there’s no harm being safe!

So, we’re going to create a do/catch block, and inside there use the prediction() method of our model. This wants the wake time, estimated sleep, and coffee amount values required to make a prediction, all provided as Double values. We just calculated our hour and minute as seconds, so we’ll add those together before sending them in.

Please add this code to calculateBedtime() now:

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

    // more code here
} catch {
    // something went wrong!

With that in place, prediction now contains how much sleep they actually need. This almost certainly wasn’t part of the training data our model saw, but was instead computed dynamically by the Core ML algorithm.

However, it’s not a helpful value for users – it will be some number in seconds. What we want is to convert that into the time they should go to bed, which means we need to subtract that value in seconds from the time they need to wake up.

Thanks to Apple’s powerful APIs, that’s just one line of code – you can subtract a value in seconds directly from a Date, and you’ll get back a new Date! So, add this line of code after the prediction:

let sleepTime = wakeUp - prediction.actualSleep

And now we know exactly when they should go to sleep. Our final challenge, for now at least, is to show that to the user. We’ll be doing this with an alert, because you’ve already learned how to do that and could use the practice.

So, start by adding three properties that determine the title and message of the alert, and whether or not it’s showing:

@State private var alertTitle = ""
@State private var alertMessage = ""
@State private var showingAlert = false

We can immediately use those values in calculateBedtime(). If our calculation goes wrong – if reading a prediction throws an error – we can replace the // something went wrong comment with some code that sets up a useful error message:

alertTitle = "Error"
alertMessage = "Sorry, there was a problem calculating your bedtime." 

And regardless of whether or not the prediction worked, we should show the alert. It might contain the results of their prediction or it might contain the error message, but it’s still useful. So, put this at the end of calculateBedtime(), after the catch block:

showingAlert = true

Now for the more challenging part: if the prediction worked we create a constant called sleepTime that contains the time they need to go to bed. But this is a Date rather than a neatly formatted string, so we need to use Swift’s DateFormatter to make that look better.

DateFormatter can format dates and times in all sorts of ways using its dateStyle and timeStyle properties. In this instance, though, we just want a time string so we can put that into alertMessage.

So, put these final lines of code into calculateBedtime(), directly after where we set the sleepTime constant:

let formatter = DateFormatter()
formatter.timeStyle = .short

alertMessage = formatter.string(from: sleepTime)
alertTitle = "Your ideal bedtime is…"

To wrap up this stage of the app, we just need to add an alert() modifier that shows alertTitle and alertMessage when showingAlert becomes true.

Please add this modifier to our VStack:

.alert(isPresented: $showingAlert) {
    Alert(title: Text(alertTitle), message: Text(alertMessage), dismissButton: .default(Text("OK")))

Now go ahead and run the app – it works! It doesn’t look great, but it works.

Hacking with Swift is sponsored by RevenueCat

SPONSORED Building and maintaining in-app subscription infrastructure is hard. Luckily there's a better way. With RevenueCat, you can implement subscriptions for your app in hours, not months, so you can get back to building your app.

Try it for free

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

Buy Pro Swift Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift (Vapor Edition) Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.8/5

Unknown user

You are not logged in

Log in or create account

Link copied to your pasteboard.