Update Note: Updated for Xcode 8.2 / Swift 3 by Ernesto García. Previous update to Xcode 6.3 / Swift 1.2 by Michael Briscoe. Original post by Ernesto García.
Welcome back to the second and final part of this macOS Controls tutorial series!
In the first part of this tutorial, you started building a Mad Libs style macOS application, where the user enters various words and phrases to create a funny sentence.
Along the way, you learned about some of the core UI macOS controls — namely, Text Fields, Combo Boxes, Pop Up Buttons, Push Buttons and Text Views.
More macOS Controls
In this final part of the tutorial, you’ll finish off your application, and learn how to use the following:
- Sliders
- Date Pickers
- Radio Buttons
- Check Boxes
- Image Views
At the end of this two-part tutorial you’ll have a solid understanding of macOS controls.
This tutorial will pick up where you left off. If you don’t have it already, here’s the final project of the first part.
It’s time to get to work!
Slipping and Sliding — NSSlider
A slider is a control that lets the user choose from a range of values. A slider has a minimum and a maximum value, and by moving the control’s knob, the user can choose a value between those two limits. Sliders can be either linear or radial. What’s the difference between the two, you ask?
Linear sliders can be either vertical or horizontal, and they let you choose a value by moving the knob along the track. A really great example of linear sliders is in macOS’ Mouse preferences panel:
Radial sliders are a little different — they are displayed as a small circle with a knob, which can be rotated a full 360 degrees. To select a value, you click and drag the knob to the required position. You can find a great example of radial sliders in Adobe Photoshop, where they’re used to define the angle of a gradient, as such:
The control responsible for this on macOS is an NSSlider.
All three types of sliders (horizontal, vertical and radial) are in fact the same control, NSSlider
. The only difference is how they’re displayed. Interface Builder has an object in the Object Library for each of the three types, as shown below:
Slider Semantics
There are two common tasks you’re likely to perform when working with sliders: getting or setting the current value, and getting and setting the high and low limits of the slider’s range. These properties are outlined here:
// getting & setting an integer value let theInteger = mySlider.integerValue mySlider.integerValue = theInteger // getting & setting a float value let theFloat = mySlider.floatValue mySlider.floatValue = theFloat // getting & setting a double value let theDouble = mySlider.doubleValue mySlider.doubleValue = theDouble // getting & setting the minimum value of the range let theMinimumValue = mySlider.minValue mySlider.minValue = theMinimumValue // getting & setting the maximum value of the range let theMaximumValue = mySlider.maxValue mySlider.maxValue = theMaximumValue |
Again, nothing too surprising here — if you’ve learned anything by now, it’s that implementing standard UI macOS controls is a fairly straightforward exercise. Move on to the next section to include an NSSlider
in your app!
Pick a Number, Any Number
Open Main.storyboard. Locate the Label control In the Object Library palette and drag it onto the content view below the Phrase label. Resize the window vertically and move the Go! button down if you need to make space for it.
Double-click the control to edit its default text, and change it to Amount: [10]. Find the Horizontal Slider control and drag it onto the window, placing it to the right of the label.
Click on the slider to select it. Open the Attributes Inspector and set the Minimum value to 2, and the Maximum value to 10. Change the Current value to 5. This will be the default value of the slider when the user first runs the app.
Make sure that Continuous is checked. This tells the slider to notify any change in the slider’s value.
Now you need to create two outlets; one for the slider, and one for the label. Wait, you may say — that’s a little different. Why are you adding a property for the label?
That’s so you can update the label’s text to list the current amount whenever the value of the slider is changed; hence why you set the Continuous property on the slider. Aha! Makes sense now, doesn’t it?
Open the Assistant editor and make sure ViewController.swift is open. Ctrl-Drag from label to ViewController.swift to create a new outlet.
In the popup screen, name the outlet amountLabel.
Repeat the above process with the slider, naming the outlet amountSlider.
Now you need to add an action that will be called when the slider value changes. You already created an action for your button in Part 1; adding an action is very much like creating an outlet, so you’ll get a little more practice!
Select the slider and Ctrl-Drag to ViewController.swift anywhere within the class definition:
In the popup window, be sure to set the connection as an action rather than a outlet. Name it sliderChanged, like so:
Now you need to update the label whenever the action is called.
Open ViewController.swift and add the following code inside sliderChanged()
:
let amount = amountSlider.integerValue amountLabel.stringValue = "Amount: [\(amount)]" |
A quick review of the code above shows that you first read the slider’s current value. Then you set the value of the label to a string containing the slider’s value. Please note, that although amountLabel
cannot be edited by the user from the UI, it is still editable programmatically.
Note: This example uses integerValue
to get a nice round number, but if you need more precision you could use either floatValue
or doubleValue
for your slider.
Build and run the app. Try moving the slider back and forth to see the label update with the slider’s current value:
There’s one small problem. Did you notice it? The label doesn’t display the correct value when the app first launches! While it’s not a big problem, it makes the app look unfinished. It’s because the label is only updating when the slider’s knob is moved.
Fear not — it’s very easy to fix.
Add the following code to the end of viewDidLoad()
:
// Update the amount slider sliderChanged(self) |
Now the app will call sliderChanged()
at launch, and that will update the label. Neat!
Build and run. The label now displays the correct value at first run, which is a small touch, but is one of those “fit and finish” elements that make your app look polished.
What about more complicated values, such as calendar dates? Yup, macOS has those handled too! :]
Hot Date Tonight — NSDatePicker
Date Picker is a macOS control that display date and time values, as well as providing a method for the user to edit those values. Date Pickers can be configured to display a date, a time or both a date and time. The control responsible for this on macOS is NSDatePicker.
Date Pickers can be displayed in one of two styles: textual, where the date and time information is shown in text fields, and graphical, where the date is represented by a calendar and the time by a clock. You can find examples of all these styles in macOS’ Date & Time preferences panel, as in the screenshot below:
The most common tasks you’ll perform with a date picker are getting and setting the date or time value, and setting the minimum and maximum date or time values that are permitted in your control. The properties to do this are set out below!
// getting & setting the date/time value let myDate = myDatePicker.dateValue myDatePicker.dateValue = myDate // getting & setting the minimum date of the range let theMinimumDate = myDatePicker.minDate myDatePicker.minDate = theMinimumDate // getting & setting the maximum date of the range let theMaximumDate = myDatePicker.maxDate myDatePicker.maxDate = theMaximumDate |
Again — the controls have a very simple getter and setter style interface to update these values. Now it’s time (pardon the pun!) to put this control to work.
I’m Late for a Very Important Date
Following the usual procedure, add a new Label to your window. Change its title to Date: and its alignment to Right. Find the Date Picker control in the Object palette, and drag it onto the window, placing it to the right of the label. Resize the window and move the Go! button down if needed:
Create an outlet for the date picker, just as you’ve done for each of the previous macOS controls. In the popup window, name the property datePicker.
Just like the other macOS controls in your app, it’s nice to display a default value to the user when they first run your application. Picking today’s date as the default sounds like a good choice! :]
Open ViewController.swift and add the following code to the end of viewDidLoad()
:
// Set the date picker to display the current date datePicker.dateValue = Date() |
Build and run the app! You should see your shiny new date picker displaying current date, like in the screenshot below:
Video Killed the Radio…Button
Radio buttons are a special type of control that always appear in groups; they are typically displayed as a list of options with selectable buttons alongside. Their behavior is also somewhat unique; any button within the group can be selected, but selecting one button will deselect all other buttons in the group. Only a single button, within the same group, can be selected at one time.
A good example of radio buttons that are used to present a set of options is the iTunes Back Up options, as shown below:
Radio buttons are organized in radio groups.
When you click a radio button in a group, it is selected and the system automatically deselects the rest of the buttons within that group. You only need to worry about getting and setting the proper values. How convenient!
But how do you define a group? Quoting the documentation:
“Radio buttons automatically act as a group (selecting one button will unselect all other related buttons) when they have the same superview and -action method.”.
So, all you need to do is add the radio buttons to a view, create an action, and assign that action to all the buttons in the group.
Then you just need to change the state of one button (On / Off) and the system will take care of deselecting the others.
// Select a radio button radioButton.state = NSOnState // Deselect a radio button radioButton.state = NSOffState // Check if a radio button is selected. let selected = (radioButton.state == NSOnState) |
Once again, a complicated control is reduced to some very simple methods. Read on to see how to implement a radio button control in your app!
A Place to Call Home – Adding Radio Buttons
Add a new Label to your app (you should be getting pretty comfortable with this by now!), and change its title to Place:. Locate the Radio Button in the Object Library palette, and drag three onto the content view, just beside the label. Double click on the radio buttons to change their titles to: RWDevCon, 360iDev and WWDC respectively.
Now, you need to create a new outlet for every radio button — another action you should be quite familiar with by now! Open the Assistant Editor and Ctrl-Drag the first radio button into the ViewController.swift source file, just below the existing properties. Name the outlet rwDevConRadioButton. Repeat the process for the other two radio buttons, naming the outlets threeSixtyRadioButton and wwdcRadioButton respectively.
Build and run.
Click on the radio buttons, and you’ll immediately notice a problem. You can select all of them. They’re not behaving as a group. Why? Because there are two conditions for different radio buttons to act as a group: They must have the same superview (which is the case), and they need to have a common action. The second condition is not met in our app.
To solve that, you need to create a common action for those buttons.
You are already an expert creating actions using Interface Builder, right? So, to learn an alternative way to do it, you’re going to create the action in code and assign it to the radio buttons in Interface Builder.
Open ViewController.swift and add the following code inside the class implementation:
@IBAction func radioButtonChanged(_ sender: AnyObject) { } |
That’s a typical action method which includes the @IBAction
annotation so that Interface Builder can find it and use it. Now, open Main.storyBoard to assign this action to the radio buttons.
Go to the Document Outline and Control-Click (or right click) on the View Controller. Go to the Received Actions section in the popup window, and drag from the circle next to radioButtonChanged:
onto the RWDevCon radio button. With that simple action you’ve just assigned the action to that radio button.
Repeat the process to assign the same action to the other two radio buttons. Your received actions section should look like this:
Build and run:
Now the radio buttons are behaving like a group.
Now you’ll need to to make the RWDevCon radio button the default when the app starts. You just need to set the radio button state to On, and then the other buttons will be automatically deselected.
Open ViewController.swift. Add the following code to the end of viewDidLoad()
:
// Set the radio group's initial selection rwDevConRadioButton.state = NSOnState |
Build and run the application. You should see the RWDevCon radio button selected when the app starts.
Radio buttons are one way to toggle values in your app, but there’s another class of macOS controls that perform a similar function — check boxes!
Ticking all the Boxes — Check Box Button
You typically use check boxes in an app to display the state of some boolean value. That state tends to influence the app in some way such as enabling or disabling a feature.
You will likely find check boxes where the user can enable or disable a functionality. You can find them in almost every screen of the Settings app. For instance, in the Energy Saver window you use them to enable or disable the different energy options.
Working with check boxes is relatively easy; most of the time you’ll only be concerned with getting and setting the state of the control. The state of the check box can be one of these: NSOnState (feature on everywhere), NSOffState (feature off everywhere) and NSMixedState (feature on somewhere, but not everywhere).
Here’s how you can use it:
// Set the state to On myCheckBox.state = NSOnState // Set the state to Off myCheckBox.state = NSOffState // Get the state of a check box let state = myCheckBox.state |
Super simple! Time to add a checkbox to your app.
Check and Double Check – Adding Checkboxes
Open Main.storyboard. Find the Check Box Button in the Object Library and drag it onto the content view. Double-click on it to change its title to Yell!! as in the image below:
Now add an outlet for the check box and name it yellCheck. You are now officially an expert creating outlets!
Now, you’ll make the check box default to the off state when the app launches. To do that, add the following at the end of viewDidLoad()
:
// set check button state yellCheck.state = NSOffState |
Build and run the application! You should see the check box, and it’s state should be unchecked. Click it to see it in action:
Choices, Choices – NSSegmentedControl
A segmented control, represented by the NSSegmentedControl class , represents an alternative to radio buttons when you need to make a selection from a number of options. You can see it in Xcode’s Attributes Inspector:
It’s very easy to use. You just need to get or set the selected segment to find out the user’s selection.
// Select the first segment segmentedControl.selectedSegment = 0 // Get the selected segment let selected = segmentedControl.selectedSegment |
Tuning the Voice – Adding Segmented Controls
If you remember, the readSentence()
had a parameter to control the voice speed (Normal, Fast, Slow). You’ll use a segmented control to change the speed of the voice.
Open Main.storyboard and add a Label to the content view. Change its title to Voice Speed:. Locate a Segmented Control and drag it onto the content view. You can double click on every segment of the control to set its title. Change the titles to Slow, Normal and Fast respectively.
Create an outlet for that segmented control in ViewController
, and name it voiceSegmentedControl. Now, you want to select the Normal segment when the app starts, which is the segment number 1 (segment numbers are zero based). Open ViewController.swift and add the following code to viewDidLoad()
:
// Set the segmented control initial selection voiceSegmentedControl.selectedSegment = 1 |
As easy as it looks. Just set the selectedSegment
property to 1. Build and run now and see how the Normal segment is selected.
Okay! You’ve finally added all the controls you need to create your funny mad lib sentences. All you’re missing is a way to collect the value of each control, combine those values into a sentence, and display it on-screen!
Pulling it All Together
You need two more controls to show the results: a label to display the complete sentence, and an image view to display a picture, which should liven up the user interface!
Open Main.storyboard. Find the Wrapping Label in the Object Library palette and drag it onto the window, just below the Go!! button. Make it look a little more attractive by using the Attributes Inspector to change the border of the label to Frame, which is the first of the four buttons.
After that, remove the default text of the label by double-clicking it, selecting the text and deleting it.
Now you have to create an outlet to set the value of this new label to contain your new hilarious sentence! As before, Ctrl-Drag the label to the ViewController.swift file and name the property resultTextField.
Leave this control as it is for now; you’ll write the code that populates it in just a bit.
Room with a View — NSImageView
An Image View is a simple and easy to use control that — surprise! — displays an image. Bet you didn’t expect that! :]
There are very few properties you need to interact with an Image View at runtime:
// Get the image from an image view let myImage = myImageView.image // Set the image of an image view myImageView.image = myImage |
At design time, you can configure the visual aspects: the border, scaling and alignment. Yes, these properties can be set in code as well, but it’s far easier to set them in Interface Builder at design time, as below:
Just a Pretty Face – Populating the Image Well
Time to add an image view to your application! Find the Image Well in the Object Library and drag it onto view, to the left of the wrapping label. Feel free to resize the app window if necessary.
Create a new outlet for the image view in the same way you’ve done for all the previous controls: Ctrl-Drag the image view to the ViewController.swift file, and in the popup window name the property imageView.
Build and run. Your app should now look like this:
Phew! Your user interface is finally finished — the only thing that’s left to do is to create the code that will assemble your hilarious sentence and populate the image view that you added above!
Time To Make It Work
Now you need to construct the sentence based on those inputs.
When the user clicks the Go! button, you’ll collect all the values from the different controls and combine them to construct the full sentence, and then display that sentence in the wrapping label you added previously.
Then, to spice up your all-text interface, you will display a picture in the image view that you added in the last section.
Download the resources file for this project (normally goes into your Download folder). If the downloaded zip file was not unzipped automatically, unzip it to get the face.png image file. Select Assets.xcassets in the Project Navigator, and drag the image file into the assets list.
It’s finally time to add the core of the application — the code which constructs the Mad Lib sentence!
Open ViewController.swift and add the following property inside the class implementation:
fileprivate var selectedPlace: String { var place = "home" if rwDevConRadioButton.state == NSOnState { place = "RWDevCon" } else if threeSixtyRadioButton.state == NSOnState { place = "360iDev" } else if wwdcRadioButton.state == NSOnState { place = "WWDC" } return place } |
This code adds a computed property that returns the name of the place based on which radio button is selected.
Now, replace all the code inside goButtonClicked()
with this:
// 1 let pastTenseVerb = pastTenseVerbTextField.stringValue // 2 let singularNoun = singularNounCombo.stringValue // 3 let amount = amountSlider.integerValue // 4 let pluralNoun = pluralNouns[pluralNounPopup.indexOfSelectedItem] // 5 let phrase = phraseTextView.string ?? "" // 6 let dateFormatter = DateFormatter() dateFormatter.dateStyle = .long let date = dateFormatter.string(from: datePicker.dateValue) // 7 var voice = "said" if yellCheck.state == NSOnState { voice = "yelled" } // 8 let sentence = "On \(date), at \(selectedPlace) a \(singularNoun) \(pastTenseVerb) \(amount) \(pluralNoun) and \(voice), \(phrase)" // 9 resultTextField.stringValue = sentence imageView.image = NSImage(named: "face") // 10 let selectedSegment = voiceSegmentedControl.selectedSegment let voiceRate = VoiceRate(rawValue: selectedSegment) ?? .normal readSentence(sentence, rate: voiceRate) |
That may seem like a lot of code, but it’s fairly straightforward when you break it down:
- Here you’re getting the text from
pastTenseVerbTextField
- In this section of code, you get the string from the combo box by calling its
stringValue
property. You might ask why you don’t just look up the selected row, and then retrieve the string associated with that row. Quite simply, it’s because the user can enter their own text into the combo box. So use thestringValue
to get the current string, which could have been either selected or typed. - Next, read the slider’s current value using its
integerValue
method. Remember that if you need more precision with this control, you could also usefloatValue
ordoubleValue
. - Here you get the plural noun, selected from the popup button. How is this done? Look up the appropriate plural noun in your
pluralNouns
array using array subscript syntax and getting theindexOfSelectedItem
property. - Next up is the phrase the user typed. To acquire it, simply retrieve the string value of our text view by getting its
string
property. Again, you’re using nil coalescing since the property is an optional and could benil
. - To get the date, call the date picker’s
dateValue
method. Then, convert the returned date to a human-readable string using an NSDateFormatter. - Should you speak or shout? Simply get the checkbox state: if it’s
NSOnState
, assign yelled to the string variable. Otherwise, leave it as the default said. - At this point, you’ve collected all the information you need to construct the mad lib sentence! This is where the magic happens. The results constant uses string interpolation, and the different values read from the controls to build a string, based on the user’s input.
- Now you can display the results of all your hard work! First, display the sentence in the results label by setting its
stringValue
property. Then, add some pizazz to the app by displaying an image to the user, which is as easy as loading the image and setting the property of the image view control. - And finally, say it out loud! Get the voice speed based on the currently selected segment, and call the method that converts the text to speech
That’s it! You’re done! Build and run the app, so you can construct some hilarious sentences for yourself!
Congratulations — you’ve finished building the Mad Libs application, and have learned a ton about the most common macOS controls along the way.
Feel free to play with the controls, select different values, type funny nouns or verbs and see the results each time you click the Go! button, and see what funny stories you can create! :]
Where To Go From Here?
Here is the final project containing all the code from this tutorial.
In order to gain a deeper understanding of the controls provided by macOS, I recommend you have a read through the different programming guides available from Apple listed below, which contain a wealth of information about how to use the available controls.
In particular, I highly recommend you read the macOS Human Interface Guidelines. This guide explains the concepts and theories of user interfaces on macOS, and Apple’s expectations of how developers should use the controls and design their UI’s to provide a consistent and pleasurable experience. It’s essential reading for anyone intending to develop for the Mac platform, especially if they plan to distribute their applications via the Mac App Store.
Here are some useful links to reinforce or further explore the concepts you’ve learned in this tutorial:
- macOS Human Interface Guidelines
- Button Programming Topics
- Attributed String Programming Guide
- Combo Box Programming Topics
- Segmented control Programming Topics
- Application Menu and PopUp Programming Topics
- Image Kit Programming Guide
I hope you enjoyed this tutorial, and as always if you have any questions or comments please join the forum discussion below!
The post macOS Controls Tutorial: Part 2/2 appeared first on Ray Wenderlich.