forcing setNeedsDisplay - ios

I have a routine that draws a gearwheel using CoreGraphics and drawRect. I used a button to advanced the gears through the stages:
-(IBAction)advanceButtonPressed:(id)sender{
stage=stage+1;
if (stage==4) stage=0;
[self setNeedsDisplay];
}
The routine for drawing the gear is in drawRect, and the gear teeth are drawn in a new position depending on the vale of stage, which is passed to drawRect. In four steps, one per button press, the gear turns to its new position (one segment around the circle.)
I changed the buttonPressed routine to have it do the four stages in a loop, so I only need to press the button once, but it doesn't work:
-(IBAction)buttonPressed:(id)sender{
for (stage=0;stage<4;stage++){
NSLog(#"%i",stage);
[self setNeedsDisplay];
}
}
My NSLog shows that stage goes through its four values, just as it would in the earlier routine. But setNeedsDisplay is only executed once, (as a NSLog trace in drawRect confirms) so the gear is displayed in its final position but not the intermediate stages. It looks like setNeedsDisplay only executes when the button routine finishes, despite it being inside the loop.
Is there any way to force it to execute?

The iPhone only updates the screen 60 times a second no matter how often you call setNeedsDisplay. Your loop is going to execute in less than 1/60th of a second. That's why you only will see the last state.
If you want to show all four states through one button press then you could use a timer or performSelector: afterDelay to show all four states. A little more complicated but there you go.

Invoking setNeedsDisplay just marks the view to be redrawn at the next drawing cycle.
To redraw it during your method, you could explicitly call drawRect:

Related

iOS layer color change

For my custom UIView I've overriden touchesBegan method. What I told it to do is to change its' layer's background color:
dispatch_async(dispatch_get_main_queue()){
self.layer.backgroundColor = clr_someCGColor
}
It acts weird. If I quickly tap the view while in Landscape it does everything perfectly well, but if I do this in Portrait, I have to hold it for some time to see the result, however the touchesEnded method is called right away, if I quickly tap. What could be the reason, causing the delay in Portrait?
Remove the dispatch_async wrapper. All it does is cause a delay (we can't execute on the main thread until, as you rightly say, the tap ends and touchesEnded has come and gone). You are already on the main thread, in touchesBegan, so there is no need for this extra delay.
Even better, use a tap gesture recognizer.

Reflecting UIView changes after drawing

How can I reflect UIView changes of a setNeedsDisplay?
In drawRect triggered by setNeedsDisplay
- (void)drawRect:(CGRect)rect {
double i = 0;
for(...)
//i is incremented
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(i * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
drawStuff(element, context);
});
For example, each draw update would happen after 1s, 2s, 3s, ...
DrawStuff switches back to the main thread with
dispatch_async(dispatch_get_main_queue(), ^(void) { ...
when updating the UI. I have tried both UIBezierPath and Core Graphics and get the same results. I am also using UIGraphicsPushContext and UIGraphicsPopContext to get the correct context.
Using NSLog, I can tell the drawStuff is called in the 1s increments. However, the visible UIView remains unchanged. I can rotate the device screen and this will reposition the UIView and change its size. On rotation the UIView will reflect what it has currently drawn, but then I need to rotate it again to see the updated state after a few seconds.
Back to the question above, is there a call to tell UIView to display its current graphics after setNeedsDisplay? Thanks.
If you are trying to make sure your view is redrawn once per second, the right way to do this is to set some form of timer elsewhere in your view, and have the timer function call -setNeedsDisplay. This will in turn invalidate your view and cause your -drawRect: to be called, which should do the drawing (ie call your drawStuff)
Conceptually, -drawRect: should only be a dumb method that just knows how to do the actual drawing work based on whatever the current state of the view is. Your normal event logic (or a timer) outside of that should (a) update the state as necessary and (b) mark the view as needing to be redrawn.

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.

iOS - limit on UIView animate?

So I'm making a simple trivia game and I have a timerView that shrinks as time passes. When the user selects an answer, it needs to stop shrinking immediately - it must be very responsive. I give the user 10 seconds per question. Originally I would animate 10 times (with a duration of 1.0f), calling the next "segment" of animation in the completion block of the previous animation. In the completion block I would check to see if the user has tapped an answer, and if so I don't continue the chain. That solution works fine except that it's not very responsive because it's on a per second basis-- user taps an answer at the start of the second segment and the bar has a noticeable continuation.
My solution to THAT problem was to instead have 1000 animation calls with a duration of 0.01f. After doing that, the responsiveness was on point - the view stops animating as soon as I tap an answer -- the issue though, is that it's not actually 10 seconds, it takes more like 20.
So question number 1: what's the smallest time interval animateWithDuration can actually process properly?
Question number 2: is there a better way to accomplish what I'm trying to do accomplish?
ill answer question two: yes there definitely is a better way, have a look at CADisplayLink
use it to shrink your view a little bit each frame, and end the display link when you need to
the most responsive way is: the user taps an answer, you response in the touch callback, remove animations. you can remove animations by CALayer's removeAllAnimations method
Another way to do it is to set the view to shrinking using a single animation with linear timing, and then set the speed of the view's layer to 0 to pause the animation. When you set the speed on the layer to 0 the animation pauses instantly.
This works because under the covers, UIView animation actually creates and installs CAAnimation objects on the view's layers. It's possible to pause and continue an in-flight UIView animation just like you can a CAAnimation.
I have a project called KeyframeViewAnimations (link) on github that allows you to pause, continue, or "scrub" UIView and CAAnimations back and forth with a slider. You could use that technique. The tricky bit will be figuring out how far along the animation is.

UIScrollView animation problems

I'm using a horizontal UIScrollView that displays photos on the first half of the screen. (Imagine a CGRect (0,0,320,120). The first scrollView is embedded in a second scrollview, which takes all the screen. When I scroll down the page (thus the second scrollView), the first scrollview stop being animated. I programmed a NSTimer to change photo every 3 seconds in the first UIScrollView, but while I'm scrolling the second scrollView, it seems like the animations are being queued. When I release my finger of the screen, there are few photo transitions (i.e: 2 changes if I scrolled without stoping for 6 seconds). In short: how can I make use of blocks (or something else) to continue my animations while I'm scrolling the second scrollView?
My NSTimer (in viewDidLoad):
timer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:#selector(changePage:) userInfo:nil repeats:YES];
Your iOS app has a run loop that continually checks for things like user input. If it is busy detecting touch events, things that are scheduled on that run loop will not get performed. (Similarly, if you block the main thread with e.g. a big server request, you won't get touch events during that time).
Here's how you can get the NSTimer on the right run loop:
First, use timerWithTimeInterval:target:selector:userInfo:repeats: to create a timer not scheduled on a run loop.
Second, use - (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode to schedule the timer on a run loop. For the mode argument (the run loop type), use NSRunLoopCommonModes to allow the timer to fire without having to wait on the main run loop.
Note that this same effect exists for e.g. performSelectorAfterDelay if done in the main run loop. It also applies to animation completion blocks. I'm not sure if there's an easy way around the animation completion block problem besides using CoreAnimation.

Resources