UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Day 28 Project 4 my version of BetterRest

Forums > 100 Days of SwiftUI

I took some liberties and have 0 to 20 cups of coffee. 0 cups does not use the ML Model. I also have inflections on 0 cups, 1 cup and 2 cups...

struct ContentView: View {
    @State private var wakeUp:Date = defaultWakeTime
    @State private var sleepAmount = 8.0
    @State private var coffeeAmount = 0
    @State private var alertTitle = ""
    @State private var alertMessage = ""
    @State private var showingAlert = false
    @State private var timeToBed:Date = defaultBedTime

    func shortenDate(convertDate: Date) -> String{
        let dateToString = DateFormatter()
        dateToString.dateFormat = "HH:mm"
        return dateToString.string(from: convertDate)
    }

   static var defaultBedTime: Date {
       var components = DateComponents()
       components.hour = -1
       components.minute = 0
       return Calendar.current.date(from: components) ?? .now
    }

    static var defaultWakeTime: Date {
        var components = DateComponents()
        components.hour = 7
        components.minute = 0
        return Calendar.current.date(from: components) ?? .now
    }

        var body: some View {
            NavigationStack{
                List {
                    Section (header: Text("When do you want to wake up?")){
                        DatePicker("Please Enter a time", selection: $wakeUp, displayedComponents: .hourAndMinute)
                            .onChange(of: wakeUp){newValue in
                                calculateBedtime()
                            }
                            .labelsHidden()
                    }
                    Section (header: Text("Desired amount of sleep")){
                        Stepper("\(sleepAmount.formatted()) hours", value: $sleepAmount, in: 4...12, step: 0.25)
                            .onChange(of: sleepAmount){newValue in
                                calculateBedtime()
                            }
                    }
                    Section (header: Text("Daily coffee intake")){
                        Picker("", selection: $coffeeAmount){
                            ForEach(0...20, id: \.self){
                                number in
                                if number == 1 {
                                    Text("^[\(number) cup](inflect: true)")
                                } else {
                                    Text("\(number) cups")
                                }
                            }
                        }
                    }
                    .onChange(of: coffeeAmount){newValue in
                        calculateBedtime()
                    }
                    VStack {
                        Text("Your Bedtime is \(shortenDate(convertDate: timeToBed))")
                            .font(.title2)
                            .bold()
                            .padding([.top, .bottom], 30)
                    }
                }
                .navigationTitle("BetterRest")
            }
        }
    func calculateBedtime() {
        if coffeeAmount == 0 {
            timeToBed = Calendar.current.date(byAdding: .minute, value: -Int(sleepAmount * 60), to: wakeUp) ?? Date.now
        } else {
            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 * 6
                let minute = (components.minute ?? 0) * 60
                let prediction = try model.prediction(wake: Double(hour + minute), estimatedSleep: sleepAmount, coffee: Double(coffeeAmount))

                timeToBed = wakeUp - prediction.actualSleep

            } catch {
                alertTitle = "Error"
                alertMessage = "Sorry, there was a problem calculating your bedtime."
            }
                    showingAlert = true
        }
    }
 }

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }

}

3      

Taking liberties with your code is a GREAT way to experiment with the lesson's concepts. Testing out assumptions, playing with Swift built-in function options, and changing the interface is a key to learning more about the language.

Here's a comment on function naming. Try to go out of your way to name functions with descriptions that make it crystal clear to future you™ what your function does.

What does this function do?

func shortenDate(convertDate: Date) -> String{
        let dateToString = DateFormatter()
        dateToString.dateFormat = "HH:mm"
        return dateToString.string(from: convertDate)
}

If I were to paste this into my solution, the name is confusing.
First, the signature says it will "shorten the date". Then it's asking me to provide a date to be converted.
Which is it? shorten? or convert?

Then when I run this code, SURPRISE! It neither shortens the date, nor does it convert it! Instead, the result is the time extracted from the date your programmers provides! (TBH, is does convert the results to a String.)

Consider

So please consider the following.

// Your goal is to extract the time.
// If called without a date, provide a default of right now.
// The programmer sees fromDate, but inside
// this function the variable is named providedDate
func extractTime(fromDate providedDate: Date = Date.now) -> String {
        let desiredTimeFormat        = DateFormatter()      // create a formatter
        desiredTimeFormat.dateFormat = "HH:mm"              // define final format
        return desiredTimeFormat.string(from: providedDate) // extract time, return a string
    }

// Test cases
extractTime(fromDate: .now.addingTimeInterval(-3500))  // subtract some time
extractTime()  // use the default time

Keep coding!

3      

You added this

if number == 1 {
    Text("^[\(number) cup](inflect: true)")
} else {
    Text("\(number) cups")
}

however Text("^[\(number) cup](inflect: true)") does 1 cup or 2 cups so no need for if statement.

Also do NOT leave the title with "" as this does not read well with screen reader you better off doing this

Picker("Select amount of coffee", selection: $coffeeAmount) {
    ForEach(0...20, id: \.self) { number in
        Text("^[\(number) cup](inflect: true)")
    }
}
.labelsHidden() // <- use this to hid labels

Why shortenDate(: ) when timeToBed.formatted(date: .omitted, time: .shortened) and will respect the locale of user.

Let complier do the heavy lifting

3      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

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

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.