Adaptive layout for iOS in Swift
How to adapt views and constraints without size classes
How to adapt a view dynamically in Interface Builder? Using the aspect ratio technique.
The idea is to constraint the view to its own aspect ratio and add the additional constraint of equal width or height of superview.
The choice of equal width or height depends on the plane based on which you want to resize the view proportionally.
This technique is illustrated in this GIF:
The view is adapted! But what about the constraints? We don’t have an easy way to adapt the constraints based on device screen size.
In this article, I’ll explain how we could address this issue both in the storyboard and programmatically through the use of a simple helper and custom class.
Let’s dive in!
We have to define Device enum and conform it to Raw Representable.
Device enum will help us keep the device screen dimensions in one place.
First, we have to specify Device enum cases. Based on the app you’re building, the enum cases will vary. In the example below, I’ve added only iPhone devices since I don’t intend to support the iPad.
Second, to set case raw value as CGSize, we have to conform enum to Raw Representable.
To conform to raw representable we have to add a typealias for raw value, in our case we have to specify CGSize:
Third, we have to implement init with our raw value type:
And lastly, add a rawValue computed property:
That’s it! All that’s left is to specify the concrete device model based on which the app design was made:
Adaptive Layout Helper
This helper contains resized and adapted functions as well as dimension computed property.
Let’s review them in order.
In order to get the current device screen dimensions, we have to call UIScreen.main.bounds.size:
To adapt CGFloat in base dimension (design dimension) passed to the function, first we need to calculate the ratio of base dimension size to base screen size:
Then we have to multiply the current screen width or height by the ratio to get the adapted CGFloat for the current device screen size:
The main purpose of the resized function is to resize passed CGSize preserving the initial aspect ratio. We can choose which dimension will be taken into account when resizing the base CGSize: width or height.
There are three steps to resize the base CGSize to the current device screen size:
First, we calculate the aspect ratio of the original CGSize:
Second, we need to calculate the new dimension size (width or height), which was chosen as the base dimension that will be used for resizing:
Third, we have to multiply the new dimension size (width or height) by the aspect ratio to get the other dimension size resized according to the initial aspect ratio:
Dimension computed property
This computed property will help us dynamically change the dimension based on the device orientation. The use of this property is only justified if the app supports both orientations (landscape and portrait).
Adapted constraint class
Adapted constraint is a subclass of NSLayoutConstraint. The main task of this class is to adapt the constraint’s constant. Let’s take a closer look at it.
It has initialConstant which holds an optional initial constant of the constraint. We need this property in order to reset the constraint’s constant after orientation change.
Then the class overrides awakeFromNib function in which we pass two functions saveConstant and adaptConstant:
Let’s review the functions.
In adaptConstant function, we adapt constant by using the adapted function from Adaptive Layout Helper.
To get the proper dimension for the adapted function, we use another function called getDimension.
To get the dimension for constraint we pass NSLayoutConstraint.Attribute to getDimension function. Inside the function body, we switch attributes.
In the first case, we enumerate all NSLayoutConstraint.Attribute which represent the width dimension.
In the second case, we enumerate all attributes which represent the height dimension.
If constraint’s attribute doesn’t match any case, we’ll return nil.
The function is pretty simple, it assigns a constraint’s constant to initialConstant variable allowing us to reset the constant later on.
The function assigns initialConstant value to self.constant thus resetting the constraint’s constant.
To adapt font size, we need to extend CGFloat with adaptedFontSize computed property:
For convenience, let’s create a font enum which will contain static functions of font typefaces:
In the UIFont initializer, as a size parameter, we will pass a font size modified by adaptedFontSize computed property. Now when we call any of our static functions it’ll return a font size adapted to the current screen dimension.
Below you can see the usage example:
To adapt constraints in Interface Builder, we now can use AdaptedConstraint class.
In the example below, I’ll add the top and bottom constraints to my UIView and specify AdaptedConstraint class in Identity Inspector.
That’s it! Now the constraints will be adapted to all screen sizes.
We will create a button programmatically and constraint it using NSLayoutConstraint with adapted constants.
First, we should initialize our constraints:
To resize the button proportionally, we’ll use the resized method to get adapted CGSize:
The result will be used as a constant for height and width constraints:
All that’s left is to activate constraints and call the setup function inside viewDidLoad:
If you need to support both portrait and landscape orientations, add a UIView extension called updateAdaptedConstraints:
This extension allows us to get all AdaptedConstraints of the UIView, reset their constants, and adapt constants to the new screen dimension. These actions are necessary in order to adapt constraints when the orientation has been changed.
The updateButtonConstraints function updates button constraint constants when orientation changes.
updateButtonConstraints function alongside with updateAdaptedConstraints function are wrapped inside updateConstraints function:
Which is then called inside viewWillLayoutSubviews in order to respond to device rotation:
Here you can see the screenshots from different iPhones that have different screen dimensions:
You can get tutorial source code and have a look at usage example in my GitHub repository: https://github.com/creimbord/adaptive-layout-uikit/tree/master/AdaptiveLayoutUIKit