AutoLayout resize view - ios

I have custom UIView with 2 multiline labels. Sometimes I need to hide this view so I set hidden = YES and height constraint to 0.
I am doing it this way but I'm not quite sure if I can change my constraints in my custom UIViews' layoutSubviews.
- (void)layoutSubviews {
[super layoutSubviews];
if (!self.hidden) {
self.heightConstraint.constant = 15 + self.titleLabel.frame.size.height + 4 + self.bodyLabel.frame.size.height + 15;
} else {
self.heightConstraint.constant = 0;
}
}
I know that layout is not one-way street process and layoutSubviews can cause updateConstraints and vice versa.
Is it safe to change constraints of view itself in layoutSubviews?

layoutSubviews is called after the updateConstraints method has done its job. If you change constraints after that, you're gonna have to call [super layoutSubviews] again. The correct place to modify constraints is in the updateConstraints method, but in your case you can simply make your changes outside the layoutSubviews method and then call setNeedsLayout after you're done.

You need setNeedsLayout method. Example:
self.view.setNeedsLayout()

Related

How can I get the frame of a UIView subclass using Autolayout with Storyboard?

When I use a subclass of UIView and put it on the storyboard with some constraints,i check its frame in awakeFromNib() or didMoveToWindow(). The result is not correct cause the constraints are not applied yet.
Is there any callback to inform me that the constraints are applied? So after that i can do sth with the correct frame.
When awakeFromNib viewDidLoad or didMoveToWindow is called, layout is not completed yet. You have to use viewDidLayoutSubviewsof a viewController. This method will be called after applying constraints and completing layout.
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
}

Is there an iOS method that fires when Autolayout has completed?

