116

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!

yuf
  • 3,082
  • 3
  • 20
  • 18
  • I would have thought that the view's bounds at any particular time held its current width and height. That's what gets adjusted during the layout process. – Phillip Mills Nov 19 '12 at 02:39
  • Hmm I never thought to check bounds. I will do that now. – yuf Nov 19 '12 at 03:29

5 Answers5

255

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.

algal
  • 27,584
  • 13
  • 78
  • 80
  • 16
    An *excellent* answer and well explained, too. Saved me after an hour or two of pulling my hair out. – Ben Kreeger Jul 11 '13 at 15:32
  • 3
    `layoutIfNeeded` is great, and will make the frame immediately available. However, it will also force rendering of the constraints on all of the views in the sub-trees of the view upon which it is called. If you are adding views programmatically, for example, and call `layoutIfNeeded` on all of them in your recursive routine, you may find that your view hierarchy renders very slowly. (I learned this the hard way) As mentioned in the excellent answer, 'setNeedsLayout` is more efficient and will make the frame available on the next layout pass, which ideally happens in about 1/60th of a second. – shmim Sep 18 '14 at 18:45
  • 1
    I know this is old, but where do you call this method? In `initWithCoder`? – MayNotBe Oct 18 '14 at 05:02
  • @MayNotBE In my experience, whenever it would be safe to directly set the frames within a view's subviews, it's also safe to indirectly set the frames by forcing early auto layout with `layoutIfNeeded`. The AL system can be opaque, but it is essentially just doing math in `layoutSubviews` and using the results to make a lot of `setFrame:` calls. So you can use this technique at any point after constraints are installed. (If you're loading from a storyboard or xib, that's in `awakeFromNib` not initWithCoder.) But I'd try to use it as late as possible, to be safe. – algal Oct 19 '14 at 05:43
  • Well, I think I love you. I had no idea what the purpose for setNeedsLayout or setNeedsDisplay was. I was struggling with getting the new layout update and was having to fish for the height of my object and hardcode a width and height. Thank you, thank you, thank you. – Stephen Paul Mar 11 '15 at 02:10
  • 4
    Why force the layout just to get the bounds? This seems inefficient and with a complex interface this is potentially going to be expensive. Instead access the latest bounds from the viewDidLayoutSubviews or even viewWillLayoutSubviews and let cocoa handle its own layout timing. Set a property if you need to access the value or flag to avoid multiple calls if that's a problem. – SmileBot Mar 14 '15 at 15:53
  • 1
    Yes, you only need to force layout if you need access to the auto layout-computed value before the turn of the run loop – for example, within the extent of the current function call. – algal Mar 14 '15 at 17:21
  • This answer kind insists on the user (us) of this rendering engine (UIKit) should take responsibility of the rendering itself. It firmly believe that's not right. Using something like viewDidLayoutSubviews should be best in most cases and similar alerts to whenever something changed on screen should be used instead. – Jonny Dec 10 '15 at 02:19
  • @Jonny Good point — if you don't need the size value immediately, you're better off waiting for normal layout to occur and then accessing the size from within `viewDidLayoutSubviews`. But this question was explicitly asking about getting the size implied by a view's _current_ constraints. Waiting until `viewDidLayoutSubviews` fires means you are getting the size implied not by the current constraints but by the constraints after layout completes, and it means you won't get the answer until that happens. That's often good enough but it's not quite what was asked. – algal Dec 10 '15 at 16:55
  • "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" why should it be weak? – Warpzit Jun 13 '16 at 11:40
  • Because adding the constraint to the view causes the view to retain it (or maybe it to retain the view), so by using a weak property you ensure that your view controller does not incorrectly hold onto the constraint if the view has been removed – algal Jun 14 '16 at 21:06
  • In my case that trying to update my custom view's whole size following different device sizes, the one line worked perfectly! thx – KoreanXcodeWorker Jun 16 '16 at 09:58
  • _"to update the layout immediately ... then you need to call `[view setNeedsLayout]` and `[view layoutIfNeeded]`"_ --- I'm finding that only `[view layoutIfNeeded]` is necessary. – invisible squirrel Jun 23 '16 at 16:23
  • 1
    @dene `layoutIfNeeded` will suffice if you've already done some action that implicitly calls `setNeedsLayout`. But doing them both is good practice since it will work if you want to force layout unconditionally. – algal Jun 23 '16 at 20:05
  • @smileBot, It doesn't work if you need to access the size of a child of the view, deep in the view hierarchy. Requesting layout ensures that the entire hierarchy is updated. – Iulian Onofrei Oct 04 '18 at 10:59
8

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.

Syed Ali Salman
  • 2,894
  • 4
  • 33
  • 48
Brandon Read
  • 81
  • 2
  • 1
  • 3
    viewDidLayoutSubviews being called a few times, you are creating tons of layers... You need to add some existence checks before adding *another* layer. – Kalzem Jan 12 '16 at 23:23
  • Comment some years late... you should also call this method on the superclass when overriding before anything else: `[super viewDidLayoutSubviews];` – Alejandro Iván Nov 21 '16 at 03:43
5

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
}
Mayank Pahuja
  • 115
  • 1
  • 3
  • 12
  • this is the only way I could get the real final size of my view – Benoit Jadinon Aug 24 '16 at 15:29
  • This was the only way I was able to get the final size for any of my constrained views because before I was trying to get them on awakeFromNib which didn't give the subviews time to actually resize first. Thank you! – Azin Mehrnoosh Apr 27 '20 at 17:15
3

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).

2

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?

borrrden
  • 33,256
  • 8
  • 74
  • 109
  • 1
    I am seeing otherwise. The frame values don't change, even after changing the width/height constraints directly (by changing their constants. I have an IBOutlet to them in the xib) – yuf Nov 19 '12 at 03:29
  • My problem is unique because I change the constraint, then need to use the frame in the same function. Calling setNeedsLayout doesn't update it immediately. I guess I need to wait for the layout first then use the frame. My second question is, how can I change a height/width constraint if I don't have a reference to it directly? – yuf Nov 19 '12 at 03:41
  • 1
    You should dispatch_async then, and it will allow you to delay your code until the next frame. As for the second part...I don't know...you would have to iterate all the constraints and look for which one is applicable...IBOutlets are much easier. – borrrden Nov 19 '12 at 04:19
  • True for IBOutlets, but I want to write a function in a category for an UIView that I won't have IBs to work with. – yuf Nov 19 '12 at 04:48