Amazon Style Disclosure Button

UIButton With Right Arrow Disclosure Indicator

This post demonstrates a UIButton with a right arrow disclosure indicator, and includes a demo app.

Although this user interface paradigm is traditionally something you see for a UITableView, there are plenty of apps that are using such a component individually, such as the Amazon iOS app, where you can see the button for providing a review, as shown below.

We’ll create a UIButton that looks just like the Write a review button above.

To get started, I created the actual arrow image using the Sketch app. You could obtain such an image by extracting iOS artwork using tools, but I’m a big Sketch fan and usually do all my mobile app graphics with it. The link to the demo app at the end of this post includes the Sketch file, should you wish to modify it for your own needs, but the 1x, 2x, and 3x images are also included, so feel free to use those in your own projects if they suit your needs.

To achieve the desired functionality, I’ve created one method as an extension for UIButton that can be called for any button.

As you can see, it does not require much code. The disclosureButton method takes a color as the lone parameter, so it can be reused for buttons with different tint color requirements. The title color of the button is set for .normal and .highlighted to mimic a System button, and to achieve this effect an alpha of 0.3 is used for .highlighted.

Images are created for both of those states as well, using Template rendering mode so that the image can be tinted to match the text tint color.

The text in the button is left aligned in Interface Builder, so the key to positioning the arrow image just inside the right edge is to use imageEdgeInsets, and setting the X position of the image by taking the width of the button, and subtracting 1.5 times the image width, with moves the image just a little away from the button border.

A border is applied to the button for aesthetics, in a darkGray color since that works well for this demo, and finally, I’ve added padding to the top and bottom of the button using contentEdgeInsets.

You’ll notice that when the images are created, the imageHighlight has a method .alpha(0.3) which applies an alpha to the image just as we did with the title color. This method is realized via an extension on UIImage, and credit for that goes to Peter Kreinz who asked a question about this here on Stack Overflow and then answered it himself! Thanks Peter for the Swift 3 code snippet. The extension is shown below for simplicity, and the only change I made was to avoid a force unwrap by returning an optional UIImage. You can see that come into play when the method is called in the UIButton extension.

Now that everything is setup for customizing a button to become a disclosure indicator button, we can call the disclosureButton method in the ViewController. I’m using the default tint color for a UIView as the baseColor, but you can choose something that works for your own needs.

It is important to call the disclosureButton method in the viewDidLayoutSubviews method, and not viewWillAppear, so that the button is always setup after the bounds are known for the button, otherwise the arrow will not be placed correctly when the device is rotated. This is because the placement of the arrow indicator is dependent on the button bounds, and specifically the width of the button, which changes upon rotation.

There is one interesting aspect to the above code, which is the view.layoutIfNeeded call. Apple states the following about viewDidLayoutSubviews.

When the bounds change for a view controller’€™s view, the view adjusts the positions of its subviews and then the system calls this method. However, this method being called does not indicate that the individual layouts of the view’€™s subviews have been adjusted. Each subview is responsible for adjusting its own layout.

The key aspect of Apple’s guidance is that although the main view has adjusted the positions of its subviews, the individual layouts of the subviews may not have been adjusted yet. With only the iPad rotation, and only the first time, without the layoutIfNeeded, the arrow is not placed correctly because the bounds of the button subview have not been adjusted yet when viewDidLayoutSubviews is called. To overcome this phenomena, layoutIfNeeded is added prior to configuring the button. You can read more about layoutIfNeeded and setNeedsLayout in a previous post here on my blog.

The app that I originally implemented this disclosure button for only supports portrait, so I first noticed the need for layoutIfNeeded while writing the demo app to accompany this post. There may be another way to avoid forcing a layout, but I haven’t investigated further. If you have a different approach, it would be great if you would leave a comment.

When you run the app in portrait, you’ll see the screen looking very much like the Amazon screenshot above.

The demo app is just enough to demo this button, and I’ve wired up the button such that the app goes to a  placeholder review screen when the button is clicked.

You can download the demo app from my GitHub, and rotate it between portrait and landscape to see it in action.

Leave a Comment: