setNeedsDisplay breaks UIView's Auto Layout animations - ios

I have a UIViewController that contains two views (from two child view controllers): A top content view, and a bottom menu popup. The constraints are set up so that as the bottom menu expands, the top content view is squashed:
All the constraints are set up correctly so the subviews all layout correctly when the menu is expanded and collapsed. It even works correctly with a UIView animateWithDuration: block (I am calling [self.view layoutIfNeeded] to update the layout of the subviews during animation).
However, one of the views within the Content View area is a custom view containing a drawRect: method. Every time this view updates, I am calling setNeedsDisplay. As soon as setNeedsDisplay is called for the first time, the animations for this view break and will not update its frame until setNeedsDisplay is called again. I cannot get it to animate with all the other views! Comment out the drawRect: method, or comment out the [self setNeedsDisplay], and the view animates with the parent views again perfectly.
Does anyone know what I might be doing wrong?

I think you just should let the iOS do the work for you.
If your view needs to be redrawn often, set the view's contentMode:
yourView.contentMode = UIViewContentModeRedraw;
And it should redraw when needed.

Related

What's exactly viewDidLayoutSubviews?

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.

Resize a superview to make its size equal to the the size of its subviews

I have subclassed a UIView and created few views inside it. I want to create an instance of this view inside my view controller's view and there's another custom view which will be placed below that view. I am not sure how do I put the second view below my first view.
When I debug the frames, I get {{0,0}, {0,0}}. I am using Masonry for view layout. I have tried several solutions from this thread
I have created a sample project which has the actual code I have tried.
https://github.com/anuj-rajput/ViewSample
In this sample project, there are 2 subclasses of UIView, PhoneVerificationTopView and PhoneVerificationPhoneNumberView and a view controller PhoneVerificationViewController which creates objects from both these views. I need to perfectly order them in such a way they are perfectly aligned vertically (PhoneNumberView below TopView).
Am I doing something wrong? Is there a right way to subclass a UIView and then refer it in the controller?
This is how the view should look like
When you are using AutoLayout, and you want the parent view to resize according to I'ts subclasses, It needed to know how big the content is.
The most top view has to have a constraint to the top of the parent view.
And the most bottom view has to have a constraint to the bottom of the parent view.
Currently your views are acutely not containing there subclasses.
If you will try, for instance to set the background color of "topView" to red,
You will notice, It does not change I'ts color. That is because I'ts size is 0.
I'ts not wrapping I'ts content.
If you will set "clipToBound" to YES, you will not see Its subclasses too.
To fix it, add a bottom constraint, from the bottom view to the parent view:
[self.subtitleTextLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.titleTextLabel);
make.width.equalTo(self.mas_width).multipliedBy(0.7);
make.top.equalTo(self.titleTextLabel.mas_bottom).with.offset(10.f);
make.bottom.equalTo(self.mas_bottom); //This line is added
}];
[self.sendButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self);
make.width.equalTo(self).multipliedBy(0.4);
make.height.equalTo(#40);
make.top.equalTo(self.phoneNumberTextView.mas_bottom).with.offset(30.f);
make.bottom.equalTo(self.mas_bottom); // This line is added
}];

Animating UIView with auto layout when the view is just created and about to appear

I have a UIView that contains subviews that contains subviews...(Hierarchy of UIViews).
Every UIView is set with auto layout. When I press a button, this UIView is created and then I would like it to drop from the top of the screen.
The problem when I use layoutIfNeeded in an animation is that if will animate everything at the same time as it is a newly created UIView (not yet display).
My question is, is there a way to do the animation of only the UIView dropping with all the subviews already laid out in it?
I guess you can do it by creating the UIView when UIViewController displays and then hide it but I was wondering it there was another way.
Cheers
If you call layoutIfNeeded right after you add it to a view, while still outside the view, it should lay out it's views correctly. And then in the animation you call it again so the view animate to the final position.

animation not correct with implicit autolayout

I upload my demo code here:https://github.com/liuxuan30/Problems
The main problem is, I had a view which have a scroll view inside, and there is a label and collection view inside scroll view.
There will be a unread message button generated by code, when there is unread messages, the button will pop out, and I expect the animation: label and collection view will down-shift by the button's height.
When I test the animation without adding the button in subviews, animate as expected.
When I add the button in, it seems like the label and collection view's origin.Y up-shifted, and start the animation. Turning off auto layout will solve it, but I have to have auto layout. I tried to add constraints for all views, but the animation still not work out.
You can try comment out
[self.HomeScrollView addSubview:AlertView]; and self.UnreadAlertView.alpha = 1.0f; inside the code to see the animation.
Hope someone could figure out where i did wrong.
UICollectionView is already a subclass of UIScrollView. Embedding your collection view inside a scroll view may be what is causing problems with your animation.
Try removing your scroll view and just put your UIButton, UILabel, and UICollectionView inside your overarching view.

layoutSubviews called twice when rotating

When my main view rotates I want to re-arrange the sub views, so in my ViewController, I override
willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration
and set the frames of the subViews in there. This is all well and good, but in the subViews I have also overridden
layoutSubviews
so they layout themselves correctly. But the problem is this now gets called twice - presumably once when I set the Frame in willAnimateRotationToInterfaceOrientation and once because of the rotation. (If I don't set the Frame it gets called once.)
Surely it's the responsibility of the ViewController to layout the frames so this seems like a design flaw - what's the solution so layoutSubviews is only called once?
I had the same question. I found this page to be helpful for me. Is this useful for you?
EDIT:
Here is the summary of the page (copied):
init does not cause layoutSubviews to be called (duh)
addSubview causes layoutSubviews to be called on the view being added, the view it’s being added to (target view), and all the subviews of the target view
setFrame intelligently calls layoutSubviews on the view having it’s frame set only if the size parameter of the frame is different
scrolling a UIScrollView causes layoutSubviews to be called on the scrollView, and it’s superview
rotating a device only calls layoutSubview on the parent view (the responding viewControllers primary view)
removeFromSuperview – layoutSubviews is called on superview only (not show in table)

Resources