I have a ViewController with a UIScrollView on it. On this VC I programatically adjust the frame of a UILabel which is inside the scroll view. This is done on viewDidLoad. This UILabel comes from the VC's xib file, it is not created programatically, only its frame changed.
When I transition from this VC to another, then back, the UILabel's frame gets reset to the XIB's state. It's text however is not reset, it stays the same text I set before.
My investigation tells me that this occur on layoutSubviews, as the UILabel's properties are correct on willLayoutSubviews, then reset on didLayoutSubviews when moving back to the VC.
Is this expected behaviour? Is there a reason why the label's text remains but the frame gets reset? Is this because the UIScrollView calls layoutSubviews on its parent view whenever scrolling?
Thanks
I see two problems in your description. First, you said this:
On this VC I programatically adjust the frame of a UILabel which is inside the scroll view. This is done on viewDidLoad.
It is generally a bad idea to modify view frames in viewDidLoad, because the system's layout phase (during which layoutSubviews messages are sent) hasn't happened yet. In viewDidLoad, your view's frame hasn't been adjusted for the current device's screen size and interface orientation yet.
Second, you said that you're using autolayout. The autolayout system sets the frames of views during the layout phase. The layout phase can be triggered by many different events, including (as you've discovered) the appearance and disappearance of views.
In order to make your adjustment to the label's frame “stick”, you need to modify the constraints that control the label's frame. One way to do this is to create an outlet of type NSLayoutConstraint on your view controller for each constraint that you need to modify, and connect these outlets to the constraints in your xib. Then in your view controller's viewWillLayoutSubviews, you can modify the constant property of each constraint as necessary. (Ironically, constant is the only modifiable property of an NSLayoutConstraint.)
Related
I was reading the description of viewDidLayoutSubviews of UIViewController:
Called to notify the view controller that its view has just laid out its subviews [...] However, this method being called does not indicate that the individual layouts of the view's subviews have been adjusted. Each subview is responsible for adjusting its own layout [...].
For me, it means: "Called when the layout of subviews is complete, but actually this is not true". So what's really behind viewDidLayoutSubviews?
When bounds change for a ViewControllers View, this method is called after the positions and sizes of the subviews have changed.
So this is our chance to make changes to view after it has laid out its subviews, but before it is visible on screen.
Any changes that depending on bounds has to be done, we can do here and not in ViewDidLoad or ViewWillAppear.
While ViewDidLoad & ViewWillAppear, the frame and bounds of a view are
not finalised. So when AutoLayout has done it's job of fixing mainView and
it's subviews, this method is called.
When using autolayout, framework does not call layoutSubviews every time. This is called in these cases.
Rotating a device: only calls layoutSubview on the parent view (the responding viewControllers primary view)
Its own bounds (not frame) changed. (The bounds are considered changed only if the new value is different, including a different origin.)
A subview is added to the view or removed from the view.
Your application forces layout to occur by calling the setNeedsLayout or layoutIfNeeded method of a view.
Scrolling a UIScrollView causes layoutSubviews to be called on the scrollView, and its superview.
Note:
The call for viewDidLayoutSubviews also depends on various factors like autoresize mask, using Auto-Layout or not, and whether view is in view hierarchy or not.
For any other clarification, check When is layoutSubviews called?
viewDidLayoutSubviews will be called when
When the bounds change for a view controller's view, the view
adjusts the positions of its subviews and then the system calls this
method.
For example you have set constraints of your view then you want to update the frame for your subview in viewDidLoad(), which will not make any impact as in viewDidLoad() your constraints are not properly set, they will get properly set when viewDidLayoutSubviews get called, now you want to update the frames of your subview, then you can do that in this method as this method get called only after all the constraints of your view are properly set.
So I want to lay out a UITableView and I want the cells to calculate their own height. I can do this with systemSizeFittingSize, but this doesn't always return the correct height, as the rows still have their frame from the storyboard (or xib).
I want to update the frames so that they are what they will be on the device.
How can I do this? I have tried setNeedsLayout and layoutIfNeeded, but I would have to do this on the highest view in the hierarchy to achieve the desired effect, as each view seems to use the frame of their superview.
I am setting the tableViewDataSource and calling reloadData in viewDidLoad in my viewcontroller.
Is there a way to set the correct frames before viewDidLoad gets called?
Edit: I want to give the height of a subview to a layout object in my viewDidLoad, so that it can work its magic providing a nice layout. Now this object only does the layout calculations. I notice that the height I request (through view.frame.size.height) in viewDidLoad is the same as in my storyboard, again telling me that in viewDidLoad the frames haven't been updated yet... Anyone with a workaround?
I think tableView:heightForRowAtIndexPath: may be something that you need:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableViewDelegate_Protocol/#//apple_ref/occ/intfm/UITableViewDelegate/tableView:heightForRowAtIndexPath:
But setting the correct frames before viewDidLoad is possible only if you know the size before and then you can just calculate it in init.
I have a UITableView with several columns of data. The reusable cells are loaded from an xib file which has the appropriate labels and autolayout constraints. Everything works perfectly; the table columns are laid out correctly on different devices and when the devices are rotated.
The problem I am having is trying to create a table footer to show the totals of the columns in the table.
I created an xib file with the same autolayout constraints as the cell xib file and am loading it in tableView.viewForFooterInSection the same way I did for the cells. As required, I am using a subclass of UITableViewHeaderFooterView instead of UITableViewCell.
The awakeFromNib method in the UITableViewHeaderFooterView subclass sets the background color, so I can see that it is the correct size on all devices/orientation, but the labels from the footer xib file are not getting laid out to match the table cells.
The autolayout constraints from the footer xib file are not being honored. When I set a different background color in the footer xib file, the table footer shows this background color for the length of the xib's view.
I'm new to all of this technology and would greatly appreciate help in resolving this incredibly frustrating issue.
Is there a way to use autolayout for UITableViewHeaderFooterViews loaded from nibs?
You should call setNeedsUpdateConstraints() to update your view.
From apple documentation
Controls whether the view’s constraints need updating.
When a property of your custom view changes in a way that would impact constraints, you can call this method to indicate that the
constraints need to be updated at some point in the future. The system
will then call updateConstraints as part of its normal layout pass.
Updating constraints all at once just before they are needed ensures
that you don’t needlessly recalculate constraints when multiple
changes are made to your view in between layout passes.
Also, you can update view throw layoutSubviews()
Lays out subviews. The default implementation of this method does
nothing on iOS 5.1 and earlier. Otherwise, the default implementation
uses any constraints you have set to determine the size and position
of any subviews. Subclasses can override this method as needed to
perform more precise layout of their subviews. You should override
this method only if the autoresizing and constraint-based behaviors of
the subviews do not offer the behavior you want. You can use your
implementation to set the frame rectangles of your subviews directly.
You should not call this method directly. If you want to force a
layout update, call the setNeedsLayout method instead to do so prior
to the next drawing update. If you want to update the layout of your
views immediately, call the layoutIfNeeded method.
I'm creating a view controller that has non-trivial rotation behavior. I can't find a way to get all my transformations to occur with only UIViewAutoresizing (I'm doing things like keeping by bottom bar at the same "absolute value" on the screen" while rotating buttons). What's the correct way to handle these type of rotation behaviors? Is there a way to disable auto resizing for a specific view and handle the rotations yourself?
If you set a view's autoresizesSubviews property to NO, that view will not apply autoresizing to its subviews. You can do this in a xib or storyboard by unchecking the “Autoresizes Subviews” checkbox in the attributes inspector.
The correct place to programmatically lay out subviews is in the layoutSubviews method of your custom UIView subclass. Your view will receive the layoutSubviews method whenever its size changes or when its subviews array changes. You will receive it when the view is first being put on the screen, and inside the autorotation animation block.
I have a grouped table view with custom cells (created programmatically by subclassing, not with IB). To properly position custom cell's subviews (such as labels and text fields), I need to know the current width of the cell's contentView just before the cell displays (taking into account that real cell width in a table view can change (according to screen orientation, modal presentation style, etc.)).
if I override in custom cell class the layoutSubviews method, it works perfectly, but it can be called frequently, thus I have to reposition my subviews every time when it's called, even when there's no need to do that.
Please, recommend me more elegant solution.
The recommended way of doing this is by setting the autoresizingMask of the table cell. If you need to have more control over the layout, you can store the last used view width in a member variable, and only layout the subviews if this differs from the current view width.