Cesare Cesare - 1 year ago 121
iOS Question

layoutSubviews getting called repeatedly when the app enters background state

I want labels to have a font size that is proportional to the size of the screen. I've subclassed the

class to accomplish this:

@IBDesignable class MyCustomLabel: UILabel {
override func layoutSubviews() {
self.font = UIFont(name: "myFontName", size: (self.font?.pointSize)!)
self.adjustsFontSizeToFitWidth = true

The labels (which have a proportional width to superview constraint attached to them) resize correctly when the app is first launched, but when it enters background state
gets repeatedly called. The app no longer responses to users' input and keeps assigning a font size to the font.

Download test project. Why is this happening?

Answer Source

In your test project, if I run on an iPhone, I'm not seeing layoutSubviews() being called in the background. It only happens on iPad.

That's because your app supports Multitasking:

  • When your app is deactivated, iOS resizes it into different sizes in order to take snapshots of it for the app switcher. Those resizes cause your view's layoutSubviews() to be called. That's completely normal.
  • iOS then returns your app to the original size.

The real problem is that you are creating a "layout loop". Your code in layoutSubviews() is causing your own view's layout to be invalidated, so the system needs to run the layout process again. Then layout runs, you do it again, and it happens all over again.

Specifically, the cause is:

self.font = UIFont(name: fontName, size: fontSize)

Changing your label's font causes its intrinsicSize to change, which means that its superviews may need their layout to be updated, so the layout process needs to run again. It's a bad idea to do this in layoutSubviews() because it causes layout loops. You should really only change properties of your subviews, not your view itself.

Why do you think you need to do this in layoutSubviews()? There is probably a better place to put it, outside of the layout process. In your example, I don't see how this code does anything useful at all.

It would make more sense to set adjustsFrameSizeToWidth once, and then don't do anything in layoutSubviews():

override init(frame: CGRect) {
    super.init(frame: frame)
    self.adjustsFontSizeToFitWidth = true

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    self.adjustsFontSizeToFitWidth = true