Creating custom UI components in iOS with Swift


How to create custom UI components in iOS with Swift?

All the examples below are also found here— https://github.com/kirillzzy/CustomButton


After researching about creating custom buttons in Swift, I found very few facts about UIButton properties, which developers insistently try to customise. In this article I’m going to talk about how to create custom components and how to implement them.

Idea and Purpose

“The time it takes to make a decision increases as the number of alternatives increases.” — William Edmund Hick

Sometimes you will need to implement some components with an unusual logic. You may try to find the best way but you might not come up with an elegant solution. To get a grip on UI customisation, I suggest you to practise by creating custom components.

Design

Standard changeable aspects of UIButton are text, image, background color, shape and a few other minor things. However, if you need to create animations once the button is pushed, or maybe if you want to change the layout and size of it, the best solution is to create this button as a custom one.

Ambiguous Logic

Some of Apple’s solutions are ambiguous at best. Some developers could say that it’s not a bug, it’s a feature, but to improve it just make a custom button. For instance, when you press on a button with a small image, the image highlights. And the best solution here is to highlight the background instead of the image.

Quick Start

So let’s start, and create our custom button with the power of Swift.

Beginning

  1. Let’s create our xib view file called MyButton and drag components that we need (UILabel, UIImageView …).
  2. Let’s create a class called MyButton and inherit it from UIButton.
  3. Set our class as custom at xib file in identifier and drag all IBOutlets to class.

Loading from xib

Now we should initialize our xib file in class. So, there are a lot of correct solutions, but I have a good automatic helper for it. (Thank you Alex for helping me with that).

override func awakeAfter(using aDecoder: NSCoder) -> Any? {
return self.loadFromNibIfEmbeddedInDifferentNib()
}

awakeAfter will put another object in place of the object that was decoded and subsequently receive this message aDecoder.

So, loadFromNibIfEmbeddedInDifferentNib() is a helper that will load our xib.

extension UIView {
func loadFromNibIfEmbeddedInDifferentNib() -> Self {
let isJustAPlaceholder = subviews.count == 0
if isJustAPlaceholder {
let theRealThing = type(of: self).viewFromNib()
theRealThing.frame = frame
translatesAutoresizingMaskIntoConstraints = false
theRealThing.translatesAutoresizingMaskIntoConstraints = false
return theRealThing
}
return self
}
}

Here we ask if our view had been already initialized. If yes, then we just return self. Otherwise, we return new viewFromNib with some properties. On the first run subviews.count will equals zero because view’s components haven’t been initialized yet. We will initialize them via our own viewFromNib() extension method.

This method that we call in loadFromNibIfEmbeddedInDifferentNib() will initialise UINib. The main logic is below.

extension UIView {
class func viewFromNib(withOwner owner: Any? = nil) -> Self {
let name = String(describing: type(of: self)).components(separatedBy: ".")[0]
let view = UINib(nibName: name, bundle: nil).instantiate(withOwner: owner, options: nil)[0]
return cast(view)!
}
}
private func cast<T, U>(_ value: T) -> U? {
return value as? U
}

We need to get a hold on the xib file from our code somehow. In order to do this we need to figure out its name. The easiest way is to get the name string from our custom class name type “NameOfClass.Type”.

Then we initialize UINib with this name and then return it as Self. Method cast(_:) which will transform variable view that has type Any to UIView and return it.

Congratulations! We have successfully loaded our xib! It’s time to run our project and see the custom button with all components we dragged to our xib file.

“Smart” Components

After successfully creating our custom button let’s implement some components. Remember, it was the main reason we implemented our button subclass.

States

State is something that we can control and can use to understand if something changes after user actions. So, when states change, we should change components that we need. For example, the user taps on a button and then size of the button changes.

We have 3 states:

  1. isEnabled: Bool { get set }
  2. isSelected: Bool { get set }
  3. isHighlighted: Bool { get set }

If we override them, we will control their changes and then change our components. I’m not saying this is the best way to control states, but one of many.

override var isHighlighted: Bool {
didSet {
if oldValue != isHighlighted {
updateAppearance()
}
}
}

And override each state like this…

Updating Appearance

Now we should update the appearance of our button after changing a state. For instance, change background color or change shape and layout.

private func updateAppearance() {
if (isSelected || isHighlighted) && isEnabled {
buttonTouchedIn()
} else {
buttonTouchedOut()
}
alpha = isEnabled ? 1 : 0.8
}

buttonTouchedIn() will be called if button pressed and buttonTouchedOut() contrary, after pressing.

The only thing left to do is to implement these two methods where we’ll overdraw our button in order to get button like in the example at the beginning of this article.

TouchedIn

In this method we have freedom as to how to change our button.

private func buttonTouchedIn() {
backgroundColor = UIColor.random // as an example
textForLabel = "."
startTimer() // add '.' every millisecond
UIView.animate(withDuration: 0.3, animations: {
self.viewHeightConstraint.constant += 40.0 // change size
self.layoutIfNeeded()
})
animateClouds() // animate something inside
}

TouchedOut

And there we should return our button to the initial state.

private func buttonTouchedOut() {
backgroundColor = oldValue // some old value (example)
invalidateTimer() // stop the timer
label.text = constantExampleText
UIView.commitAnimations()
UIView.animate(withDuration: 0.3, animations: {
self.viewHeightConstraint.constant -= 40.0 // initial size
self.layoutIfNeeded()
})
}

Result

And now we have a button that can control states of pressing. Those properties will help us to do something after changing the state. We can make any changes in component design and behaviour. For instance, we can make some improvements on the design side and change background color, shape and create animations. In the end we return our button to the initial state it had before pressing.

Conclusion

All in all, I’m not saying that this code is perfect or that this design is the best, I’m just showing you an example of implementation that I haven’t found in other sources.

In this article we discussed why we should create custom components and found out some ways to implement custom buttons. Such buttons can be used everywhere, and most importantly— you can maintain approximate design in the whole app.

So, this is my first article, which I made after a speech about custom buttons at a great iOS meetup. I’ll be waiting for your suggestions and advices. Thanks!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.