I have an iOS app in which I need to know when a new view is completely visible on-screen; that is, when Autolayout has finished its calculations and the view has finished drawing.
ViewDidAppear seems to fire well before the view is completely visible. If I turn off Autolayout, the timing seems to line up as far as human perception goes, but I need to use Autolayout in this project (so this isn't a solution...just a test).
Is there any method that fires when Autolayout is done calculating? Or another method that fires when the view is ACTUALLY visible (since ViewDidAppear doesn't work for this)?
Thanks!
The following can be used to avoid multiple calls:
- (void) didFinishAutoLayout {
// Do some stuff here.
NSLog(#"didFinishAutoLayout");
}
and
- (void) viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:#selector(didFinishAutoLayout)
object:nil];
[self performSelector:#selector(didFinishAutoLayout) withObject:nil
afterDelay:0];
}
I'm using viewDidLayoutSubviews for this. Apple's documentation says, "Called to notify the view controller that its view has just laid out its subviews."
If you watched 2018's WWDC about "High-Performance AutoLayout", you would know the answer to this question.
Technically, there is no such API method that will be called when autolayout has completed your view's layout. But when autolayout has completed the calculations, your view's setBounds and setCenter will be called so that your view gets its size and position.
After this, your view's layoutSubviews will be called. So, layoutSubviews can, to some degree, be thought of as the method that fires after autolayout has done calculations.
As to view controller's viewDidLayoutSubviews, this is a bit complicated. The documentation says:
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. 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.
So when viewDidLayoutSubviews called on a view controller, only the view controller'view 's first-level subviews are guaranteed to be laid out correctly.
What it worked in my case was request layout after changed a constraint value:
self.cnsTableviewHeight.constant = 50;
[self layoutIfNeeded];
Later on override layoutSubviews method:
- (void) layoutSubviews { //This method when auto layout engine finishes
}
You can call setNeedsLayout also instead of layoutIfNeeded
I guess implementing viewDidLayoutSubviews is the correct way but I used an animation just to write the completion callback inside the same method.
someConstraint.constant = 100; // the change
// Animate just to make sure the constraint change is fully applied
[UIView animateWithDuration:0.1f animations:^{
[self.view setNeedsLayout];
} completion:^(BOOL finished) {
// Here do whatever you need to do after constraint change
}];
You might face this problem not just with UIViewControllers but also UIViews. If you have a subview and want to know if AutoLayout has updated it's bounds, here is the Swift 5 implementation,
var viewBounds: CGFloat = 0.0
var autoLayoutHasCompleted: Bool = false
override func awakeFromNib() {
super.awakeFromNib()
// someSubView is the name of a view you want to check has changed
viewBounds = someSubView.bounds.width
}
override func layoutSubviews() {
if viewBounds != someSubView.bounds.width && !autoLayoutHasCompleted {
// Place your code here
autoLayoutHasCompleted = true
}
}

Dynamically resize UITableView in UIViewController created in Storyboard

In a UIViewController on a storyboard, I have a UITableView that is sized specifically to have two rows in one section with no header or footer, i.e. the height is 88.0f. There are some cases when I want to add a third row. So in viewWillAppear:animated: (and other logical places) I set the frame to be 44.0f logical pixels higher:
CGRect f = self.tableView.frame;
self.tableView.frame = CGRectMake(f.origin.x, f.origin.y, f.size.width, f.size.height + 44.0f);
NSLog(#"%#",NSStringFromCGRect(self.tableView.frame));
Nothing controversial; pretty standard resize code, and yet... It doesn't work! The tableView height doesn't change visually. The NSLog statement reports the height I expect (132.0f). Is this because I'm using Storyboards? I'm not sure why this isn't working.
Set an auto layout constraint for the height of the table view in your storyboard. Then connect the constraint to an outlet in your view controller so you can access the constraint in your code. Have the constraint be set to 88. When you want to change the height of the table view, just change the constraint's constant to 132.
You can modify the frame only after the call to layoutSubviews is made, which occurs after viewWillAppear. After layoutSubviews is called on the UIVIew you can change the dimensions.
As Gavin suggests, if you have the autolayout enabled you can add the constrains to the UITableView via storyboard, connect the height constraint and modify its value as follow:
constraint.constant = 132.0f
Otherwise if you have the autolayout disabled you can simply change the frame updating the height, but putting the code in a different method, for example viewDidLoad:.
recently I'm try to do what you've do. And I got same problem, tableview height won't change. Now I got the solution, you need to call layoutSubviews after change the frame. And it work on me.
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
tableView.frame = CGRectMake(tableView.frame.origin.x, tableView.frame.origin.y, tableView.frame.size.width, tableView.frame.size.height + 44.);
[tableView layoutSubviews];
}
don't place it in viewDidLoad or viewWillAppear: because even layoutSubviews is called, the frame won't change. place it on viewDidAppear:

Is layoutSubviews called whenever view's size is changed?

In my impression, with autoresizesSubviews = YES, layoutSubviews should be called every time view's size is changed. But I found it is not the case for my view. Is my expectation wrong?
According to sources at Apple,
"-[UIView layoutSubviews] should get called when the size of the view changes."
They also referred me to this, from the the View Programming Guide for iOS:
"Whenever the size of a view changes, UIKit applies the autoresizing behaviors of that view’s subviews and then calls the layoutSubviews method of the view to let it make manual changes. You can implement the layoutSubviews method in custom views when the autoresizing behaviors by themselves do not yield the results you want."
At this point, your best move is to create a small sample project where layoutSubviews does not get called (or, send your existing project) file a bug with Apple using BugReporter, and include that sample project with your bug.
If you need something to happen when your view is resized, you can also override setBounds: and setFrame: for your class to make sure it happens. It would look something like this
-(void)setBounds:(GCRect newBounds) {
// let the UIKit do what it would normally do
[super setBounds:newBounds];
// set the flag to tell UIKit that you'd like your layoutSubviews called
[self setNeedsLayout];
}
-(void)setFrame:(CGRect newFrame) {
// let the UIKit do what it would normally do
[super setFrame:newFrame];
// set the flag to tell UIKit that you'd like your layoutSubviews called
[self setNeedsLayout];
}
The other reason that I sometimes override these methods (temporarily) is so I can stop in the debugger and see when they are getting called and by what code.
From my understanding, layoutSubviews is called when the view's bounds change. This means that if its position changes in its superview (but not its size) then layoutSubviews won't be changed (since the origin point in the bounds is in the view's coordinate system - so it is almost always 0,0). In short, only a change in size will cause this to be fired.
whenever you want to resize the views manually and resizes automatically call layoutSubViews method
-(void)layoutSubviews
{
[super layoutSubviews];
CGRect contentRect = self.contentView.bounds;
CGFloat boundsX = contentRect.origin.x;
CGRect frame,itemlabelframe,statuslabelframe;
frame= CGRectMake(boundsX+1 ,0, 97, 50);
itemlabelframe=CGRectMake(boundsX+100, 0, 155, 50);
statuslabelframe=CGRectMake(boundsX+257, 0, 50, 50);
ItemDescButton.frame=itemlabelframe;
priorityButton.frame = frame;
statusButton.frame=statuslabelframe;
// ItemDescLabel.frame=itemlabelframe;
// statusLabel.frame=statuslabelframe;
}

Perform code when frame is changed

I have a custom class that's a subclass of UIView. In the storyboard I set a UIView's class to the custom class. The view in the storyboard has a height constraint so that I can change the height programmatically. (I know it's not the only way, but I think it's the easiest way.)
I want to perform some code in the custom class every time the view's height changes.
I tried the following:
- (void)setFrame:(CGRect)frame {
[super setFrame:frame};
NSLog(#"Frame did change");
}
But this method only runs on startup, not when it's (self) height was changed. How can I perform code anytime it's frame is changed?
Just override layoutSubviews method in your custom view class
- (void)layoutSubviews
{
[super layoutSubviews];
NSLog(#"Frame did change");
}

Resources