100 Days of SwiftUI Day 28 BetterRest

100 Days of SwiftUI – Day 28 – BetterRest Wrap Up

It’s day 28 of the 100 Days of SwiftUI! Yesterday, we finished our BetterRest app by implementing our machine learning model and creating a simple layout. Today, we’re wrapping up our project by reviewing what we learned with a short test by Paul, as well as a few challenges. Let’s dive in!

BetterRest SwiftUI challenges

Learning about machine learning and implementing a machine learning model this early on was a lot of fun and very interesting. That being said, we have much more to learn on this subject, so the challenges today won’t be focused on using Core ML. The idea was to introduce us to the subject and show how easy it can be to implement in our code.

As for our actual challenges, let’s take a look:

  1. Replace each VStack in our form with a Section, where the text view is the title of the section. Do you prefer this layout or the VStack layout? It’s your app – you choose!
  2. Replace the “Number of cups” stepper with a Picker showing the same range of values.
  3. 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.

For reference, I’ll start with the original code before modifications were made:

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()
    }
}

Challenge #1

Replacing each VStack with a Section was done within minutes and I have to say that I find it looks better. It’s cleaner and more cohesive.

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("Daily coffee intake", selection: $coffeeAmount) {
                        ForEach(1..<21) {
                            Text("\($0) cup(s) of coffee")
                        }
                    }
                }
                
                Section(header: Text("Recommended bedtime")) {
                    Text("\(calculateBedtime())")
                        .font(.title)
                }
            }
            
            .navigationTitle("BetterRest")
        }
    }

Challenge #2

Next, I replaced the number of cups Stepper with a Picker. In this case, I preferred the Stepper.

 Picker("Daily coffee intake", selection: $coffeeAmount) {
                        ForEach(1..<21) {
                            Text("\($0) cup(s) of coffee")
                        }

Challenge #3

Last but not least, we had to change the user interface so that we didn’t have to press the calculate button. Instead, the app should display the recommended bedtime in real time, using a nice large font.

I started by removing the toolbar and the accompanying button. Then, I modified the calculateBedtime function to return a string and returned the formatted Date in a constant called bedTime. Then, I called the function in a new Section using string interpolation and added a few modifiers.

Section(header: Text("Recommended bedtime")) {
                    Text("\(calculateBedtime())")
                        .font(.title)
                }
 
func calculateBedtime() -> String {
        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
            
            let bedTime = sleepTime.formatted(date: .omitted, time: .shortened)
            return bedTime
        } catch {
            alertTitle = "Error"
            alertMessage = "Sorry, there was a problem calculating your bedtime."
            showingAlert = true
        }
        
        return ""
    }

And that’s it for day 28! This project was a lot of fun and I look forward to using Core ML again in the future. For now, it’s time to recharge as we look ahead to our fifth project where we’ll develop an app called Word Scramble. See you tomorrow!

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. Required fields are marked *

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

100 Days of SwiftUI – Day 38

100 Days of SwiftUI – Day 73

100 Days of SwiftUI – Day 0 – Introduction

100 Days of SwiftUI – Day 75