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.
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 toupdateConstraintsIfNeeded
callsupdateConstraints
.setNeedsLayout
makes sure a future call tolayoutIfNeeded
callslayoutSubviews
.
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), callsetNeedsUpdateConstraints
, 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:
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;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
Swift Uicolor Initializer - Compiler Error Only When Targeting Iphone5S
iPhone - Convert Ctfont to Uifont
How to Prevent from Scrolling Uitableview Up When Nsfetchedresultscontroller Add New Record
How to Prevent iOS 13 Dark Mode from Breaking Emails
Downloading Uiimage via Alamofireimage
Swift - Image Data from Ciimage Qr Code/How to Render Cifilter Output
How to Save an Array to .Plist in the App's Mainbundle in Swift
Why Does CSS Background-Size: Cover Not Work in Portrait Mode on iOS
Sort Array of Dictionaries by Key Value
Integration New Facebook Sdk by Swift
Z-Index Issue on iOS Safari & Chrome
Coca Pod Chart Not Appearing (Swift4)
What Is Lldb_Expr in Swift Playground
Swiftui Holding Reference to Deleted Core Data Object Causing Crash