How to animate stuff when using -drawRect:? - iphone

I didn't use subviews but painted my things with -drawRect: inside an UIView subclass. Now I want to do some animations in there.
I guess that I can't count on core animation now since I have no subviews. So how would I animate then? Would I set up a timer which fires like 30 times per second? How would I know the animation step? Would I make an ivar which counts the frame of the animation so that I can do my stuff in -drawRect as it gets called?

You can do exactly that: have an instance variable with the frame number and increment it in the timer. If you don't care about easing, you can then just multiply the frame number by a speed and then set that to the property which you want to animate.
-(void)timerfunction{
++frame;
[self setNeedsDisplay];
}
-(void)drawRect:(CGRect)rect{
CGContextFillRect(UIGraphicsGetCurrentContext(),CGRectMake((2*frame)+5,5,20,20));
}

This works but sounds like crazy.YOu draw the same content hunderts of times and it could cause performance issues in case of TableView.I would refer to CALayer animations

Related

How can I move a UIView, but leave its content positions unchanged relative to the superview?

A UIView has 2 images as sub-views, and clip to bounds set to yes.
I want to move the UIView in an animation block, but have the sub-views not move with the parent. If the view was to move 50 points to the right, more of the second image would be seen, and less of the first.
I can move the view manually, and update the subview positions opposite the new view position, but I can't figure out how to achieve the same thing in a block.
I think the limitation you're running into has to do with the fact that UIView Animation blocks use Core Animation under the hood, and Core Animation animates a 'presentation' layer which is a copy of the original view/layer that the animation was scheduled on. So during your block's animation, the presentation layer's position is changing, but the position of the model view remains unchanged.
Since you mentioned that you can update the view position manually (and subview positions in the opposite direction) to achieve the desired effect, consider creating an NSTimer at animation schedule time that runs 60 times per second (to try and catch every animation frame) that simply queries the presentation layer for its current position (as set by the animation itself + any special timing functions specified as argument to the animation), and perform your work to update the underlying model to match that.
You should be able to query the animated layer's position with:
yourUIView.layer.presentationLayer.frame.origin
Then add a completion block to your animation that invalidates the timer.
Before you can access the view's layer property, you must add to your project the
QuartzCore.framework
Cheers,

CAKeyFrameAnimation not repeat

