I don't know the difference between setneedsdisplay and uiviewcontentmoderedraw, when would you use each, aren't they the exact same thing?
They are different things. setNeedsDisplay is a verb. Use it to tell a view that the state of the stuff it's viewing has changed, so it should redraw (by calling its drawRect: method on the next iteration of the run loop).
contentMode is an attribute of a view. It doesn't cause the view to do anything immediately. It specifies how the view handles its content relative to its size. UIViewContentModeRedraw is a value that might be assigned to this property. It means that the view will render size changes by causing itself to redraw (by invoking setNeedsDisplay on itself).
If you plan to animate alteration of your view's size, UIViewContentModeRedraw is an expensive choice because it will try to repeatedly redraw from scratch during the animation (rather than manipulating a bitmap copy).
Related
What does cell.layoutIfNeeded() and cell.layoutSubviews() and cell.setNeedsDisplay() method do in general ?
layoutSubviews
Lays out subviews. in short this method lets determine the size and position of any subviews, it also helps to get desired behaviour you want for your view if your have a subclass. Documentation here
2.layoutIfNeeded
Forces view to layout immediately,for example you have changed a constraints constant and to reflect the change you need to call layoutIfNeeded.(it can also be animated :P). Documentation here
setNeedsDisplay
Marks the view need to be redrawn in the next drawing cycle, when you call this method, system is notified that view's content is changed and it will be redrawn in next drawing cycle. Documentation here
Please consider reading documentation for better understanding
I'm having this general question about a problem that arises very often when I'm designing complex UIViews that require a special layout and animations at the same time.
What I'm currently building is the same kind of view that the Mail app uses on iOS to present a list of recipients when writing an email (the blue badges container) :
So I understand and know how I would build such a view, but I always have this question when facing such a case : How do you handle the complex layout (layouting all the blue rounded rectangles) and their animations at the same time (when adding or removing a recipient) ?
1. Normally I would reimplement :
- (void)layoutSubviews
to reflect the state of the view at the layout moment (ie layout each blue rounded UIButton side by side) according to my current bounds and just add and animate a UIButton when someone adds a new person to the list.
But what would happen to the animation if a layout pass is already running? (this may be a dumb question since everything is supposed to happen on the main thread and so no concurrency is involved here, but I'm not sure on how to handle this)
I think those two things (layout and animations) are not supposed to happen at the same time, since the main runloop "enqueues work and dequeues work" one "block" at a time (but maybe an animation is just hundreds of blocks enqueued that draw different things overtime, which could easily be compromised by a layout block in between??)
2. Also, would this solution be acceptable ?
Reimplement layoutSubviews the exact same way to handle the correct layout of my subviews
When someone adds or deletes a person, just call this to re-position everything animated
[UIView animateWithDuration:0.25f animations:^{
[self setNeedsLayout];
[self layoutIfNeeded];
}];
As you can see, lots of questions here, I don't know exactly how to handle this gracefully.
Both of these solutions have been tested and approved but the thing is which one is the best ?
Do you have a better solution ?
Thanks for your help!
As I understood, you mean animations applied to a view for which layoutSubviews method is implemented, e.g. animated change of its frame. If something is changed in your view while animation is ongoing that causes the view to re-layout, then layoutSubviews method will be called.
Basically, such kind of animations is nothing more than following:
change view's frame (actually, frame of a view's layer) by some small value (e.g. increase origin's Y coordinate by 1 pixel)
call layoutSubviews, if needed
redraw view (i.e. its layer)
change view's frame by some small value
call layoutSubviews, if needed
redraw view
...
repeat steps above until view's properties reach target values
All those steps are performed on the main thread, so steps above are consecutive and are not concurrent.
Generally speaking, the layoutSubviews method should define coordinates and sizes of subviews basing on its own bounds. Animations change the view's frame and correspondingly its bounds, so your implementation of the layoutSubviews method is supposed to handle those changes correctly.
That is, the answer for your question is implement correctly layoutSubviews method.
P.S. This answer has a great explanation of how animations are implemented.
Update
It seems that previously I understood the question incorrectly. My understanding was:
What happens if layoutSubviews method is called for a view being animated?
That is, I assumed that there is some view which e.g. changes its y coordinate with animation, the layoutSubviews method is called for this view during animation, and some subview of the view changes its position in the layoutSubviews.
After clarification from #Nicolas, my understanding is as follows:
Let's have some view ('Parent') with a subview ('Child'); let's animate this subview ('Child'); let's call layoutSubviews method of the 'Parent' view and change 'Child' subview's frame in this layoutSubviews method while animation is ongoing. What will happen?
Background Theory:
UIView itself doesn't render any content; it's done by Core Animation layers. Each view has an associated CALayer which holds a bitmap snapshot of a view. In fact, UIView is just a thin component above Core Animation Layer which performs actual drawing using view's bitmap snapshots. Such mechanism optimizes drawing: rasterized bitmap can be rendered quickly by graphics hardware; bitmap snapshots are unchanged if a view structure is not changed, that is they are 'cahced'.
Core Animation Layers hierarchy matches UIView's hierarchy; that is, if some UIView has a subview, then a Core Animation layer corresponding to the container UIView has a sublayer corresponding to the subview.
Well... In fact, each UIView has even more than 1 corresponding CALayer. UIView hierarchy produces 3 matching Core Animation Layers trees:
Layer Tree - these are layers we used to use through the UIView's layer property
Presentation Tree - layers containing the in-flight values for any running animations. Whereas the layer tree objects contain the target values for an animation, the objects in the presentation tree reflect the current values as they appear onscreen. You should never modify the objects in this tree. Instead, you use these objects to read current animation values, perhaps to create a new animation starting at those values.
Objects in the render tree perform the actual animations and are private to Core Animation.
Change of UIView's properties such as frame is actually change of CALayer's property. That is, UIView's property is a wrapper around corresponding CALayer property.
Animation of UIView's frame is actually change of CALayer's frame; frame of the layer from Layer Tree is set to the target value immediately whereas change of frame value of layer from presentation tree is stretched in time. The following call:
[UIView animateWithDuration:5 animations:^{
CGRect frame = self.label.frame;
frame.origin.y = 527;
self.label.frame = frame;
}];
doesn't mean that self.label's drawRect: method will be called multiple times during next 5 seconds; it means that y-coordinate of the presentation tree's CALayer corresponding to the self.label will change incrementally from initial to target value during these 5 seconds, and self.label's bitmap snapshot stored in this CALayer will be redrawn multiple times according to changes of its y-coordinate.
Answer:
Given this background, now we can answer the original question.
So, we have ongoing animation for a child view, and layoutSubviews method gets called for a parent view; in this method, child view's frame gets changed. It means that frame of a layer assiciated with the child view will be immediately set to the new value. At the same time, layer from the presentation tree has some intermidiate values (according to ongoing animation); setting new frame just changes target value for presentation tree layer, so that animation will continue to the new target.
That is, result of situation described in the original question is a 'jumping' animation. Please see demonstration in the GitHub sample project.
Solution for such complex cases
In your layoutSubviews method, if an animation is ongoing, you need to use in-flight animation coordinates. You can obtain them with the presentationLayer method of CALayer associated with a view. That is, if a view being animated is called aView, then presentation layer for this view can be accessed using [aView.layer presentationLayer].
I have a CALayer for which I provide content for only the visible area (somewhat similar to CATiledLayer). The problem is there does not seem to be a way to receive notification when the visible area of the CALayer changes so that displayLayer is called. I currently subclass and hook setPosition, setBounds, and setTransform, but this doesn't catch the cases where a superview/layer changes (for example, UIScrollView scrolls by changing the scroll views origin ). I'm left hooking parent views and sprinkling setNeedsDisplay all over the code.
Is there a better way?
The currently visible rect is [CALayer visibleRect]. This is set by the scroll view (layer) and is what you're expected to base drawing on in scroll views.
You probably want to override -needsDisplayOnBoundsChange to return YES. That's typically how you handle most of what you're describing.
If you want things like position to force a redraw (that's unusual, but possible), then you can override +needsDisplayForKey: to return YES for any key changes that you want to force a redraw.
If you want to make sure you're only drawing what you need to draw, then you should be checking your clipping box using CGContextGetClipBoundingBox() during your drawing code.
I have some view controller, that uses my own view class and XIB interface, so the view initializes from coder. In that view controller, when I move the slider, it redraws rectangle, in initial state rectangle is on full screen and when I move the slider to the left, the size of rect become smaller proportionally (from full screen to 10x14 continuously with 4 pixel intervals).
The "slider did change a value" calls some method, that sets new rectangle size end sends "setNeedsDisplay" method. And if I change to "setNeedsDisplayInRect:" method, it not updates all what I need on screen, there is some artifacts. But with debugging it updates all like I want. Tried to send deferent rectangles to update, like:
redrawRect=CGRectUnion(oldRect, newRect);
[cView setNeedsDisplayInRect:redrawRect];
and some others, same story. My question is, which rectangle I need to send in "setNeedsDisplayInRect:" ?
And the second question, how can I accelerate/optimize all that story for smoother animation and less cost?
Already solved the problem 1. The "setNeedsDisplayInRect:" method uses points, and not pixels like I thought.
Assume that two very small areas of my view need to be redrawn. One is in the upper left corner, the other in the bottom right. I could use their coordinates to pass a single large CGRect that contains both areas to setNeedsDisplayInRect, but this will end up including a lot of other areas that do not need to be redrawn.
So the other option would be to simply pass their individual containing CGRects to setNeedsDisplayInRect, one after the next, i.e.
[self.view setNeedsDisplayInRect:rectForArea1]
[self.view setNeedsDisplayInRect:rectForArea2]
Which would generally be faster? Minimizing the number of times that drawRect: ultimately gets called, or minimizing the amount of screen area that it has to redraw, even if it must redraw twice?
It does not matter. As described here, iOS will always update the whole view, independent of what rect you pass into setNeedsDisplayInRect:
Note that, because of the way that iPhone/iPod touch/iPad updates its
screen, the entire view will be redrawn if you call
-setNeedsDisplayInRect: or -setNeedsDisplay:.
it is depending upon that two rect.
if both bounds addition are 75% of view bounds then i will call simply call
[self.view setNeedsDisplay];
because we draw almost whole view.
if both bounds addition are below 50% of view bounds then i will call simply call
[self.view setNeedsDisplayInRect:rectForArea1];
[self.view setNeedsDisplayInRect:rectForArea2];
because we need to draw small spaces