I need to update a constraint programmatically in my project to adjust a view's height accordingly (based on different subviews). I've made outlet of the constraint in my controller but facing an issue.
when I try to update this for the first time (in viewDidLoad or viewWillAppear method), it's not updated. and if i update it afterwards (because of some rotation etc), then it is done correctly. Kindly tell me why this is happening and what is the right way/place to do this? (As i feel that the constraint is updated somewhere again after my updation in viewWillAppear/DidLoad).
I tried view.layoutIfNeeded as well but it didn't help. I guess it has something to do with viewDidLoad and other viewController delegate methods
P.S. I'm also using size classes in my project but I think it has nothing to do with that as it's working in some cases.
Updating constraints may not work in viewWillAppear.
It will, however, work in viewDidAppear.
There are other places you may overwrite, such as: (using static BOOL shouldRefreshView = NO; for the first time)
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
if(shouldRefreshView) {
shouldRefreshView = NO;
[self.view layoutIfNeeded];
}
}
Related
I need a little help to understand the viewDidLAyoutSubviewsmethod and how to safely use it.
I need to edit some of my subviews programmatically before the viewDidAppear method. And in order for it to work properly I of course need to wait until the targeted subviews are layed out before I edit them.
Now I thought this was what the viewDidLayoutSubviewsmethod was for, but when I tested it I found out that it was actually called two times before my viewDidAppear method. I tried to edit my subviews only the first time viewDidLayoutSubviews was called, because I just want to run [self editMySubviews] once, but then the targeted subview wasn't ready and it got messed up. This is how I tried:
- (void)viewDidLayoutSubviews {
if (!myBoolean) {
[self editMySubviews];
myBoolean = YES;
}
}
Of course if I remove the if-statement it fixes itself next time the method is called, but I only want [self editMySubviews]to be called once.
So my question is, when I can call the [self editMySubviews] method before the viewDidAppear method, and be 100% sure that all subviews are ready to be edited? Now in my case the viewDidLayoutSubviews gets called two times before viewDidAppear, but will that be the case every time? Is it safe to just call [self editMySubviews] after the second time viewDidLayputSubviews is called?
According to your comment you said
Editing some button constraints according to its superView.frame.size.width which is different on different devices
After viewdidload you will get the proper size from view.bounds
so you can easily set/update constraint and add at the end of this
setNeedsUpdateConstraints and layoutIfNeeded method call
I have many places in my code where I need to do stuff based upon the layout of the views in a UIViewController. For example, I have a view in a nib that I then capture in code and use to create a mask for another layer, but I need to wait until the mask view and its subviews have the correct size before I do so.
I have discovered that, although hacky, I can achieve this by creating a counter which increments each time viewDidLayoutSubviews is called, and triggers the code I wish to execute when it has been called for the second time - for some reason, most of the views have yet to properly lay themselves out until viewDidLayoutSubviews is called for the second time.
Like I say, this seems pretty hacky and there's every chance that an iOS update could break my code. Does anyone have a better way of doing this?
- (void)viewDidLoad
{
[super viewDidLoad];
self.layoutCount = 0;
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.layoutCount ++;
if (self.layoutCount == 2)
{
// perform final layout code, e.g. create masks
}
}
The safest approach might be to "refresh" the masks every time viewDidLayoutSubviews is called. this way you don't have to worry about whether the views have laid out or not.
In my xib file, I have a constraint for height for the label in my xib file.
And in my objective c, I tried to change it using
self.heightConstraint.constant = newHeight;
But nothing is changed when I run it in simulator. I have used debugger and make sure that line is executed. And in Spark tool, I see the label height is the old height.
How can I adjust the height dynamically?
Updated:
I have changed my code to add a nil check.
if (self.heightConstraint != nil) {
self.heightConstraint.constant = newHeight;
}
My code still get executed, but nothing get changed.
There's nothing wrong with your code itself:
self.heightConstraint.constant = newHeight;
But the reason this changes nothing in your interface, even though it is being executed, is that self.heightConstraint is not a reference to a constraint in your interface. (It is probably nil.)
I agree with Matt. The likely reason your code isn't working is that you have a broken outlet link. In general, when code that tries to do something with an outlet or action doesn't work, the most likely cause is a broken outlet or action connection.
Rule of thumb: When debugging stereo or computer hardware, check the cables.
Corollary for for iOS or Mac OS apps in Xcode: When debugging UI code, check your IBOutlet and IBAction connections.
At what part of the view controller lifecycle are you changing the constraints? You may need to call setNeedsLayout on the parent view.
I see different examples where constraints are set. Some set them in viewDidLoad / loadView (after the subview was added). Others set them in the method updateViewConstraints, which gets called by viewDidAppear.
When I try setting constraints in updateViewContraints there can be a jumpiness to the layout, e.g. slight delay before the view appears. Also, if I use this method, should I clear out existing constraints first i.e. [self.view [removeConstraints:self.view.constraints]?
I set up my constraints in viewDidLoad/loadView (I'm targeting iOS >= 6). updateViewConstraints is useful for changing values of constraints, e.g. if some constraint is dependent on the orientation of the screen (I know, it's a bad practice) you can change its constant in this method.
Adding constraints in viewDidLoad is showed during the session "Introduction to Auto Layout for iOS and OS X" (WWDC 2012), starting from 39:22. I think it's one of those things that are said during lectures but don't land in the documentation.
UPDATE: I've noticed the mention of setting up constraints in Resource Management in View Controllers:
If you prefer to create views programmatically, instead of using a
storyboard, you do so by overriding your view controller’s loadView
method. Your implementation of this method should do the following:
(...)
3.If you are using auto layout, assign sufficient constraints to each of
the views you just created to control the position and size of your
views. Otherwise, implement the viewWillLayoutSubviews and
viewDidLayoutSubviews methods to adjust the frames of the subviews in
the view hierarchy. See “Resizing the View Controller’s Views.”
UPDATE 2: During WWDC 2015 Apple gave a new explanation of updateConstraints and updateViewConstraints recommended usage:
Really, all this is is a way for views to have a chance to make changes to constraints just in time for the next layout pass, but it's often not actually needed.
All of your initial constraint setup should ideally happen inside Interface Builder.
Or if you really find that you need to allocate your constraints programmatically, some place like viewDidLoad is much better.
Update constraints is really just for work that needs to be repeated periodically.
Also, it's pretty straightforward to just change constraints when you find the need to do that; whereas, if you take that logic apart from the other code that's related to it and you move it into a separate method that gets executed at a later time, your code becomes a lot harder to follow, so it will be harder for you to maintain, it will be a lot harder for other people to understand.
So when would you need to use update constraints?
Well, it boils down to performance.
If you find that just changing your constraints in place is too slow, then update constraints might be able to help you out.
It turns out that changing a constraint inside update constraints is actually faster than changing a constraint at other times.
The reason for that is because the engine is able to treat all the constraint changes that happen in this pass as a batch.
I recommend creating a BOOL and setting them in the -updateConstraints of UIView (or -updateViewConstraints, for UIViewController).
-[UIView updateConstraints]: (apple docs)
Custom views that set up constraints themselves should do so by overriding this method.
Both -updateConstraints and -updateViewConstraints may be called multiple times during a view's lifetime. (Calling setNeedsUpdateConstraints on a view will trigger this to happen, for example.) As a result, you need to make sure to prevent creating and activating duplicate constraints -- either using a BOOL to only perform certain constraint setup only once, or by making sure to deactivate/remove existing constraints before creating & activating new ones.
For example:
- (void)updateConstraints { // for view controllers, use -updateViewConstraints
if (!_hasLoadedConstraints) {
_hasLoadedConstraints = YES;
// create your constraints
}
[super updateConstraints];
}
Cheers to #fresidue in the comments for pointing out that Apple's docs recommend calling super as the last step. If you call super before making changes to some constraints, you may hit a runtime exception (crash).
This should be done in ViewDidLoad, as per WWDC video from Apple and the documentation.
No idea why people recommend updateConstraints. If you do in updateConstraints you will hit issues with NSAutoresizingMaskLayoutConstraint with auto resizing because your views have already taken into account the auto masks. You would need to remove them in updateConstraints to make work.
UpdateConstraints should be for just that, when you need to 'update' them, make changes etc from your initial setup.
Do it in view did layout subviews method
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
I have this solution to change constraints before those who are in the storyboard are loaded.
This solution removes any lags after the view is loaded.
-(void)updateViewConstraints{
dispatch_async(dispatch_get_main_queue(), ^{
//Modify here your Constraint -> Activate the new constraint and deactivate the old one
self.yourContraintA.active = true;
self.yourContraintB.active= false;
//ecc..
});
[super updateViewConstraints]; // This must be the last thing that you do here -> if! ->Crash!
}
You can set them in viewWillLayoutSubviews: too:
override func viewWillLayoutSubviews() {
if(!wasViewLoaded){
wasViewLoaded = true
//update constraint
//also maybe add a subview
}
}
This worked for me:
Swift 4.2
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Modify your constraints in here
...
}
Although honestly I am not sure if it is worth it. It seems a bit slower to load than in viewDidLoad(). I just wanted to move them out of the latter, because it's getting massive.
Add your constraints in viewWillLayoutSubviews() to add constraints programmatically
See Apple Documentation in Custom Layout Section
If possible, use constraints to define all of your layouts. The
resulting layouts are more robust and easier to debug. You should only
override the viewWillLayoutSubviews or layoutSubviews methods when you
need to create a layout that cannot be expressed with constraints
alone.
Following example is to pass any view to another class. create my view from storyboard
Swift 5.0
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.main.async {
self.abcInstance = ABC(frame: self.myView.frame)
}
}
If you miss DispatchQueue.main.async, it will take time to update constraints in viewWillAppear. Create myView in storyboard and give constraints same as screen width & height, then try printing frame of myView. It will give accurate value in DispatchQueue.main.async or in viewDidAppear but not give accurate value in viewWillAppear without DispatchQueue.main.async.
What are your best practising when you subclass UIView?
I'm trying to do a view for a viewcontroller entirely in code, using the new ios 6 autolayout.
So I've subclassed UIView and setup the basic constraints in #initWithFrame (should I do this in #updateConstraints?). The problem is that I have an UILabel with dynamic text and variable width so I have to setup #preferredMaxLayoutWidth after the constraints have determined a frame, so I think #layoutSubviews is the best place to do that, but doing that results in a NSInternalInconsistencyException: Auto Layout still required after executing -layoutSubviews. MyView's implementation of -layoutSubviews needs to call super. (RuntimeError)
Before you asks I'm sure I have called super in every method I overridden.
Any idea?
Perhaps what you need here is to not have your view call [super layoutSubviews], but UIView, the ancestor, call its [super layoutSubviews].
This can be achieved not by subclassing, but implementing a category of UIView, which would have layoutSubViews function implemented, calling super. If I could have your code I could have tried. Someone has tried it successfully here.
Again, this does not guarantee that this will be fixed. There seems to be many bug reports concerning this in Apple forums. Unfortunately, we can't browse others' bugs in Apple portal, we can just file our own.
As a workaround, I would also suggest if any of your constraints conflict with your
preferredMaxLayoutWidth argument: try increasing or decreasing it. As per the overriding order of its own, it can change things, and who (other than Apple) knows - it maybe fixed. Fingers crossed!
This seemed to work for me, I can't explain why, but it did.
-(void)layoutSubviews
{
[super layoutSubviews];
layout code here, including updated constraints
[super layoutSubviews];
}