When the autolayout constraints set frames during view controller life cycle? - ios

I have used autolayout constraints from storyboard. However in some cases, I want to calculate dynamic height of subview. I code this in viewDidAppear(), it works fine because this method is called after all view frames are set by layout constraints. The problem here is that I can see the frame set by constraints for half a second. And then the code reframes the view.
I came to know about viewDidLayout() which is called after constraints has set the frame so I can change. But it doesn't work. It is like this method is called before constraints are used.

The viewDidAppear method is called at the end of the view life cycle. So if you change the constraints here, it will always be visible.
If the change you want to do is a one time event, then you may do so in the method viewWillAppear. But this won't always help because the view drawing may not always be finished by this time. So a better option is to place the code in viewWillLayoutSubviews or viewDidLayoutSubviews.
NOTE: viewWillLayoutSubviews or viewDidLayoutSubviews will be called multiple times in a view's life cycle, such as orientation change or moving in and out of another view or any frame change for that matter. If the code change you want to put here is a one time event, then please make sure to use flags.
eg:-
- (void)viewWillLayoutSubviews {
if(firstTime) {
// put your constraint change code here.
}
}
Hope this helps! :)

As name suggests, viewDidLayoutSubviews is called when the view of your viewController has just finished its laying out and you can assume that at that dynamic height your views are in their correct places/frames according to your autolayout constraints.
Swift :
override func viewDidLayoutSubviews() {
// Set your constraint here
}
Objective C :
-(void)viewDidLayoutSubviews {
// Set your constraint here
}

I am not sure what you are actually trying to do in viewDidLayoutSubviews(). I always use to customise view by modifying layout constraint values overriding this method.
// Called to notify the view controller that its view has just laid out its subviews
override func viewDidLayoutSubviews() {
//Write your code here, any constraint modification etc.
super.viewDidLayoutSubviews()
}

Related

After constraint update, when does UIView's bounds change?

In my view controller's viewDidLoad, I'm adjusting a UIImageView's constraints to reflect its image's aspect ratio. After it's done, I'd like to know the image view's updated bounds. The bounds do update in the UI, but when viewWillLayoutSubviews and viewDidLayoutSubviews are called (each is called only once), the bounds are still the original bounds from before I changed the aspect ratio. When should I check the bounds and make my associated changes?
After you update the constraints in the viewDidLoad, update them using either one below
[yourImageViewOL setNeedsDisplay];
[yourImageViewOL layoutIfNeeded]; //Allows you to perform layout before the drawing cycle happens. -layoutIfNeeded forces layout early
Then you can look for the updated bounds in the - (void)layoutSubviews; method as this method gets called by layoutIfNeeded automatically.
As of iOS 6.0, when constraints-based layout is used the base implementation applies the constraints-based layout, otherwise it does nothing.
Try to override the method -updateViewConstraints
- (void)updateViewConstraints {
[super updateViewConstraints];
/// your code here
}

safeAreaInsets return wrong values in viewDidLoad, where's the best place to get it?

When I try to retrieve the safeAreaInsets in viewDidLoad in my view controller, it returns (0, 0, 0, 0) on the iPhone X, which I know is wrong. The documentation says that this will happen if the view is not part of the hierarchy or the view isn't visible. So my question is, where is the best place to retrieve the safeAreaInsets and then layout my subviews using these insets?
You need to use viewDidLayoutSubviews instead of viewDidLoad.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// your layout code here
}
Note: Unlike viewDidLoad which is only called once, viewDidLayoutSubviews is often called multiple times. Make sure to only set the frames inside viewDidLayoutSubviews, and don't perform operations that should only happen once, like adding subviews.

UIView frame not updating in viewDidAppear

