Updating UI in iOS


Today I got so much confused about updating UI in my little iOS project when I saw some snippet from Stackoverflow.

search function

Two lines in side the closure UIView.animateWithDuration caught my eyes:

self.view.setNeedsDisplay()
self.view.layoutIfNeeded()

And I thought, “What are they doing there? Why are they ordered like that?”

setNeedsDisplay vs. displayIfNeeded vs. setNeedsLayout vs. layoutIfNeeded

When you update your drawings that uses custom draw(_ rect:), you are dealing with display. Run setNeedsDisplay() whenever you update your drawing and it will run asynchronously. It’s usually smart to put setNeedsDisplay() in property observers like didSet. If you want to run it synchronously then run displayIfNeeded() after you run setNeedsDisplay().

Similarly when you update your layers(frames, bounds and etc.) that uses custom layoutSubviews(), you use setNeedsLayout() and layoutIfNeeded(). However, normally you don’t need to worry about this because of the UIView instance property autoresizesSubviews. If autoresizesSubviews is set to true, the view will adjust the size of its subviews when its bounds change and it’s true by default.

// UIKit.UIView
extension UIView {    
    open var autoresizesSubviews: Bool // default is YES. if set, subviews are adjusted according to their autoresizingMask if self.bounds changes
}

So what were they doing there?

I think the guy who made the snippet didn’t need to run setNeedsDisplay() since there isn’t a custom draw(_ rect:). You don’t need layoutIfNeeded() either here because of two reasons: you don’t have a custom layoutSubviews() and even if you had one, you have to run setNeedsLayout() before you run layoutIfNeeded().

Am I really correct? I made my own demo here(the source code is below the gif):

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var button: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        /*
         Let's check how autoresizesSubviews affects layer drawing
         If layer drawing works fine, it means that non-custom
         layoutSubviews() are called automatically independent to
         autoresizesSubviews.
         */
        view.autoresizesSubviews = false
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    @IBAction func clicked(_ sender: UIButton) {
        if sender.bounds.width == 46 {
                sender.frame = sender.frame.updateSize(ratio: .double)
        } else {
            sender.frame = sender.frame.updateSize(ratio: .halve)
        }
    }
}

enum SizeUpdateRatio {
    case double
    case halve
}


extension CGRect {
    func updateSize(ratio: SizeUpdateRatio) -> CGRect {
        var width: CGFloat
        var height: CGFloat

        switch ratio {
        case .double: width = self.width * 2; height = self.height * 2
        case .halve: width = self.width * 1/2; height = self.height * 1/2
        }

        let x: CGFloat = (UIScreen.main.bounds.width - width ) / 2
        let y: CGFloat = (UIScreen.main.bounds.height - height ) / 2
        let origin = CGPoint(x: x, y: y)
        return CGRect(origin: origin, size: CGSize(width: width, height: height))
    }
}

References