How can I get a view's current width and height when using autolayout constraints?

IosAutolayout

Ios Problem Overview


I'm not talking about the frame property, because from that you can only get the view's size in the xib. I'm talking about when the view is resized because of its constraints (maybe after a rotation, or in response to an event). Is there a way to get its current width and height?

I tried iterating through its constraints looking for width and height constraints, but that's not very clean and fails when there are intrinsic constraints (since I can't differentiate between the two). Also, that only works if they actually have width and height constraints, which they don't if they rely on other constraints to resize.

Why is this so difficult for me. ARG!

Ios Solutions


Solution 1 - Ios

The answer is [view layoutIfNeeded].

Here's why:

You still get the view's current width and height by inspecting view.bounds.size.width and view.bounds.size.height (or the frame, which is equivalent unless you're playing with the view.transform).

If what you want is the width and height implied by your existing constraints, the answer is not to inspect the constraints manually, since that would require you to re-implement the entire constraint-solving logic of the auto layout system in order to interpret those constraints. Instead, what you should do is just ask auto layout to update that layout, so that it solves the constraints and updates the value of view.bounds with the correct solution, and then you inspect the view.bounds.

How do you ask auto layout to update the layout? Call [view setNeedsLayout] if you want auto layout to update the layout on the next turn of the run loop.

However, if you want it to update the layout immediately, so you can immediately access the new bounds value later within your current function, or at another point before the turn of the run loop, then you need to call [view setNeedsLayout] and [view layoutIfNeeded].

You asked a second question: "how can I change a height/width constraint if I don't have a reference to it directly?".

If you create the constraint in IB, the best solution is to create an IBOutlet in your view controller or your view so you do have a direct reference to it. If you created the constraint in code, then you should hold onto a reference in an internal weak property at the time when you created it. If someone else created the constraint, then you need to find it by examining the examining the view.constraints property on the view, and possibly the entire view hierarchy, and implementing logic which finds the crucial NSLayoutConstraint. This is probably the wrong way to go, since it also effectively requires you to determine which particular constraint determined the bounds size, when there's not guaranteed to be a simple answer to that question. The final bounds value could be the solution to a very complicated system of multiple constraints, with multiple priorities, etc., so that no single constraint is the "cause" of the final value.

Solution 2 - Ios

I had a similar issue where I needed to add a top and bottom border to a UITableView that resizes based on its constraints setup in the UIStoryboard. I was able to access the updated constraints with - (void)viewDidLayoutSubviews. This is useful so that you do not need to subclass a view and override its layout method.

/*** SET TOP AND BOTTOM BORDERS ON TABLE VIEW ***/
- (void)addBorders
{
    CALayer *topBorder           = [CALayer layer];
    topBorder.frame              = CGRectMake(0.0f, self.tableView.frame.origin.y, 320.0f, 0.5f);
    topBorder.backgroundColor    = [UIColor redColor].CGColor;
    
    CALayer *bottomBorder        = [CALayer layer];
    bottomBorder.frame           = CGRectMake(0.0f, (self.tableView.frame.origin.y + self.tableView.frame.size.height), 320.0f, 0.5f);
    bottomBorder.backgroundColor = [UIColor redColor].CGColor;
    
    [self.view.layer addSublayer:topBorder];
    [self.view.layer addSublayer:bottomBorder];
}

/*** GET AUTORESIZED FRAME DIMENSIONS ***/
- (void)viewDidLayoutSubviews{
    [self addBorders];
}

Without calling the method from the viewDidLayoutSubview method, only the top border is drawn correctly, as the bottom border is somewhere offscreen.

Solution 3 - Ios

For those who may still be facing such issues, especially with TableviewCell.

Just override the method:

-(void)layoutSubviews
{
//your code here like drawing a shadow
}

In case of UITableViewCell or UICollectionViewCell create a subclass of the cell and override the same method:

-(void)layoutSubviews
{
//your code here like drawing a shadow
}

Solution 4 - Ios

Use -(void)viewWillAppear:(BOOL)animated and call [self.view layoutIfNeeded]; - It works I have tried.

because if you use -(void)viewDidLayoutSubviews it will work definitely but this method is called every time your UI demands updations/changes. Which will get hard to manage. Soft key is you use a bool variable to avoid such loop of calls. better use viewWillAppear. Remember viewWillAppear will also be called if view is loaded back again (without reallocating).

Solution 5 - Ios

The frame is still valid. In the end, the view uses its frame property to lay itself out. It calculates that frame based on all the constraints. The constraints are only used for the initial layout (and any time layoutSubviews is called on a view like after a rotation). After that, the position info is in the frame property. Or are you seeing otherwise?

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionyufView Question on Stackoverflow
Solution 1 - IosalgalView Answer on Stackoverflow
Solution 2 - IosBrandon ReadView Answer on Stackoverflow
Solution 3 - IosMayank PahujaView Answer on Stackoverflow
Solution 4 - Iosuser3693546View Answer on Stackoverflow
Solution 5 - IosborrrdenView Answer on Stackoverflow