Hi I have a CAKeyFrameAnimation scale, that makes an object fade down from full size (1) to near nothing (0.01)
Then this is called:
- (void)animationDidStop:(CAKeyframeAnimation *)anim finished:(BOOL)flag
{
[self setHidden:YES];
}
It seems to hide the object, but not before making it re-appear again as full sized for a split second, which ruins the fade to small/nothing effect :P
How do I stop the animation from going back to frame 1 for the "animationDidStop" is called? Thanks!
I believe you need to set the fillMode property of your animations to kCAFillModeForwards. That should freeze the animations at their end time.
Another suggestion (and honestly, this is what I'd usually do) is just set the properties of the layer itself to their final position after you've set up the animation. That way when the animation is removed, the layer will still have the final properties as part of its model.
You can set .scale object property instead of CAKeyframeAnimation.
You can disable animations and set target scale after creating CAKeyframeAnimation.
You can set scale (0.1) in animationDidStop method (do not sure if it will work)
The point is to set target properties of the object. It jumps to them after finishing the animation.

Avoiding UIView to create very large drawing canvas

I derived a class from UIView, only to realize that there are real limitations on its size due to memory. I have this UIView inside UIScrollView.
Is there a way for me to put something inside a scroll view that is not a UIView-derived class but into which I can still draw, and which can be very very large?
I don't mind having to respond to expose-rectangle events, like one does when using conventional windowing systems.
Thanks.
The things inside of a UIScrollView must be UIViews, which are size-restricted for memory reasons. UIView maintains a bitmapped backing store for performance reasons, so it has to allocate memory proportional to its size.
The usual way that you handle this is to generate several UIViews and swap them out as the user scrolls around. The other version of that is to use CATiledLayer. Neither of those give you the "giant canvas" drawing model, though. It's up to you to break things up and draw them as needed. This is the usual approach, though.
If you really want a giant canvas, my recommendation would be a CGPDFContext. There is rich existing support for these, particularly using UIWebView (remember, you can open data: URIs to avoid reading files from disk). And you can draw parts of them directly by applying affine transforms and then CGContextDrawPDFPage. CGBitmapContext is another approach, but it could require a lot more memory for a small amount of drawing.
So you have a UIView inside a UIScrollView, but you want your UIView to have very large bounds (i.e., so it matches the size of your UIScrollView's contentSize). But you don't want to draw the entire UIView every time it needs displaying, nor can you fit its entire contents in memory at once.
Make your UIView uses a CAScrollLayer backing, as follows:
// MyCustomUIView.m
+ (Class) layerClass
{
return [CAScrollLayer class];
}
Add a method to update the scroll position when the user scrolls the UIScrollView containing your UIView:
// MyCustomUIView.m
- (void) setScrollOffset:(CGPoint)scrollOffset
{
CAScrollLayer *scrollLayer = (CAScrollLayer*)self.layer;
[scrollLayer scrollToPoint:scrollOffset];
}
Ensure that when you draw your UIView, you only draw the portions contained in the CGRect provided to you:
- (void)drawRect:(CGRect)rect
{
// Only draw stuff that lies inside 'rect'
// CGRectIntersection might be handy here!
}
Now, in your UIScrollViewDelegate, you'll need to notify your CAScrollLayer backed view when the parent UIScrollView updates:
// SomeUIScrollViewDelegate.m
- (void) scrollViewDidScroll:(UIScrollView *)scrollView
{
// Offset myCustomView within the scrollview so that it is always visible
myCustomView.frame = CGRectMake(scrollView.contentOffset.x,
scrollView.contentOffset.y,
scrollView.bounds.size.width,
scrollView.bounds.size.height);
// "Scroll" myCustomView so that the correct portion is rendered
[myCustomView setScrollOffset:self.contentOffset];
// Tell it to update its display
[myCustomView setNeedsDisplay];
}
You can also use CATiledLayer, which is easier because you do not have to track the scroll position — instead your drawRect method will be called with each tile as-needed. However this will cause your view to fade in slowly. It might be desirable if you intend to cache parts of your view and don't mind the slow updates.

How to keep the previous drawing when using Quartz on the iPhone?

I want to draw a simple line on the iPhone by touching and dragging across the screen. I managed to do that by subclassing UIView and change the default drawRect: method. At the same time in my viewcontroller I detect the touch event and call the [myView setNeedsDisplay] when necessary. The problem is that when I try to draw the second line the previous line disappears. Is there a way to keep the previous line on the screen?
Any input will be very much appreciated.
The usual method is to use CGBitmapContextCreate(). Create it in -init/-init-WithFrame:/etc and call CGContextRelease() in -dealloc. I'm not sure how you handle the 2x scale of the "retina display" with CGBitmapContextCreate().
(UIGraphicsBeginImageContext() is easier, but it might not be safe to do UIGraphicsBeginImageContext(); myContext = CFRetain(UIGraphicsGetCurrentContext()); UIGraphicsEndImageContext();.)
Then do something like
#import <QuartzCore/CALayer.h>
-(void)displayLayer:(CALayer*)layer
{
UIGraphicsPushContext(mycontext);
... Update the bitmap context by drawing a line...
UIGraphicsPopContext();
CGImageRef cgImage = CGBitmapContextCreateImage(mycontext);
layer.contents = (id)cgImage;
CFRelease(cgImage);
}
I've used -displayLayer: (a CALayer delegate function; a UIView is its layer's delegate) instead of -drawRect: for efficiency: if you use -drawRect:, CALayer creates a second context (and thus a second copy of the bitmap).
Alternatively, you might have luck with CGLayer. I've never seen a reason to use it instead of a bitmap context; it might be more efficient in some cases.
Alternatively, you might get away with setting self.clearsContextBeforeDrawing = NO, but this is very likely to break. UIKit (or, more accurately, CoreAnimation) expects you to draw the whole view contained in the clip rect (that's the "rect" argument of -drawRect:; it's not guaranteed to be the bounds). If your view goes offscreen, CoreAnimation might decide that it wants the memory back. Or CoreAnimation might only draw the part of the view that's on-screen. Or CoreAnimation might do double-buffered drawing, causing your view to appear to flip between two states.
If you use drawRect: to draw, then you need to draw the whole area. So you need to store not only the data for the latest part but everything.
As an alternative, you might draw directly into a bitmap, or generate dynamically subviews for your lines (makes only sense for very limited drawing (i.e. some few vector-based stuff).

Immediate manipulation of UIView animatable properties during rotation?

I am trying to accomplish some complex effects during my UIView rotation, and I'm using the first half/second half method of rotation animations.
However, when my second half starts, I'd like to set some properties on my subviews (alpha, frame) etc, from which to begin animating in the second half. But setting any of these properties of course causes them to be animated. I'd like to say, set the alpha to 0.0, and THEN say, "ok, now animate it to 1.0 throughout the rest of the rotation."
Note that I can't set this property before the whole rotation; I want to affect its immediate value partway through.
Can this be done?
Thanks.
Implement this function in your UIControllerView :
- (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration