API Design With Custom UIButton

Ali Fakih
5 min readMay 13, 2022

While working on a project you will have to deal with UI customization. and if you are working in a team you need to take into consideration that the UI customization will be used across the project by different developers. So to make sure that your customization works well from an API design perspective you will need to assure that your code assures those 5 points:

  • Ease of use
  • Discoverability
  • Capabilities
  • Domain definition
  • Future proof

In my case, I don’t have a real complicated UI customization, instead, it is a really simple one, but it shows how important following the API design specification is.

Flatten Button

This is what will call my customized button, it will have a new title and a loading indicator, and it will get updated based on the currentState .

Before we begin simply this is how it will look like:

The FlattenButton will inherit UIButton . By doing so, we will have all the functionality of the UIButton and we can override and customize what we need instead of building from scratch a button with different interactions.

State

The State in an enum type, we will be using it in FlattenButton to control and update its mode.

enum State {    case regular    case loading}

Regular

When the state is regular we’re going to display only a UILabel centered in the middle with custom padding

Loading

When it is loading, we need to display a custom loading indicator and update the UILabel position

Updating the title’s text & color

As you know the UIButton already natively have

func setTitle(_: String?, for: UIControl.State)

&

func setTitleColor(_: UIcolor?, for: UIControl.State)

and, at the same time, we want to update the title’s text and color based on the button's current state. Todo so I created 2 functions with the same naming but instead of having a state of a type UIControl.State we’re going to use FlattenButton.State . But, how we can make sure the developers are going to use those 2 new functions. well, with the help of the deprecated annotation, the autocompletion of these functions will be greyed out, and when the developer tries to use the native function he will get a warning message tells him that this function is renamed to it is corresponding with FlattenButton.State

@available(*, deprecated, renamed: “setTitleColor(color:loadingState:)”)

Update the UI

I want to note that in this project I’m using SnapKit to make AutoLayout easy.

The button on the first initialization will have currentState = .regular by default, and the developer can change the state simply by calling

flattenButton.currentState = .loading

so we need to have some KVO on the currentState like this

Whenever the value of the currentState gets changed we will call the validateState() which is going to update the UI accordingly.

if the currentState is regular , we need to make that the spinner is hidden, and set the text for the regular state.

To keep a record for the normalText, color, loadingText and color we need to define a new model StateContext a new struct that has title and color properties

We are going to have private 2 properties inside the FlattenButton to record the context for each state, and when the states’ value gets changed we update the UI and the context accordingly using those 2 properties.

Let us check how we are going to update the context:

so Simple right?

what is left is the constraints update

Constraints

As I will be using SnapKit, you will be seeing a SnapKit script.

As you can tell from the code above, I’m removing the instance of the spinner when I want to display the regular mode, and re-add it when it is loading .

And for the UILabel , I had to remake the constraints according to the positing and the state.

Why not updateConstraints ?

SnapKit has an issue with updateConstraints where it leads to crash on that version so this was the only I could do it back then or use native constant layout 😅.

And voilà we’re done creating FlattenButton . Now let us see how we’re going to use it:

As this is just for test purposes, I’m deploying for iOS 15 so I’m using configuration, but for the older version, you will need to deal with the button as in the old days.

calling setTitle:

animatedFlattenButton.setTitle(“Hold tight!”, for: .loading)

animatedFlattenButton.setTitle(“Press Me”, for: .regular)

Simply, similar to the native function, and with the deprecated annotation, the developer will never mistake which function he needs to use to make sure it will work properly

Changing state

I’m toggling the state based on the old value, but you get it.

animatedFlattenButton.currentState = animatedFlattenButton.currentState == .loading ? .regular : .loading

A straightforward implementation and easy to maintain, which will make all the API design points fulfilled.

Again it is a really simple project, A POC so you understand the points that you need to make sure of when you create your next APIs. I know when you work on a real project creating APIs is a pain in 🍑

To know more about how I Created the custom Spinner check it out here

You find the full project by clicking on Github. Don’t forget to hit follow on Twitter if you liked this Article it encourages me to write more ❤️

--

--

Ali Fakih

I'm a software dev & tech enthusiast who loves iOS, Swift, networking & electronics. Follow me for insightful articles on software & tech.