Creating a custom animation with CGLayers? - iphone

Basically, I want to animate a shrinking circle. I've already done this by drawing progressively smaller circles onto CGLayer's, and then using
if(index < 30){
[self performSelector:#selector(showNextLayer) withObject:nil afterDelay:(NSTimeInterval)0.02];
index++;
}
in my showNextLayer method. This works for my simple goal, but in general is this a more or less sensible way to draw a custom animation?
Apple has a lot of documentation on animation -enough to confuse me-, but as far as I -finally- understand:
The animation services provided by UIView is good for animating some basic view properties, view transitions etc.,
those provided by classes that inherit from CAAnimation, are good for animating properties like color change, 3d transformations etc.
UIViewImage is good for animating custom bitmap images (and it is not a good idea to create bitmap images when you can just draw circles into a CGLayer instead).
It seems to me that displaying the contents of a sequence of CGLayer 's would also be supported, am I missing something?
Thanks in advance for any clarification..

Related

Make Image Wrap Around Screen Edge? - iPhone Obj-C

I couldn't figure out a quick, concise way to explain what I'm trying to accomplish (which has made it difficult to find an answer searching Google), so I'll just try to elaborate more here. What I am trying to accomplish is moving a UIImageView to the top edge of the iPhone's screen and then keep moving it up, but once the image passes the top edge, have it appear at the bottom of the screen, as it continues moving through.
I've been using the basic [UIView commitAnimations] commands for the basic moving of objects in my application, so it'd be nice if this could be accomplished using using that (though it doesn't have to).
You don't need to mess around with scroll views or switch to cocos2d to implement simple animations. All you need to do is create 2 CALayer objects and then assign layer.contents to the same image.
CALayer *layer1 = [CALayer layer]
CALayer *layer2 = [CALayer layer]
UIImage *image = [UIImage imageNamed:#"whatever.png"];
layer1.contents = (id) image.CGImage;
layer2.contents = (id) image.CGImage;
Then use a normal animation to update the position of the two frames as described this Apple QA doc. You just need to determine the height of the window and then make sure that the start and end Y points of the 2 CALayers are always 1 height of difference. So, when layer1 animates to Y = -1 that means that layer2 would be at Height-1. Only when both layers are partially exposed would you see then both, that is how you get this effect to work and seem to be only 1 layer.
I think you could do this with a UIScrollView... here's something I wrote on cyclic scroll views a while ago.
I would begin by making the content size be the height of the window with the image view at one edge. As the image scrolls off the visible area the next page is coming into view with another image that looks the same but is not. I think this could save you a lot of frame calculations, nested animation blocks and other confusions. You could certainly do it with discrete views but I think it could be reinventing the wheel.
The best way to implement this is to create a CCNode subclass that will contain first and second sprite and will swap them if required. In this way all your logic will be very simple. You will just work with one CCNode (subclass) and will not think about swaping sprites - it will be done automatically by your class
#interface MyNode : CCNode
{
CCSprite *sprite1;
CCSprite *sprite2;
CCSprite *currentSprite;
bool isChanging; //indicates that now two sprites are visible
}
#end

Caching Quartz Rendered Drawing Into CALayer

I'm in the process of trying to tweak the process of a game which uses Quartz to draw a moderate number of sprites, around 50, during each game loop (40 fps).
At the moment I have a very simple drawing mechanism where a single game view (UIView) iterates over all the active sprites and asks each one to render itself to a CGContext (that of the view). This works fine, but performance is starting to drop with more than 50 active objects and I'd quite like to tweak things.
I've decided that holding a CALayer for each sprite which its cached drawing is the way to go - then using Core Animation to render/rotate/scale the drawing.
I'm struggling to understand how I achieve this. Where exactly do I initially draw to? I don't have a CGContext when my sprite is initialised. Is the right approach to render to a CGImage as a buffer then set it as the content on the CALayer?
Just subclass CALayer and do your drawing in drawInContext:. The layer will then cache its contents automatically. If you don't want to subclass CALayer, you can also assign it a delegate (which must not be a UIView) and implement drawLayer:inContext:.

Drawing class to allow layer control

I would like to implement a drawing class with the help of Quartz.
I want to be able to save parts of what is drawn on separate layers. I want these layers to be retrievable, so I can delete/hide/show layers on command.
Can I save multiple CGLayerRef as a NSMutableArray property of my class and then be able to retrieve them? If yes, can you point me to an example.
If there are any flaws in the "architecture" above, please point me to alternative solutions that could help me accomplish layered control over graphs.
Thank you.
CALayers that you create, can of course be stored in NSMutableArray, and you can work with them later on, usually by animating their properties, or asking them to redraw themselves.
Usually you create a custom UIView, create and manage layers within that view. Those layers are either member variables of that view, or you store them in an array. As things are happening in your app, your view animates the layers accordingly. Usually you want to react on touch events (which you also implement in that particular view - touchesBegan/Moved...) and animate the layers.
CALayer draws itself and caches the content for as long as you call [layer setNeedsDisplay], or it's bounds (size) are changed (well, if needsDisplayOnBoundsChange is true). Practically in all my apps I did, such redrawing happens very rarely - only if data are changed, and layer needs to redraw. Animating layers, transforming their size, rotation, changing position - layer is not redrawn during any of these. Hiding, showing, changing transparency - no redraw is required.
That "drawing class" you are talking about - you actually have only two options - either you extend CALayer and overwrite drawInContext:, or you create basic CALayer, set its delegate, and there you draw in drawLayer:inContext:. I personally prefer creating delegates.

CATiledLayer blanking tiles before drawing contents

All,
I'm having trouble getting behavior that I want from CATiledLayer. Is there a way that I can trigger the tiles to redraw without having the side-effect that their areas are cleared to white first? I've already subclassed CATiledLayer to set fadeDuration to return 0.
To be more specific, here are the details of what I'm seeing and what I'm trying to achieve:
I have a UIScrollView with a big content size...~12000x800. Its content view is a UIView backed by a CATiledLayer.
The UIView is rendered with a lot of custom-drawn lines
Everything works fine, but the contents of the UIView sometimes change. When that happens, I'd like to redraw the tiles as seamlessly as possible. When I use setNeedsDisplay on the view, the tiles redraw but they are first cleared to white and there's a fraction-of-a-second delay before the new content is drawn. I've already subclassed CATiledLayer so that fadeDuration is set to 0.
The behavior that I want seems like it should be possible...when you zoom in on the scrollview and the content gets redrawn at a higher resolution, there's no blanking before the redraw; the new content is drawn right on top of the old one. That's what I'm looking for.
Thanks; I appreciate your ideas.
Update:
Just to follow up - I realized that the tiles weren't being cleared to white before the redraw, they're being taken out entirely; the white that I was seeing is the color of the view that's beneath my CATiledLayer-backed view.
As a quick hack/fix, I put a UIImageView beneath the UIScrollView, and before triggering a redraw of the CATiledLayer-backed view I render its visible section into the UIImageView and let it show. This smooths out the redraw significantly.
If anyone has a better solution, like keeping the redraw-targeted tiles from going away before being redrawn in the first place, I'd still love to hear it.
I've found that if you set levelsOfDetailBias and levelsOfDetail both to the same value (2 in my case), then it only redraws the tiles that are touched by my setNeedsDisplayInRect: call, as you'd hope.
However if the levelsOfDetail is different to LODB, then any calls to setNeedsDisplayInRect: redraw all the tiles.
You could add another layer (possibly a CATiledLayer) behind the existing tiled layer. (Sort of a double-buffered solution.) You would call setNeedsDisplay: on the second layer from a timer that fires after a few seconds to ensure that that layer doesn't redraw at the same time as the front layer.
Another potential option is to use the same delegate to draw content to a bitmap context and swap the bitmap into the backing store once the content is refreshed. This should produce a flicker-free result. That being said, I can't tell you how this might be done, and one nice thing about CATiledLayers is they automatically generate tiles when you zoom and pregenerate tiles when you pan once zoomed in.
I would like to see how you implement your application. I have been looking for weeks to find an example that uses a combination of UIScrollView and a CATiledLayer-back view with a lot of custom drawn lines. Apple has some great sample code - but it all involves images rather than line art, so no help for me.
Having read through these answers without a solution, I discovered that tiling a page was the dominant background task.
Preparing my lo-res placeholder image on a high priority queue solved this issue - the images now appear while the tiling is occurring. Caching the placeholder images further improves their appearance - they appear before the tiling begins.
With newer devices, the tiling it so fast, these tricks might not matter. A sample PDF consisting of large scanned images (e.g. a scanned book) tiles the slowest in my experience, and makes for good test data.
I had the same problem with iPad.
The solution was more simple than I thought and far more simple than using UIImageView to render display before redrawing... :
Just don't set any background color for the Layer!
I had CATiledLayer set in a similar way:
layer = [[CATiledLayer alloc] init];
layer.masksToBounds = YES;
layer.contentsGravity = kCAGravityLeft;
//layer.backgroundColor = [[UIColor whiteColor] CGColor];
layer.tileSize = CGSizeMake(1004.0, 1004.0);
layer.levelsOfDetail = 16;
layer.levelsOfDetailBias = 8;
Note that I have commented out the line setting layer's background color to white.
After that the white blank before redraw problem disappeared!
Let me know if anyone has tried that.

Change the z-order of subviews on the iPhone

I'm developing a game. I'm using about 150 UIImageView to hold the graphics. I'm simulating a 3D enviroment, so i would like to change the z-order (how close is an object to the camera).
I know there exists :
[superWindow exchangeSubviewAtIndex:i withSubviewAtIndex:j];
But for some reason it's not working, some of the subviews disappear an re-appear.
Now I just remove all subviews and add it again in the correct z-order, this is ok with 50 subviews (on a 2G iphone) but with 120 it takes half a second so the gameplay sucks (I dont have and 3GS so i didn't try there).
I'm using so many subviews because each one is a square, then i colored it, resize it and move it somewhere in the screen. I'm holding the subviews under a NSMutableArray...
The iPhone documentation often warns about multiple UIViews nested, as it has a large performance hit after a certain point. If you start getting into issues one option is to render your UIViews into an image and using that to lower the number of on-screen views, but if you're simulating 3D that probably isn't going to help much since your composite view would need re-rendering too often.
The iPhone has full support for OpenGL ES, which allows for a real 3D environment. Take a look at some of the samples and/or a good book on the topic and you'll find that it's much easier to simply use OpenGL.
Z-order is an artifact of where your view appears in your superview's list of subviews. The last subview added to the superview is "on top".
So, to move a view to the be "on top", you remove it from its superview, then re-add it -- making it the last one and, therefore, on top.
Here, a view moves himself to the top of the z-order.
// move to the to in the z-order
UIView* superview = [self superview];
[self retain];
[self removeFromSuperview];
[superview addSubview:self];
[self release];
Note the [self retain] to prevent your dealloc if your superview is the only reference to you.
If you don't want to take the plunge to OpenGL ES, as Timothy suggests, you might want to look at replacing the UIViews with CALayers and placing them within a CATransformLayer. CATransformLayer is new in iPhone OS 3.0, and it lets you do a true 3-D layout of the CALayers. You can then set the zPosition property on your CALayers to locate them in the Z plane of the CATransformLayer. For more on 3-D layout of CALayers, I direct you to this article.
You can even add a nice perspective effect on the overall CATransformLayer by setting the m34 element in the CATransform3D applied to that layer.
Changing from UIViews to CALayers is pretty straightforward, because their structures have much in common.
Consider using a 2D game library like Cocos2D. I'm pretty sure that supports Z-ordering, and the performance will be vastly better than using UIViews.
I know this is old, but you can change the z order with bringSubViewToFront.
See this answer: How to set iPhone UI View z index?