macOS Development for Beginners: Part 3 | Ray Wenderlich

In this macOS Development tutorial for beginners, learn how to add the code to the UI you developed in part 2 to make a fully operational egg timer.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/110269/mac-os-x-development-tutorial-beginners-part-3-first-os-x-app

Oh boy you’ve got a big boo boo.

Try this.
Start the App and start the timer.
While it’s counting down the 6 minutes;
- open up the preferences window.
- set the Egg Timing Setting to “for very firm hard-boiled eggs” = 15 minutes.
- press ok
- the app’s checkForResetAfterPrefsChange() warning will trigger. Press cancel.

Et Voila! Egg magically turns yellow!!

I’m very new to programming so it took me ages to work out the problem. It’s a pretty big structural problem actually.
What’s happening is when you press ok, that triggers the saveNewPrefs() function which literally the first thing it does is save the new preference!!
Only AFTER that does it send out a notification to the NotificationCenter which sets off the checkForResetAfterPrefsChange() warning.

Ah but you might think all you have to do is change the order, send out the notification then save the new preference - nope! not so fast buddy!
All the checkForResetAfterPrefsChange() warning does, is update the view’s preferences if you press ok!!
It doesn’t deal with if you press CANCEL, It doesn’t stop the saveNewPrefs function AND It even updates the view’s preferences BEFORE the preference’s values have been updated themselves!!! <- this is why when you change the preferences even normally, the Egg Timer’s TimerText won’t change until you press start (which re-executes updateDisplay).

You’ve got to have the checkForResetAfterPrefsChange() function send out a notification that will tell the saveNewPrefs() function to abort if you press cancel.
And if you don’t cancel, the saveNewPrefs() function needs to send a notification to tell the updateFromPrefs() function to execute after it has saved the new preferences, not before.

Me again.
Something I really don’t get is in the Preferences.Swift file.
There’s a line that says;
UserDefaults.standard.set(newValue, forKey: “selectedTime”)
to set the selectedTime variable.

My question is what’s up with the constant newValue?
The QuickHelp tells me it’s declared as a timeInterval constant in Preferences.Swift (it’s not even mentioned anywhere else?!).
More mysteriously is the set function that (according to QuickHelp) takes in 2 inputs, a Double and a String.
But newValue is a timeInterval, not a Double?!
So as far as I can see, newValue is a constant that is never declared and is the wrong type for the function it’s being called for, yet this somehow works fine?!
If you change the name newValue to something else like oldValue, then it triggers an error exactly the way I’d expect.
But there’s no mention of any newValue constants built into the Foundation in the API reference.

So what is this mysterious newValue???

@sarah Can you please help with this when you get a chance? Thank you - much appreciated! :]

Hi,

You are correct - I forgot to handle the Cancel case correctly.
The solution is to reset the Preferences value back to the current eggTimer duration if Cancel is pressed.

Here is an update to checkForResetAfterPrefsChange():

  func checkForResetAfterPrefsChange() {
    if eggTimer.isStopped || eggTimer.isPaused {
      updateFromPrefs()
    } else {
      let alert = NSAlert()
      alert.messageText = "Reset timer with the new settings?"
      alert.informativeText = "This will stop your current timer!"
      alert.alertStyle = .warning

      alert.addButton(withTitle: "Reset")
      alert.addButton(withTitle: "Cancel")

      let response = alert.runModal()
      if response == NSAlertFirstButtonReturn {
        self.updateFromPrefs()
      } else {
        self.prefs.selectedTime = self.eggTimer.duration
      }
    }
  }

Check out the else after the response from the alert - this is the new section.

The Preferences struct uses a really nice feature of Swift to observe changes and act on them. Any computed property like selectedTime in this case, can have a get and a set.

In the get, you can return whatever you need to. Here it is reading the value from UserDefaults and supplying a default value if there is nothing there.

The set gets the new value for this property automatically in a parameter called newValue. If it makes things clearer, you can re-write the set using your own parameter name like this:

set(newSelectedTime) {
  UserDefaults.standard.set(newSelectedTime, forKey: "selectedTime")
}

In this version, I explicitly name the parameter newSelectedTime and use it as the value stored in UserDefaults.

In my original code using newValue, the type of newValue is set to TimeInterval because that is the type of selectedTime which it is setting.

But if you option click on the keyword TimeInterval, you will see that it is a typealias for Double. So it really is a Double, but using the word TimeInterval provides anyone reading the code with the extra information that this is actually a time measurement.

I hope this clarifies things, but please get back to me if it is still unclear.

Regards,
Sarah

This tutorial is more than six months old so questions are no longer supported at the moment for it. Thank you!