TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

SOLVED: Better Rest - How do I recalculate bedtime when values are changed?

Forums > 100 Days of SwiftUI

I'm coming from a C#/.NET background and my instinct was to look for an onChange event handler for the DatePicker, Stepper and Picker controls but I couldn't find one., I found one, but I am completely stymied in my attempted use of it. It takes two parameters, "of" and "perform". The "of" is no problem, it's just the relevant variable I'm checking, but it will not accept calculateBedtime for the "perform" parameter. It complains that it cannot convert value of type '()' to equatable. It is not clear to me why it would insist that my function take an equatable as a parameter.

It then occurred to me that I could use didSet with the wakeUp, sleepAmount and coffeeAmount @State variables, however, while it compiles fine it doesn't work. Nothing is recalculated/updated unless I tap the Calculate button.

How can I get the calculateBedtime() function to fire whenever wakeUp, sleepAmount or coffeeAmount are changed?

1      

I got it to work, but I don't like the solution. And the question still remains as to why the didSets didn't work. Anyway, I had to supply an unused dummy parameter to the closure.

Section {
      DatePicker("Please enter a time", selection: $wakeUp, displayedComponents: .hourAndMinute)
          .labelsHidden()
  } header: {
      Text("When do you want to wake up?")
          .font(.subheadline)
  }.onChange(of: wakeUp) { bahTimApple in
      calculateBedtime() }

  Section {
      Stepper("\(sleepAmount.formatted()) hours", value: $sleepAmount, in: 4...12, step: 0.25)
  } header: {
      Text("Desired amount of sleep")
          .font(.subheadline)
  }.onChange(of: sleepAmount) { whyDoYouForceMe in
      calculateBedtime()
  }

  Section {
      Picker("Number of cups", selection: $coffeeAmount) {
          ForEach(1...20, id: \.self) {
              Text("\($0)")
          }
      }
  } header: {
      Text("Daily coffee intake")
          .font(.subheadline)
  }.onChange(of: coffeeAmount) { toUseThrowAwayParams in
      calculateBedtime()
  }

1      

Two things:

  1. instead of having to name throwaway params to your closures, you can use the underscore _, which in Swift stands for a variable that you don't care about.

So instead of

  }.onChange(of: sleepAmount) { whyDoYouForceMe in
      calculateBedtime()
  }

you can do

  }.onChange(of: sleepAmount) { _ in
      calculateBedtime()
  }
  1. If you make your property that holds the calculated bedtime into a computed variable that derives its value from the wakeUp, sleepAmount and coffeeAmount @State variables, then you can eliminate the need for those onChange handlers.

So you can either do those calculations directly in the computed var body or you can call the calculateBedtime function from within that body.

Oh, and the reason didSet didn't work is because adding one onto an @State property applies the observer to the property wrapper itself rather than to the wrapped value inside.

1      

Thanks for the explanation. Your idea about using a computed property worked great, much cleaner that way. Understood about @State and didSet, will remember that going forward.

One last thing that you might be able to clear up for me. When I'm populating the Picker I have to put quotes around $0 or it complains "No exact matches in call to initializer".

                Picker("Number of cups", selection: $coffeeAmount) {
                    ForEach(1...20, id: \.self) {
                        Text("\($0)")
                        //Text($0)  This doesn't work
                    }
                }

Any idea what the problem is?

1      

If you examine the defintion of Text you will see that it requires that the parameter initializer needs to follow the StringProtocol.

public init<S>(_ content: S) where S : StringProtocol

The anonymous parameter $0 does not follow the StringProtocol, hence you have to use double-quotes around it. Of course you cannot leave it like that as it would just literally show $0, and you need the value behind it, so you use string interpolation \($0) inbetween the quotes.

1      

OK, makes sense. Thanks

1      

Hacking with Swift is sponsored by String Catalog.

SPONSORED Get accurate app localizations in minutes using AI. Choose your languages & receive translations for 40+ markets!

Localize My App

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

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

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.