While further developing an iOS app, one of the issues I had was the fact that I needed a view containing several components to be scalable by the user. There are a few approaches. We all know about auto-layout, and the older springs and struts approach. But these will not always work, or may not be a good fit.

This article is geared towards a scaling approach provided you know auto-layout or the ‘springs and struts’ (non auto layout) methods.

Why the typical approaches may not work

  • You need the scaling to be exact. Take an example where you have a label within a view. The font was sized so that most of the time the output takes up 50% of the width. If you auto-layout this and use minimum font size, when scaled down your font will generally be always 100% of the width, and when scaled up it would just end up being smaller in comparison to the rest of the views.
  • You don’t want to fiddle with auto-layout as you want the view to be the SAME proportions when sized differently.
  • You are rendering many dynamic objects through code, making it very difficult to work with auto-layout.

In my case all three of those issues were present.

Auto Resize won’t help you if Auto Layout is present

When searching online I found a lot of posts and tips about using autoresizesSubviews. This approach wont work for many as this will only work when auto layout is disabled. The typical approach is to set a view that you are sizing with like this:

_myViewContianer.autoresizesSubviews = YES;

I wasn’t able to get this working, but I later found out that it was because I didn’t set the sizing masks on the view on the inside. I don’t typically work with this, but here is an example piece of code for a view that would need to be resized based off the container view:

- (IBAction)runme:(id)sender {

    _viewBox.autoresizesSubviews = YES;
    _viewInner.autoresizesSubviews = YES;
    _viewInner.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;

    _viewInner_Font.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;

    _viewBox.frame = CGRectMake(100, 200, 100, 100);

}

Not sure how to enable/disable autolayout? See Ray Wenderlich’s article. While the code above worked for me, I wanted to keep auto layout.

Using Transform

Ultimately I found a solution that worked very well and it was by using view.transform. I can only imagine that view.transform is a more computationally expensive approach however sometimes it can be the right choice if used sparingly.

The code is as simple as this to half the size of a view:

_viewBox.transform = CGAffineTransformScale(CGAffineTransformIdentity, .5, .5);

Extra work can be done around this by recording the initial frame of the view that needs to be resized to know how much to transform, so that you are not stuck always only ever working with a set percentage, in this case .5 or 50%.

Lets say that you need to scale a view within it’s class and this view has a reference to its parent container as well which will be the view to fit the current view into. We have to not only transform, but perform equal scaling (assuming this is wanted) and ensuring the placement is centered within the parent view.

    //Equal scaling
    CGFloat scaleX = m_connectedView.frame.size.width/m_baseWidth;
    CGFloat scaleY = m_connectedView.frame.size.height/m_baseHeight;
    CGFloat scale = MIN(scaleX, scaleY);

    self.transform = CGAffineTransformMakeScale(scale, scale);

    //Center
    self.center = CGPointMake(m_connectedView.frame.size.width/2, m_connectedView.frame.size.height/2);

    //Relayout
    [self setNeedsLayout];

Recap

Taking a look at a solution I built, there is a view which acts as a container. Inside the view is a image view as well as a label. The label is filled initially with text which stretches from end to end.

The initial view without any sizing completed.

The initial view without any sizing completed.

Lets see what occurs when trying the autoresize method.

Autoresize

Autoresize

We can see that the image scaled, provided the UIImageView was set to scale to fill or a similar fill. The text however is not only clipped at the top and bottom but lengthwise as well. Using transform will allow a uniform scale as shown below:

Transform

Transform

Transform will keep everything, including text, at the original proportions!

Having Positioning Troubles with Scaling?

Me too. That’s why I came back to touch up on this so I don’t forget and to help others with scaling where views still want to shift around and not align properly.

Check your constraints

Always check constraints, they may look right, but under pressure of a scaled view they may throw things out of proportion.  This is usually the case.

Avoid using parent and self frame references in programmatic approaches

I just had an issue where everything looked fine but as I scaled more and more certain views shifted way out of center. In my case I was using drawRect along with positioning UIViews according to self.frame.

parent/self.frame can shoot you in the back! Lets say you have a view as part of your view to be resized. If the main view is updated for any reason, such as setNeedsDisplay being called AFTER the scaling has occurred the parent frame or self frame will not be in the original units but the scaled units as this is from the view controllers point of view. However, if you use these units within a scaled view, the output will be compounded due to this.

If for example, you had a 1000×1000 view, scaled to 50%, and you wanted to center a view within that views code you can not use self.frame.center. Doing so will return 250×250, when you actually need 500×500 because you must treat your views as unscaled from within. The solution here would be to store the initial size of the view’s frame that will be scaled, before it is scaled, for the code within that view.

 

No Comments

There are no comments related to this article.

Leave a Reply

Your email address will not be published. Required fields are marked *