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

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
}

Related

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

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()
}

When to obtain the right frame size

The problem: Let's suppose I have an image and an element which constantly triggers viewDidLayoutSubviews (a scrollview.. so that at every scroll viewDidLayout would be called... or whatever element that triggers viewDidLayout quite often).
This image as well as the "viewDidLayout_element" are all set up well with autolayout in the storyboard.
Now:
I need to make the image to be a rounded one.
with the typical: layer.cornerRadius.. and layer.masksToBounds. This requires a calculated imageView frame.
In what view controller cycle do I make it programatically? Taking into consideration that:
*except for the viewDidLayoutSubviews I don't get the right frame
*the viewDidLayoutSubviews can be called even thousands of times depending on the "viewDidLayout_element" that triggers it.
*If I call layoutIfNeeded on the imageView in viewDidAppear (because it's the only case when frames are already available so we can force their calculation) the user will already catch a glimpse of the transformation from a square image to a circular image. In other words in viewDidAppear the frame becomes available for our manipulation, but is also available for milliseconds to the user's eyes.
*it does not make sense to fill the viewDidLayoutSubviews with flags (especially if there will be something more performance intensive than a circular imageView transformation) like below:
if !iDidChangedTheImage
{
imageView.applyCornerRadius()
}
The question:
Where do I have the correct frame size inside a viewController cycle without the problems from above? Or how do you usually solve this problem?
Subclass UIImageView to your custom class, and override layoutSubviews. After that you don't need to care about image view size change and updating corner radius, just use that class for your custom image view
#implementation CustomImageView
- (void)awakeFromNib {
[super awakeFromNib];
self.layer.masksToBounds = YES;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.layer.cornerRadius = self.frame.size.height / 2.0;
}
#end

UIScrollview: What is setting the contentSize back to 0?

I have a UIScrollView, and in viewDidAppear I set the contentSize:
- (void)viewDidAppear:(BOOL)animated{
CGSize scrollContentSize = CGSizeMake(320, 9200);
self.scrollView.contentSize = scrollContentSize;
}
This code is definitely running.
However, the view doesn't scroll. I wired up a button to log the contentSize, and it returns 0,0. If I get the button to set contentSize again it works fine.
I'm not referencing scrollView anywhere else in my code, what could be setting the contentSize back to 0, and is there any way I can stop it from doing so, or run my setup later in the process of setting up the view?
if you used the Autolayout then you have to make constraints for subviews in it. Then Autolayout will calculate its content size automatically.
But when I set contentsize through button action it works? How?
When the view relayout the scrollview again it will be calculated based on the constraints. So it won't work when u rotate the device/relayout the view.
I don't want to use the autolayout?Now what?
Then you are good to set contentsize.
One more thing, it is best practice to call super implementation on few lifecyle methods.
so do it so.
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
// Your code here
}
Here is the Good starting point
Excellent tutorial

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
}
}

Reloading viewDidAppear so it will function as ViewDidLoad

In my viewDidAppear i"m changing the frame of one of my ImageViews. The view and all the other methods will not show it until i will [self viewDidAppear] it.
I feel its not right, is there some reloadData message ?
Thank you.
Exactly, calling [self viewDidappear] yourself is not right.
If you need to change the frame of the views while being on the view, create a method yourself that you can call every time you want, or use viewDidLayoutSubviews;
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.
Also, check that your method has a correct implementation:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
//Your code
}
It would be better to change your image view's frame in viewDidLayoutSubviews.
You probably implemented the wrong function. The proper method signature is:
- (void)viewDidAppear:(BOOL)animated
not just viewDidAppear.

Resources