
How we made dynamically changed layout for iOS
What is dynamically changed layout?
This is a layout that differs due to the difference in device sizes. I encountered this kind of problem while working on my last project. The main idea was to scale everything that we have on the screen; as bigger screen demands more adjustments.
How can it be solved?

The first idea that you may get is “Size Classes”. Actually, we also used this at first, and well, that was not enough. When a project grows, controllers grows as well. This leads to using more different constraints for each size class. It makes your life as difficult as hell when you try to change anything. These were not all the problems that we encountered using this idea. Some controllers have so complex UI that it differs almost from any device, so we were forced to use almost all possible size classes. Imagine when you need to change something for iPad, probably you will waste a lot of time searching for the place where that is. A bigger problem comes out when we don’t want to change the constraint constant, but its multiplier or priority. That’s actually not possible within the size classes. Instead, you need to make different constraints for each size class with required values in it, otherwise, it will be disabled for other size classes of this constraint.
What do we get in the end?
Eventually, such approach is really hard to support, it’s not flexible, storyboards become really complex, we have a lot of constraints which are hard to deal with. In the end, it becomes impossible to differentiate devices such as 4s, 5, 6 etc…(in future the list may become bigger).
What about devices with new resolution?
We need to create new UI for these. This means that we need to update our code when new devices may be released and upload new versions to Appstore. That is probably not the best solution for a project, as this update may be performed by other developers that aren’t familiar with previous project architecture. The only benefit I can find is that all UI staff are performed in UI file and are not connected with code. That makes our code much clearer.
Even with all its cons, we can accept such approach, as when we switch it on, it has the possibility to scale UI for ALL devices, which is impossible with Size Classes.
The conclusion we can make is to neglect the size classes.
What’s next?
The next step we make is to change constraint values from code. Due to the difference in devices, we use different values. How good is this approach? Actually, this may be strange, but it’s definitely better than Size classes. We easily separate this code from all the rest. Yeh, it’s not the clean separation, and amount of code rises enormously.
In the end, the amount of code increases sharply. It takes a lot of time to add new views or to change existing one. Still, it becomes much clearer for developers and the weight of storyboard files decreases. Such approach allows us to have the possibility to cover all devices. The problem with future devices are still present, no one wants to go back to their previous project and add new values for ALL views. It will be hell for developers especially if the project was a big one.
I would like to emphasize that all written above can be applied to fonts as well as for constraints.
Next two solutions are probably the best, with its advantages and disadvantages. In the end, you just need to make your decision on which one is better for you.
What was the main problem of previous approach?

