What I Learned Last Week: How to Translate a UI Design into iOS UIKit Elements — Part 2 of 2
In Part1, I focused on the big picture of the design and how to “see” the various iOS UIKit elements hiding within the design. In this article, I focus on the UITableViewCell and its content. As a reminder, here’s the design we’re studying:
Let’s have a quick refresh on tableviews to ensure the proper context for the remainder of the article. A tableview displays a single column of vertically scrolling content, divided into rows and sections. Each row of a table displays a single piece of information related to your app. Sections let you group related rows. For example, the Contacts app uses a table to display the names of the user’s contacts. The UIKit element for a tableview is UITableView.
Some tableviews display static content like the iOS Settings screen (e.g., rows always exist for General, Accessibility, Privacy, etc.). For a static tableview, its cells are individually configured by the app developer to display the specific information for each row/cell.
Other tableviews display dynamic content where the same type of information display in each cell, but the exact number of cells varies based on some data model such as a shopping cart containing zero to many items (see TableView 2 in the design screenshot above).
How do you know if you need a static or dynamic tableview? Here’s the critical question I use to decide on the type of tableview needed in a design: does the content depend on some data source/model in the app, or is it “hardcoded” never (or rarely) to change? If the content is “hardcoded,” then it’s static. Otherwise, it’s dynamic.
Here’s the critical question I use to decide on the type of tableview needed in a design: does the content depend on some data source/model in the app, or is it “hardcoded” never (or rarely) to change? If the content is “hardcoded,” then it’s static. Otherwise, it’s dynamic.
What is a tableview cell? It is the visual representation of a single row in a tableview. The UIKit element is UITableViewCell. Each tableview cell contains a view that can contain other UIKit elements to display the appropriate contents for the cell. For example, in the iOS Settings Screen above, there is a UILabel whose text property has an assigned value of “General” for one row. In contrast, another row has a UILabel whose text property has an assigned value of “Accessibility.” From our design example, each cart tableview cell includes a product image, a product name, a product quantity, and a control to change the product quantity.
Before we go further, let’s discuss a few additional UIKit elements typically included in tableview cells:
- A view (UIView) manages the contents for a rectangle area of the screen. Views are the fundamental building blocks of your app’s user interface, and the
UIViewclass defines the behaviors that are common to all views. A view object renders content within its bounds rectangle and handles any interactions with that content.
- A label (UILabel) is a view that displays one or more lines of informational text. You can configure the overall appearance of a label’s text, and use attributed strings to customize the appearance of substrings within the text.
- An image view (UIImageView) is an object that displays a single image or a sequence of animated images in your interface. Image views let you efficiently draw any image that can be specified using a
UIImageobject. For example, you can use the
UIImageViewclass to display the contents of many standard image files, such as JPEG and PNG files. You can configure image views programmatically or in your storyboard file and change the images they display at runtime. For animated images, you can also use the methods of this class to start and stop the animation and specify other animation parameters.
- A control (UIControl) is the base class for controls, which are visual elements that convey a specific action or intention in response to user interactions. Controls implement elements such as buttons and sliders, which your app might use to facilitate navigation, gather user input, or manipulate content. Our design example includes a specific type of UIControl.
Now that we have some basics under our belts, we can begin to “see” the UIKit elements within the tableviews and tableview cells in our design.
Let’s start with TableView 1 in the screenshot below.
At first sight, you might question whether this is a tableview at all. Why? The vertical orientation of the label does not match the expected layout of a tableview in iOS. However, don’t let the label orientation fool you. The “normal” horizontal orientation is just an orientation, like vertical is an orientation.
Actually, it is quite simple to recreate the exact Tableview 1 using a single line of code to rotate the label 90 degrees counterclockwise:
cell.textLabel!.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2) within your
tableView(_:cellForRowAt:) UITableViewDataSource method.
Note: AutoLayout for the UILabel is not applied when using a CGAffineTransform, so you’ll need to specify the layout constraints in your Swift code.
Alternatively, you could recreate Tableview 1 by using an UIImageView instead of a UILabel. The UIImage contained in the UIImageView would be a vertical version of each text element. While this method has the advantage of AutoLayout, it requires image assets of these “labels” for each language your app needs to support in addition to the standard localization efforts. This approach requires additional maintenance, so my preference in the prior approach.
Some aspects of the TableView 2 design are very evident. For example, the UILabels depicted in the screenshot. The UILabels are very straightforward and only differ in their formatting.
The UIStepper in TableView 2 is a specific type of UIControl used to increment or decrement a value. The control itself does not display the value on the screen. That value is displayed using a separate UILabel you add to the view. The value label displayed in our design example is on top of the UIStepper, which potentially complicates user touch interactions. In this instance, I find a better approach would be having a discussion with the designer to resolve the concerns through agreement to use a standard UIStepper or the additional development time required to implement a custom design. While the custom design might be enticing, we need to keep in mind that changes in future iOS versions may invalidate the custom design causing rework and increased total cost of ownership for the app. In these situations, I find it is better to “stick with the basics” to ensure better future iOS version compatibility.
The most complex part of the TableViewCell design in TableView 2 is the view displaying the product image. When examining the product image, you see a rounded gray background sitting behind the image, and the image extends beyond the gray background view. When you see overlapping contents, it is a clue that multiple views are needed. In this case, the simplest way to reproduce the design in iOS is to use layering UIViews with a UIImageView. Here’s the approach I used to produce the desired look:
- Add a UIView with a transparent (clear) background. I’ll call this view the “Base View.” Set its constraints to match the desired height for the cell, and the width should be wide enough to include the views in Steps 2 and 3 below. The Base View serves as the container for the view you add in Step 2 and the image view you add in Step 3.
- Add a UIView within the Base View and set it to be constrained in the lower-left corner of the Base View. I’ll refer to this view as the “Rounded View.” Set its background to match the gray color from the design. You can round the corners using
view.layer.cornerRadius = 15.0to apply the rounding effect to the view.
- Add a UIImageView within the Base View and set its background to transparent (clear). Constrain the UIImageView so an image appears to overrun the gray UIView, but is still contained within the Base View. Then, apply a rotation to the image view using
cell.shoeImageView.transform = cell.shoeImageView.transform.rotated(by: CGFloat(-Double.pi / 5)) // rotate counterclockwise.
Here are a couple of screenshots depicting the 3D view hierarchy in the Xcode Interface Builder so you can better see the overlapping views I described in the above steps:
The screenshot below shows the resulting TableView cell in the iOS Simulator:
Hopefully, from this article, and my prior article (Part 1), you have a much better appreciation for “seeing” the UIKit elements when you approach a UI design translation into an iOS app. Developing your “UIKit sight” helps you break apart seemingly intricate UI designs into their UIKit elements, making your iOS development less daunting, and achieving higher quality results.
Stay safe and keep learning!