I'm using Storyboard with autolayout. In my ViewController I have a basic structure: one UIScrollView and one contentView (UIView) that contains different labels). Then I have different elements that I add in the viewDidAppear method of the class inside my contentView , then in order to recalculate it's frame size I do this:
- (void)fixContentViewHeight
{
_contentView.frame = CGRectMake(_contentView.frame.origin.x, _contentView.frame.origin.y, _contentView.frame.size.width, operators.frame.origin.y + operators.frame.size.height);
//constraint for contentView height
_contentViewHeight.constant = operators.frame.origin.y + operators.frame.size.height;
}
- (void)fixScrollViewHeight
{
_scrollView.frame = _contentView.frame;
_scrollView.contentSize = _contentView.frame.size;
}
where operators is the LAST placed element of contentView, it's always on the bottom of the frame. I call these 2 methods inside the viewDidAppear and they make the view scrollable, the problem is that the frame of contentView doesn't get updated so the last elements are always unclickable (because they're placed outside the frame of the view).
What am I doing wrong? Why the scrollView becomes scrollable but the contentView keeps it's old frame?
If you have autolayout constraints affecting the height of _contentView You will not be able to change its height by setting the frame, as the constraints will override that.
You will need to (and should be) adding new / modifying constraints in your code when you are adding new elements. then calling
_contentView.layoutIfNeeded()
After all the updates to let the UI update if it needs to, which it should.
Please update UI On Main Queue
dispatch_async(dispatch_get_main_queue(), ^{
// update any UI on Main queue
});
I think that all your frame related information should be inside viewWillLayoutSubviews() and viewDidLayoutSubviews(). You can add stuff in viewDidLoad() but for any frame management I would use mentioned methods.
In your particular situation use former, viewDidLayoutSubviews(). However, you can resolve these kind of issues using constraints connected to the code as #Simon McLoughlin mentioned. 'scrollView becomes unscrollable' means that you should update contentSize as well so keep an eye on that as well.
Try to add vertical space constraint between scroll view and your last placed element.

Frame change of table view cell's subview won't take place until view did appear

I want to change a UITableViewCell's subview when it is being setup in the cellForRowAtIndexPath with the following code:
cellSubView.center = CGPointZero
Printing out the frame's coordinates shows, that the frame updates successfully, however the view is still displayed in the position that was given in the interface builder.
Overriding the viewDidAppear function with the following code would resolve this issue:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.tableView.reloadData()
}
But this will result in blinking: the table view already did appear when I change one of its cells' subview's position.
How is it possible to change the frame before the view did appear?
If you used autoLayout check if it's creating the constraints automatically.
If yes, then you can change the constants of these constraints instead.
If you are not using autoLayout, call layoutIfNeeded and see if that changes anything.
My bet though is on autoLayout creating constraints automatically.

Objective-C/Swift (iOS) When are the auto-constraints applied in the View/ViewController work flow?

I'm having some issues of figuring out when the auto-contraints setup on a XIB are applied in the view setup process.
For more explanation:
I've setup a XIB for a view
I set the "Simulated Metrics" Size to iPhone 3.5-Inch
I've added auto-constraints to the subviews inside this view
In the View Controller I perform certain operations dependent on the subview (IBOutlet) frames/bounds in the viewDidLoad method
In the View I perform certain operations dependent on the subview (IBOutlet) frames/bounds in the awakeFromNib method
In those 2 methods (ViewController::viewDidLoad and View::awakeFromNib) the IBOutlet views HAVE been loaded but the constraints have no yet been applied. The actual frame is still set to the iPhone 3.5" size (width 320) when using a larger simulator (such as the iPhone 6 simulator).
When are these auto-constraints applied and when should any necessary operations that would need the ACTUAL frame/bounds of the subviews take place?
I'm using XCode 6.3, Swift (1.2)
The constraints are applied in the layoutSubviews method. So if you want to do something after they are applied in a UIView subclass, override the method:
override func layoutSubviews() {
super.layoutSubviews()
//the frames have now their final values, after applying constraints
}
In a UIViewController subclass, use viewDidLayoutSubviews for the same purpose:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
//the frames have now their final values, after applying constraints
}
Please note that you shouldn't set frame / bounds for a view if you added auto layout constraints to this view.
Note for Xcode 8:
If you use auto layout, you may have a problem with new feature: view as [any device]. When you test your app on another device(simulator) and have inferred screen, screen bounds for this device gonna be applied in viewWillLayoutSubviews.
you can use method
override func layoutSubviews() {
super.layoutSubviews()
//the frames have now their final values, after applying constraints
}
But this method will be called multiple times in a view's life cycle, such as orientation change or moving in and out views, so you need to handle this.

Resources