There was huge amount of code that we needed to add to new views. It takes a lot of time and code becomes realy complicated and ugly. The question was where to remove this code or how to do it much simplier for us? The solution was found in IBInspectable properties. We added to contraints property for each device that was changing constraint constants. Here is an example of code how it was implemented.
@interface NSLayoutConstraint(BBBDevices)@property (nonatomic, assign) IBInspectable CGFloat iPhone4Constants;
@property (nonatomic, assign) IBInspectable CGFloat iPhone5Constants;
@property (nonatomic, assign) IBInspectable CGFloat iPhone6Constants;
@property (nonatomic, assign) IBInspectable CGFloat iPhone6PlusConstants;
@property (nonatomic, assign) IBInspectable CGFloat iPadConstants;
@property (nonatomic, assign) IBInspectable CGFloat iPadProConstants;@end@implementation NSLayoutConstraint(BBBDevices)#pragma mark - Properties- (void)setIPhone4Constants:(CGFloat)iPhone4Constants {
objc_setAssociatedObject(self, @selector(iPhone4Constants), @(iPhone4Constants), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if ([DeviceOperations deviceModel] == DeviceModelIPhone4) {
self.constant = iPhone4Constants;
}
}- (void)setIPhone5Constants:(CGFloat)iPhone5Constants {
objc_setAssociatedObject(self, @selector(iPhone5Constants), @(iPhone5Constants), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if ([DeviceOperations deviceModel] == DeviceModelIPhone5) {
self.constant = iPhone5Constants;
}
}- (void)setIPhone6Constants:(CGFloat)iPhone6Constants {
objc_setAssociatedObject(self, @selector(iPhone6Constants), @(iPhone6Constants), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if ([DeviceOperations deviceModel] == DeviceModelIPhone6) {
self.constant = iPhone6Constants;
}
}- (void)setIPhone6PlusConstants:(CGFloat)iPhone6PlusConstants {
objc_setAssociatedObject(self, @selector(iPhone6PlusConstants), @(iPhone6PlusConstants), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if ([DeviceOperations deviceModel] == DeviceModelIPhone6plus) {
self.constant = iPhone6PlusConstants;
}
}- (void)setIPadConstants:(CGFloat)iPadConstants {
objc_setAssociatedObject(self, @selector(iPadConstants), @(iPadConstants), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if ([DeviceOperations deviceModel] == DeviceModelIPad) {
self.constant = iPadConstants;
}
}- (void)setIPadProConstants:(CGFloat)iPadProConstants {
objc_setAssociatedObject(self, @selector(iPadProConstants), @(iPadProConstants), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if ([DeviceOperations deviceModel] == DeviceModelIPadPro) {
self.constant = iPadProConstants;
}
}- (CGFloat)iPhone4Constants {
return [objc_getAssociatedObject(self, @selector(iPhone4Constants)) floatValue];
}- (CGFloat)iPhone5Constants {
return [objc_getAssociatedObject(self, @selector(iPhone5Constants)) floatValue];
}- (CGFloat)iPhone6Constants {
return [objc_getAssociatedObject(self, @selector(iPhone6Constants)) floatValue];
}- (CGFloat)iPhone6PlusConstants {
return [objc_getAssociatedObject(self, @selector(iPhone6PlusConstants)) floatValue];
}- (CGFloat)iPadConstants {
return [objc_getAssociatedObject(self, @selector(iPadConstants)) floatValue];
}- (CGFloat)iPadProConstants {
return [objc_getAssociatedObject(self, @selector(iPadProConstants)) floatValue];
}
@end
As you can see it’s pretty simple in implementation and thanks to it we get rid of ourselves the pain going through all that. We remove all UI layout building code to UI files. As at now we just need to input necessary values for each device for specific constraints. It saves a lot of time. We are really happy that such a simple solution can solve such a complex problem. The same code was written for labels and buttons fonts changing. I think this part does not need description. Though, I wasn’t satisfied enough. What then shall we do with new devices? Is it fine to give away a product that needs to be reimplemented each time when a new device comes out? As for me, it was just a good solution, but not a perfect one though.
I started searching other solutions that would fully satisfy me.
Finally, I found my silver bullet. It’s dynamically changeable layout. I named it so don’t worry if you don’t understand it at first. The main idea of such an approach is that “everything should be implemented as a proportion”. Just set proportional height, connecting your height to another one, in most cases, it was controllers height as it was a static value. Let’s check it out and see how all constraints are represented now. From time to time I will give you some useful tips that I learned using this approach.

The first tip is that “If you ever used constraint constant not equals to 0, then probably you are doing something wrong”. Probably it’s not a proportion but a static value and this constant should be changed for other screen resolution to complete proportion.
Let’s get back to our constraints.
Aspect Ratio — it transforms into aspect ratio because actually, it is a proportion.
Height and Width — you set height equally to some other height of objects and setting the multiplier to fit in due to requirements. Usually, it is the screen’s height or width. When a device size changes the size of height and width changes as well.
Another tip is to try connecting the height or width to some objects that will be rarely changed. For example, the screen is a pretty good choice for some parent view.
Center Y or Center X — you can use multipliers if the object is not exactly in the center. It will then move further from the center within a bigger screen. Easy, isn’t it? That’s not everything. What about Leading and Trailing space? How can we make them proportional? Constraint multiplier won’t help us, so we found another solution. You need to create a view with a zero height, proportional width and place it somewhere vertically in order to avoid warnings. Next, you need to connect this view with views that need to have space in-between, one to left and one to the right side with leading and trailing constants being equal to zero. As a result, the width view will increase with bigger device size. As you are connected to this view, space between connected views will increase as well. That is exactly what we are looking for. Bottom and top spaces are similar to Leading and Trailing space, the main difference is that we will have zero width, proportional height, and horizontal placement. The connection will be made from the top and bottom of our help view.
There is one other helpful thing. Building such a UI will cause a lot of problems in the previously described help views. To structure this, place them inside other views where you can find them easily and give them common names to understand what you have connected with.
Huh, it was hard but we did it.

For scaling fonts, we can use Minimum Font Scale component, etc. Just need to configure it in a proper way.
What we get in the end is that our layout will dynamically update if device screen size changes. Fonts will be automatically updated as well. There were simple cases described, you might face more complicated ones, but if you’ve grasped a general understanding of the topic, you can easily solve them. As you can see all problems that were described above are fixed within this approach.
More complex doesn’t mean it’s better. It would be the best solution unless we got new problems. The first and the main one is bad app performance. As we all know system recalculates all constraints using a system of linear equations. The more variables it has, the harder it gets to solve. With all these additional views we make it hard for the system to calculate needed layout. It creates performance issues on low devices and may even cause crashes if your UI is really complex, as working with constants always easier than working with dynamic values.
The second problem which occurs is complex UI files. It causes the fact that new developers will not be able to build good and flexible UI from start. I’m not talking about its sizes, they become bigger because of additional views we used.
All these problems may either be nothing for your project or may really be hell for you depending on the kind of project and how it fits.

To finish with, I just want to say that, I’m not waiting to recieve a medal for the best solution that has ever existed, I’m just sharing how we solved these problems. And as for me, the last two solutions are the best to recommend and they have their pros and cons. All you need is to choose what’s best and fits better into your projects and its requirements.
