Setneedslayout and Setneedsdisplay

setNeedsLayout and setNeedsDisplay

Actually the documentation is pretty clear about this:

  • setNeedsLayout will layout subviews

    Call this method on your application’s main thread when you want to adjust the layout of a view’s subviews.

  • setNeedsDisplay will call for a redraw of your view (drawRect:, etc).

    You can use this method or the setNeedsDisplayInRect: to notify the system that your view’s contents need to be redrawn.

Swift 4 - setNeedsDisplay and layoutIfNeeded not redrawing UILabel on self.UIView

Whenever GlobalVars.mainTextColor or GlobalVars.bgColor values change, you have to set the colors of all your labels again.

One option is to use Notifications. You can post a notification on didSet of your global variables and catch the notification in your MyLabel class and update the colors.

class GlobalVars {
static let onChangeColorNotification = "onChangeColorNotification"
static var mainTextColor:UIColor? {
didSet {
NotificationCenter.default.post(Notification.init(name: Notification.Name(rawValue: onChangeColorNotification)))
}
}
}

And in your MyLabel class

class MyLabel: UILabel {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
onChangeGlogabColor()
NotificationCenter.default.addObserver(self, selector: #selector(onChangeGlogabColor), name: NSNotification.Name(rawValue: GlobalVars.onChangeColorNotification), object: nil)
}
@objc func onChangeGlogabColor() {
textColor = GlobalVars.mainTextColor
}
}

You can do the same with all your custom classes like MyButton or MyView, catch the notification and update the colors.

And you don't have to call setNeedsDisplay() or layoutIfNeeded().

@IBAction func btnWhite(_ sender: UIButton) {
GlobalVars.mainTextColor = UIColor.black
GlobalVars.bgColor = UIColor.white
self.view.backgroundColor = UIColor.white
}

Why after -setNeedsLayout -layoutsSubviews method executes immediately

I've finally found an answer.
Thanks to warren-burton, to share a link of WWDC presentation, it really helps.

To answer to my question, we should firstly understand what really is the "Update cycle". It consist of three parts. Constraints part(to update all constraints), layout part(to set all frames) and drawing part(to draw a view on the screen). The main reason of misunderstanding in my question was that I don't know that

THE UPDATE CYCLE IS A LOOP

This loop is always running. That means potentially update cycle can run about 120 times per second. It was really bad performance if we would update constraints, layout and redraw 120 times every second. To avoid this problem there are a flags to update layout, update constraints and drawing. So, each update cycle the system checks in each part if it is necessary to call corresponding methods. And methods like updateConstraints(), layoutSubviews() and drawRect() calls only if the corresponding flag is on.

Can we affect these flags?

Yes, we can set the needed flag to true via setNeedsUpdateConstraints(), setNeedsLayout() and setNeedsDisplay() methods. So, if we call setNeedsLayout() method, the flag is set to true, and the layout will be updated in next update cycle.

Sample Image

Why after setNeedsLayout layoutsSubviews method executes immediately

It never executes immediately. It only seems to user that update cycle runs right after calling setNeedsLayout, because update cycle runs very fast and frequently.
The good example to understand is a changing constraint constant.

print(label.frame.height) // 100
labelHeightConstraint.constant = 200
print(label.frame.height) // 100

After we change the constraint, the system recalculates the frames and call the setNeedsLayout() to update frames in next update cycle. If you print the height of frame, it would be old, cause the update cycle doesn't occur yet. To see the calculated frame, you should force call the method immediately via layoutIfNeeded() method

print(label.frame.height) // 100
labelHeightConstraint.constant = 200
layoutIfNeeded()
print(label.frame.height) // 200

setNeedsLayout vs. setNeedsUpdateConstraints and layoutIfNeeded vs updateConstraintsIfNeeded

Your conclusions are right. The basic scheme is:

  • setNeedsUpdateConstraints makes sure a future call to updateConstraintsIfNeeded calls updateConstraints.
  • setNeedsLayout makes sure a future call to layoutIfNeeded calls layoutSubviews.

When layoutSubviews is called, it also calls updateConstraintsIfNeeded, so calling it manually is rarely needed in my experience. In fact, I have never called it except when debugging layouts.

Updating constraints using setNeedsUpdateConstraints is pretty rare too, objc.io–a must read about autolayouts–says:

If something changes later on that invalidates one of your constraints, you should remove the constraint immediately and call setNeedsUpdateConstraints. In fact, that’s the only case where you should have to trigger a constraint update pass.

In addition, in my experience, I have never had to invalidate constraints, and not set the setNeedsLayout in the next line of the code, because new constraints pretty much are asking for a new layout.

The rules of thumb are:

  • If you manipulated constraints directly, call setNeedsLayout.
  • If you changed some conditions (like offsets or smth) which would change constraints in your overridden updateConstraints method (a recommended way to change constraints, btw), call setNeedsUpdateConstraints, and most of the time, setNeedsLayout after that.
  • If you need any of the actions above to have immediate effect—e.g. when your need to learn new frame height after a layout pass—append it with a layoutIfNeeded.

Also, in your animation code, I believe setNeedsUpdateConstraints is unneeded, since constraints are updated before the animation manually, and the animation only re-lays-out the view based on differences between the old and new ones.

What is the relationship between UIView's setNeedsLayout, layoutIfNeeded and layoutSubviews?

I'm still trying to figure this out myself, so take this with some skepticism and forgive me if it contains errors.

setNeedsLayout is an easy one: it just sets a flag somewhere in the UIView that marks it as needing layout. That will force layoutSubviews to be called on the view before the next redraw happens. Note that in many cases you don't need to call this explicitly, because of the autoresizesSubviews property. If that's set (which it is by default) then any change to a view's frame will cause the view to lay out its subviews.

layoutSubviews is the method in which you do all the interesting stuff. It's the equivalent of drawRect for layout, if you will. A trivial example might be:

-(void)layoutSubviews {
// Child's frame is always equal to our bounds inset by 8px
self.subview1.frame = CGRectInset(self.bounds, 8.0, 8.0);
// It seems likely that this is incorrect:
// [self.subview1 layoutSubviews];
// ... and this is correct:
[self.subview1 setNeedsLayout];
// but I don't claim to know definitively.
}

AFAIK layoutIfNeeded isn't generally meant to be overridden in your subclass. It's a method that you're meant to call when you want a view to be laid out right now. Apple's implementation might look something like this:

-(void)layoutIfNeeded {
if (self._needsLayout) {
UIView *sv = self.superview;
if (sv._needsLayout) {
[sv layoutIfNeeded];
} else {
[self layoutSubviews];
}
}
}

You would call layoutIfNeeded on a view to force it (and its superviews as necessary) to be laid out immediately.

Is it a good practice to setNeedsLayout/setNeedsDisplay from CALayer's methods?

It's fine to call setNeedsLayout or setNeedsDisplay from the setters inside your UIView subclass. They return immediately and call layoutSubviews only once in the next drawing cycle.

self.view setNeedsDisplay and self.view setNeedsLayout are not working

You could try 2 things:

  1. calling updateDisplay on the main thread:

    [delegate performSelectorOnMainThread:@selector(updateDisplay) withObject:nil waitUntilDone:YES];

    in the end, it is accessing UIKit, so better doing it right;

  2. if that does not help, try with:

    [_area setNeedsDisplay];

    In this case also, if would be advisable to call the method on the main thread.



Related Topics



Leave a reply



Submit