contentMode property of UIView allows you to control how to layout a view when the view’s bounds change. The system will not, by default, redraw a view each time the bounds change. That would be wasteful. Instead, depending on the content mode, it can scale, stretch or pin the contents to a fixed position.
If you have trouble remembering what the different content modes do and which you should use this post should help. Not sure how
.scaleAspectFit differs from
.scaleAspectFill? When might you want to use
clipToBounds or switch to
.redraw mode? Hopefully this post will tell you all you need to know.
Scaling, Redrawing and Positioning
There are thirteen different content modes but it is easiest to think of three main groups based on the effect:
- Scaling the View (with or without maintaining the aspect ratio)
- Redrawing the View
- Positioning the View
Scaling the View
There are three modes that have the effect of scaling or stretching the view contents to fill the available space when the bounds changes:
(I will list the Swift enum value first followed by the Objective-C value)
- .scaleToFill (UIViewContentModeScaleToFill)
- .scaleAspectFit (UIViewContentModeScaleAspectFit)
- .scaleAspectFill (UIViewContentModeScaleAspectFill)
The main difference between these modes is whether and how we maintain the aspect ratio of the contents when scaling. Take the example where we have a UIImageView of a star that we add as a subview of a container view shown in green below:
The star image view is 100x100 and the larger container view is 200x350. To experiment with the contentMode we can add constraints to make the star image view fill the container view. The difference content modes then control how we make the image view fill the available space.
The default if you do not set a content mode is
.scaleToFill shown below left. .scaleToFill stretches the view to fill the available space without maintaining the aspect ratio.
starImageView.contentMode = .scaleToFill
Compare this mode with
.scaleAspectFit shown below right. .scaleAspectFit scales the content to fit the view but maintains the aspect ratio. Any part of the view bounds that is not filled with content is transparent which allows us to see the green super view in this example.
starImageView.contentMode = .scaleAspectFit
The final scaling mode is
.scaleAspectFill shown below. .scaleAspectFill scales the content to totally fill the view maintaining the aspect ratio. This can result in the content being larger than the bounds of the view.
starImageView.contentMode = .scaleAspectFill
You can see this below left where the square image view is scaled to 350x350 to fill the 200x350 bounds. If you do not want to see the contents that are outside the bounds you need to remember to set clipsToBounds to true on the containing superview. You can see that below right:
greenView.clipsToBounds = true
Positioning the View
If you do not want to scale or stretch the view with the bounds you can pin it to one of nine possible positions.
- .center (UIViewContentModeCenter)
- .top (UIViewContentModeTop)
- .bottom (UIViewContentModeBottom)
- .left (UIViewContentModeLeft)
- .right (UIViewContentModeRight)
- .topLeft (UIViewContentModeTopLeft)
- .topRight (UIViewContentModeTopRight)
- .bottomLeft (UIViewContentModeBottomLeft)
- .bottomRight (UIViewContentModeBottomRight)
Note that the contents of the view are not scaled or stretched in any way.
Redrawing the View
Finally, I started by saying that the system does not by default redraw a view when the bounds change. But what if that is what we want?
- .redraw (UIViewContentModeRedraw)
Perhaps we have a custom view that implements draw(_ rect:) and we need it called any time the bounds change? The .redraw content mode triggers the setNeedsDisplay method when the view bounds change.
starImageView.contentMode = .redraw
Getting the Playground
You can get this post as an Xcode playground from my GitHub repository.