Open Mandala.xcodeproj and create a new Swift file named ImageSelector. Define a new UIControl subclass within this file.

The interface for this control will be set up much like the existing stack view of buttons. The primary difference, in terms of code, is that the ImageSelector will not be tied directly to the array of emoji images. Instead, it will hold on to an arbitrary array of images, allowing the control to be flexible and reusable.

Let’s start re-creating the interface. Add a property for a horizontal stack view and configure some of its attributes.

This stack view is an implementation detail of the ImageSelector type. In other words, no other types need to know about this property. To keep other files from being able to access selectorStackView, the property has been marked as private.

This is called access control. Access control allows you to define what can access the properties and methods on your types. There are five levels of access control that can be applied to types, properties, and methods:

open

Used only for classes and mostly by framework or third-party library authors. Anything can access this class, property, or method. Additionally, classes marked as open can be subclassed, and methods marked as open can be overridden outside of the module.

public

Very similar to open, but public classes can only be subclassed and public methods can only be overridden inside (not outside of) the module.

internal

The default level. Anything in the current module can access this type, property, or method. For an app, only files within the same project can access internal types, properties, and methods. If you write a third-party library, then only files within that third-party library can access them – apps that use your third-party library cannot.

fileprivate

Anything in the same source file can see this type, property, or method.

private

Anything within the enclosing scope can access this type, property, or method.

Now, implement a method that will configure the view hierarchy for the control.

The control should be able to be created either programmatically or within an interface file (such as a storyboard), and the view hierarchy needs to be configured in both cases. Override the initializer used for both of these situations and call the method you just created to configure the view hierarchy.

Next, add properties to manage the images, buttons, and selected index. Also add the method that will be called when a button is tapped. This code will be nearly identical to the code in MoodSelectionViewController.

The imageButtons property stores the images. When it is set, it creates and updates the array of buttons. This, in turn, updates the stack view to remove the existing buttons and add the new buttons.

When a button is tapped, the control needs to signal that its value has changed. To accomplish this, you call the sendActions(for:) method on the control, passing in the type of event that has occurred.

Update imageButtonTapped(_:) to send the associated actions.

The .valueChanged event is one of the UIControl.Events that were discussed in Chapter 5. UISwitch, UISlider, and UISegmentedControl are common controls that utilize the .valueChanged event.

The sendActions(for:) method will look through all the target-action pairs that have been registered with this control for the specified event (in this case, .valueChanged) and will call the action method on that target. All this is being handled for you by the UIControl superclass. Later in this chapter, you will register the MoodSelectionViewController as a target-action pair with the control and associate it with the .valueChanged control event.

The control is now ready for use, so let’s update the view controller to take advantage of this control.