Animating constraints changes with setNeedsLayout - ios

The standard way of animating constraint changes is.
// Change constraints like:
someConstraint.constant = 100
UIView.animate(withDuration: 0.25) {
view.layoutIfNeeded()
}
However I was watching the WWDC 2015 session on multitasking on iPad and at the end it said don't use layoutIfNeeded in animation blocks, use setNeedsLayout instead. However I've always thought that this would mean the layout happens later in the run loop and so outside of the animation block. Perhaps it remembers it was called in an animation block?
I tried replacing layoutIfNeeded with setNeedsLayout in my code, and it seemed to work. Is this just coincidence and we actually should be animating autolayout changes with setNeedsLayout?

The documentation for layoutIfNeeded() states:
Use this method to force the view to update its layout immediately. When using Auto Layout, the layout engine updates the position of views as needed to satisfy changes in constraints. Using the view that receives the message as the root view, this method lays out the view subtree starting at the root. If no layout updates are pending, this method exits without modifying the layout or calling any layout-related callbacks.
The documentation for setNeedsLayout() states:
Call this method on your application’s main thread when you want to adjust the layout of a view’s subviews. This method makes a note of the request and returns immediately. Because this method does not force an immediate update, but instead waits for the next update cycle, you can use it to invalidate the layout of multiple views before any of those views are updated. This behavior allows you to consolidate all of your layout updates to one update cycle, which is usually better for performance.
setNeedsLayout() defers the layout for all subviews of the view, till the next cycle of the runloop, thereby consolidating all the changes to the appropriate time, instead of side-stepping the process and forcing the layout to occur instantaneously. Yes you should use setNeedsLayout() instead of layoutIfNeeded() in the UIView.animate function. Be a good citizen if possible.

Related

If I call setneedsdisplay frequently, how often will the drawrect be called? why is that?

In my case, the drawRect: will not be called immediately after every single setNeedsDisplay is called. For example, I think the following code is same as my case.
for (int i = 0; i < 100; ++i)
{
[self setNeedsDisplay];
}
From the documentation:
When the actual content of your view changes, it is your responsibility to notify the system that your view needs to be redrawn. You do this by calling your view’s setNeedsDisplay or setNeedsDisplayInRect: method of the view. These methods let the system know that it should update the view during the next drawing cycle. Because it waits until the next drawing cycle to update the view, you can call these methods on multiple views to update them at the same time.
drawRect: will only be called in time for the next frame to be drawn, which means your entire loop will result in drawRect: only being called once at the next rendering iteration. This saves unnecessary computation as it avoids drawing frames that will never be displayed on the screen. It also lets you make multiple changes in separate places in your code, each time notifying the view that a refresh is needed, without losing performance, since calling setNeedsDisplay only tells the drawing system that a redraw is needed in the next frame; it doesn't force the immediate rendering of a frame that might never be displayed on the screen.
setNeedsDisplay only marks the view as needing to be displayed again. The actual drawing call is done in the next runloop iteration of the main thread, once. This allows the drawing system to do some optimizations and "combine" repeated calls to setNeedsDisplay.

Is there a callback function when UIView's frame change?

Suppose that I need to do something whenever a view's width or height changes. When not using autoLayout, I can do this by implementing layoutSubviews. But I find that this function is not always called when using autoLayout.
Right now, I'm using the following code where something could possibly cause the view's frame to change:
[self setNeedsLayout];
[self layoutIfNeeded];
and do what I must do in layoutSubviews. But I wonder if this is the best way. Is there a simpler solution, for example a callback function I can use?
You should not have to call both methods at once. Calling setNeedsLayout schedules the view for layouting at the next layouting run, you don't know when this will happen.
On the other hand, layoutIfNeeded layouts the view and all it's subviews immediately. Then layoutSubviews should be called for all the views in the hierarchy starting with the one on which you called layoutIfNeeded.
If you changed your constraints and you need to update your view you should call layoutIfNeeded probably in an animation block so that the views don't "jump", using something like this
// Update constraints ...
[UIView animateWithDuration:0.3 animations:^{
[view layoutIfNeeded];
}];
If you don't change your frame "by hand" (meaning using setFrame:) and update your view only through constraints, after which you call layoutIfNeeded, layoutSubviews should be called every time.
If you do use setFrame:, which you should not do if you are using auto layout, then you can try using an observer.
I think you should check viewDidLayoutSubviews for a callback. Apple's documentation says
Called to notify the view controller that its view has just laid out
its subviews.
Have a closer look here: https://developer.apple.com/library//ios/documentation/UIKit/Reference/UIViewController_Class/index.html
Happy coding!
Z.
Update:
I've answered the wrong question before! :)
For UIViews you can override bounds' setter setBounds (not frame/setFrame, that's a value derived from bounds), and in there you'll get notified of any size change.
Don't forget to call [super setBounds:bounds] in your implementation :)
Z.

