14

In my app I have a number of small subviews. When the user taps a subview, I place one separate view over top of the tapped subview (same size) and then animate this separate view to full-screen size.

The enlarging view is custom-drawn, which is creating a big problem with this approach. If I create the enlarging view in my main view's viewDidLoad method and set its frame to full-screen size, the view's drawRect method is called immediately, and the view is properly rendered full-size. If I then use this view in the animation, everything works as expected.

However, I need this enlarging view to be different depending on which subview was tapped, so I need to re-create/re-draw it each time. The problem is that when I do this in response to a touch event, the view's drawRect method does not get called until the animation begins, at which time the view's frame is small instead of full-screen, and as a result everything is drawn wrong.

In my code for the touch event, I'm explicitly setting the view's frame to fullscreen and calling setNeedsDisplay before starting the animation, but this has no effect - drawRect just isn't called until the animation begins.

Is there any way to force a redraw, so that the view is drawn while its frame is the size I want it to be?

MusiGenesis
  • 74,184
  • 40
  • 190
  • 334
  • 1
    I believe that Joe was asking something similar in his question [Is there a way to make drawRect work right NOW??!](http://stackoverflow.com/questions/4739748/is-there-a-way-to-make-drawrect-work-right-now), from which [Tom's answer](http://stackoverflow.com/questions/4739748/is-there-a-way-to-make-drawrect-work-right-now/4766028#4766028) may achieve what you are looking for. – Brad Larson Mar 23 '11 at 17:08
  • 1
    @Brad: that worked perfectly, thanks. If you put your comment as an actual answer, I'll select it. This was the line of code that works: `[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];` – MusiGenesis Mar 23 '11 at 17:17
  • 1
    Glad that worked. I only commented because I wasn't quite sure if I understood your requirements as far as when the redraw needed to occur. Also, a `[CATransaction flush]` may do what you want without requiring you to manipulate the run loop. However, you'd want to use that before triggering your animations, because it could interfere with them. – Brad Larson Mar 23 '11 at 17:33
  • @Joe: if you would make more of an effort to answer my iPhone questions when they come up, I might actually be able to get back to my music software. :) – MusiGenesis Mar 24 '11 at 22:30
  • calling [CATransaction flush] after setNeedsDisplay has worked for me in a similar situation. – Ben Wheeler Jul 31 '13 at 17:35

4 Answers4

25

As I commented above, Joe asked something similar (as a more general question) here, to which Tom Swift provided an answer that manipulates the current run loop.

His suggestion was to force the run loop through one iteration immediately after you call -setNeedsDisplay using the following code:

[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];

There are some caveats to this approach, which are discussed in the comments there.

Also, it was pointed out there that

[CATransaction flush];

may also achieve the same effect of forcing an immediate redraw of the view.

Community
  • 1
  • 1
Brad Larson
  • 170,088
  • 45
  • 397
  • 571
4

The proper way to do this is to simply divide up your method.

Just call setNeedsDisplay, then return without animating. Wait until the drawRect that sets everything up correctly has finished its redraw, and then have it queue up another method that starts your desired animation (via queuing an operation or a notification). Then start the animation. This can all happen fairly quickly (at frame rate), and involves no extra (dangerous) recursive run loop calls or CATransaction calls (which may cause extra GPU setups).

gklka
  • 2,459
  • 1
  • 26
  • 53
hotpaw2
  • 70,107
  • 14
  • 90
  • 153
  • I don't think this would work. My problem is essentially that I'm trying to animate a view from very small to full screen, but I need to make sure that it's initially rendered (via drawRect) full size so that it looks best at full size. The only way I can get the view to render itself full-size - without actually displaying it on the screen, which I don't want to do until the end of the animation - is to use one of the two methods in Brad's answer. – MusiGenesis Mar 25 '11 at 00:14
0

An Apple engineer provided an alternate recommended solution. Use 2 UIViews, one small and one large, with the larger one with an alpha of 0. Render both, the set the frame of the big one small, and have the animation cross fade as well as zoom both sizes from small to big.

hotpaw2
  • 70,107
  • 14
  • 90
  • 153
0

After calling setNeedsDisplay it may take a moment for the drawRect to get called. Start your animation with a short (0.1f) delay. You could also call the animation method from [self performSelector:withObject:afterDelay:] to add the delay if that's easier.

picciano
  • 22,341
  • 9
  • 69
  • 82
  • I actually tried this, but since the view begins the animation with an alpha of 0.0 it doesn't matter how long I wait before starting the animation - `drawRect` doesn't get called until the animation actually starts. It makes sense that the OS is basically saying "I'm not going to render this thing until I have to". – MusiGenesis Mar 24 '11 at 22:37