- [Download Xcode 8.2.1 project with full Swift 3 source from GitHub.]
- [Download Xcode 8.2.1 playground with full Swift 3 source from GitHub.]
How would you enable or disable multiple user interface controls using one IBOutlet and one IBAction? For example, you might need to disable a UITextBox and UISegmentedControl because a user’s login has expired. Perhaps a user hasn’t filled in some required fields on a form, so you want to disable several buttons. Watch the following video to see how I built a Swift 3 app to use a UISwitch to enable or disable four controls all at one time — and I demonstrated the object-oriented programming (OOP) principle of polymorphism:
Refresh your memory about OOP and inheritance.
The UITextField, UISlider, UISegmentedControl, and UIStepper classes are all descendants (children) of the UIControl parent class. The UITextField, UISlider, UISegmentedControl, and UIStepper classes all inherit some common properties and methods from UIControl, like isEnabled. Interestingly, in Apple’s documentation, isEnabled is only defined in UIControl and not in any of the four descendants I showed in my video, so I would think they inherit enabling/disabling functionality from their parent class.
You can set any of the UITextField, UISlider, UISegmentedControl, and UIStepper classes’ isEnabled properties to true or false. A value of true allows the user to interact with the control. A value of false prevents the user from interacting with the control.
Y’all should know what an outlet is by now. The following video demonstrates how I created an outlet collection (IBOutletCollection in Objective-C) and connected the UITextField, UISlider, UISegmentedControl, and UIStepper to that same outlet collection:
I typed in the Swift 3 outlet collection declaration (shown immediately below), built the project so that the little connector dot showed up next to the “@IBOutlet” keyword, then [control]-dragged from the dot to each of the four controls, and finally made sure that each one highlighted when I dragged the mouse on top of the control (which meant an outlet connections was made):
1 |
@IBOutlet var multipleOutlets : [UIControl]! |
These steps resulted in an array of four different UIControls — and gave the array members a single name. This is really convenient if you have a large number of controls that you need to manipulate often, but note that you can still access each control separately. Look at Xcode’s Connections Inspector for each control and you’ll see its “Referencing Outlet Collections” (click to enlarge the image):
Remember we discussed options for finding, adding, and removing IBOutlets. [control]-click on another UIControl in our outlet collection (click to enlarge):
The point I’m trying to drive home is that we can leverage OOP polymorphism here. Remember that the origins (etymology) of the word “polymorphism” come “From Ancient Greek poly (many) + morph (form).”
With polymorphism, several descendant object/instance references can be assigned to a reference to the parent class, and yet the parent can exhibit the behavior of the descendant. In other words, you can declare a variable of a certain type (parent/UIControl) and it can store references of that type, but it can also store references to any child (subclass) of that type (here, UITextField, UISlider, UISegmentedControl, and UIStepper). This is where “poly (many) + morph (form)” comes into play.
Remember that multipleOutlets is declared as an array that can hold references to instances/objects of type UIControl. Yet I’ve connected outlets of type UITextField, UISlider, UISegmentedControl, and UIStepper — all child classes of UIControl — to multipleOutlets. The one multipleOutlets variable can take on many forms. I used an Xcode playground to illustrate what I’m talking about using Swift:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
var textField : UITextField = UITextField() var slider : UISlider = UISlider() var segmented : UISegmentedControl = UISegmentedControl() var stepper: UIStepper = UIStepper() var myControl : UIControl myControl = textField myControl.isHighlighted = true print("I am a \(type(of: myControl)) now.") myControl = slider myControl.isEnabled = true print("I am a \(type(of: myControl)) now.") myControl = segmented myControl.setNeedsLayout() print("I am a \(type(of: myControl)) now.") myControl = stepper let insets = stepper.alignmentRectInsets print("I am a \(type(of: myControl)) now.") |
Here’s the output from the Swift “print” statements in the code block immediately above:
1 2 3 4 |
I am a UITextField now. I am a UISlider now. I am a UISegmentedControl now. I am a UIStepper now. |
I wired up an IBAction (shown immediately below) that iterates over each UIControl descendant reference stored in multipleOutlets and sets their common isEnabled property to true or false (see highlighted lines 1, 11, 13, 18, 20):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
@IBOutlet var multipleOutlets : [UIControl]! ... @IBAction func enabledDisableControlsSwitchTapped(_ sender: Any) { let switchEnabledDisable = sender as! UISwitch if switchEnabledDisable.isOn { for control in multipleOutlets { control.isEnabled = true print("Control type: \(type(of: control))") } } else { for control in multipleOutlets { control.isEnabled = false print("Control type: \(type(of: control))") } } } |
Check out the output from the Swift “print” statement every time this IBAction is invoked. Note that a variable of type UIControl, multipleOutlets, can be assigned a variable of one of its descendant types — basically, it morphs into one of its child types:
1 2 3 4 |
Control type: UITextField Control type: UISwitch Control type: UISegmentedControl Control type: UIStepper |
Polymorphism enables you to develop general code that works with groups of related classes instead of developing code for each individual class. I could’ve expanded this example and accessed any common methods and properties declared in UIControl that are inherited by its children (subclasses). Let’s talk about the possibilities.
I could’ve checked (and set in two cases) the isSelected, state, or isHighlighted properties of any UIControls in my outlet collection.
I could’ve programmatically associated a method with any of my outlet collection’s UIControl instances using addTarget(_:action:for:) with this syntax:
1 2 3 |
func addTarget(_ target: Any?, action: Selector, for controlEvents: UIControlEvents) |
My development efforts could’ve gotten really granular by programmatically checking for user gestures on my UIControls with the class-family-common method beginTracking(_:with:) using the following syntax:
1 2 |
func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool |
Pretty powerful stuff, eh? We’ll soon go through another example of a parent variable being assigned child instance references. As always, leave a comment if you have questions or feedback. Thanks!
- [Download Xcode 8.2.1 project with full Swift 3 source from GitHub.]
- [Download Xcode 8.2.1 playground with full Swift 3 source from GitHub.]