NSTimer is ruining all of my animations

I have added an NSTimer which updates my interface, mainly labels with information 10 times every second. It calls a function which dispatches the work back to the main thread.
The view controller also has a scroll view. I have a paging system where I animate my scroll view's moving from page to page as a user taps on the tab (a button) which corresponds to each tab. That scrolling animation has just stopped happening - it is as if I am calling scrollToRect with animation as NO, even though I am calling it with YES.
I think that because I am updating my labels, auto layout is doing something dodgy in the background and ruining my scroll view animations. The same is happening for other animations which are using layout constraints to move views.
I know there are issues with Autolayout and NSTimer.
What can I do to fix this?
Thanks
I would create a boolean flag that inhibits the updates from the timer method. Set this to true just before you begin the animations and false once the animation completes. You probably also want to update the data 'manually' once the animation completes to capture any blocked updates.

+[UIView transitionFromView:toView:...] with multiple views

So here's the situation:
I have let's say 20 views who are all subviews of the same view. Now I want to remove and add new views in an animated matter.
I previously did this by using regular UIView animations and fading them out or in respectively. The problem is though that the animation isn't flawless when the new subview who is to be inserted overlaps with the subviews who are fading out. So I tried using the +[UIView transitionFromView:toView:...] animation block to make the animation cross dissolve. This works well, animation looks good.
This basically solves my issue. The only problem is that it might be that I want to replace 2 subviews with one bigger subview. This doesn't really work out as I can't just pas nil to the transition method.
So my question basically is how I can simultaneously cross dissolve multiple subviews no matter how many views are animated?
Thanks for your help!
Have you tried putting the views to remove inside a transparent bigger view? So you just call transitionFromViewToView using the container with the 2 little ones inside and the big one that is entering?
You can use + [UIView transitionWithView:duration:options:animations:completion:], passing the common superview (usually self.view of your view controller) to perform all animations at once.
Excerpt from the doc:
This method applies a transition to the specified view so that you can
make state changes to it. The block you specify in the animations
parameter contains whatever state changes you want to make. You can
use this block to add, remove, show, or hide subviews of the specified
view. If you want to incorporate other animatable changes, you must
include the UIViewAnimationOptionAllowAnimatedContent key in the
options parameter.

When do I need to call setNeedsDisplay in iOS?

When creating an iOS app, I'm confused as to when exactly I need to call setNeedsDisplay? I know that it has something to do with updating/redrawing the UI; however, do I need to call this every time I change any of my views?
For example, do I need to call it:
After programatically changing the text in a text field
When changing the background of a view?
When I make changes in viewDidLoad?
How about in viewDidAppear?
Could someone give me some general guidelines regarding when to use this method?
You should only be calling setNeedsDisplay if you override drawRect in a subclass of UIView which is basically a custom view drawing something on the screen, like lines, images, or shapes like a rectangle.
So you should call setNeedsDisplay when you make changes to few variables on which this drawing depends and for view to represent that change , you need to call this method which internally will give a call to drawRect and redraw the components.
When you add an imageView or a UIButton as a subview or make changes to any subview, you need not call this method.
Example:
You have a view that shows a moving circle, either you touch and move it, or may be timer based animation.
Now for this, you will need a custom view that draws a circle at given center and with given radius.
These are kept as instance variables which are modified to move the circle by changing its center or make it bigger by increasing radius of it.
Now in this case either you will modify these variables(centre or radius) in a loop and timer Or may be by your fingers in touchesEnded and touchesMoved methods.
To reflect the change in this property you need to redraw this view for which you will call setNeedsDisplay.
You only really need to call -setNeedsDisplay on UIView subclasses that draw their contents with -drawRect:.
For labels and other standard controls, changing the text will automatically cause the label to redraw so you don't need to do this yourself.
setNeedsDisplay: should be called when you want to refresh your view explicitly. It just sets an internal flag, and the iOS UI system will call drawRect: at an appropriate time later.
It sounds like it should be always called when you updating any property which may change the presentation. But it's not. Almost all the standard UI controls already handled that. I believe whenever you modify the properties of standard UI components (views), setNeedsDisplay: would be triggered internally, and the affected region will be redrawn. (In all the situations you listed)
However, if you create your own view, implement its own drawRect:, and want to update that when something has been changed, you must call setNeedsDisplay: explicitly.
I think #Amogh Talpallikar make it clear. And I just wanna discuss one thing more.
In the fact that, you should avoid override drawRectunless you really need it because it can cause bad performance. You can refer this https://yalantis.com/blog/mastering-uikit-performance/
If you only wanna change frame, position of buttons, labels, ... you can call setNeedLayout or layoutIfNeeded
You will call setNeedDisplay when you are changing the property on which your view custom drawing depends. It will explicitly call drawRect: method forcefully.

Resources