For fun I have been trying to get a lottery wheel type object set up using Xcode in objective c. So far I have been able to successfully spin the object with random rotations, and stopping at 1 of 8 items on the wheel.
The problem is, I am not sure how to "save" the animation layer that is spun. Each time I spin the wheel, once the animation is completed it resets and shows the image in the original orientation. Below is the code I have setup for spinning the wheel. (NOTE: in this code I merely just have a set rotation. this is the template I am working with to try and retain the image. The other code is in a different project that stores the stopping point and corrects it to the center of on of those objects.)
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#define SPIN_CLOCK_WISE 1
#define SPIN_COUNTERCLOCK_WISE -1
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (IBAction)spinButton:(id)sender {
[self spinLayer:_spinImage.layer duration:10 direction:SPIN_CLOCK_WISE];
}
- (void)spinLayer:(CALayer *)inLayer duration:(CFTimeInterval)inDuration
direction:(int)direction
{
CABasicAnimation* rotationAnimation;
// Rotate about the z axis
rotationAnimation =
[CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
// Rotate 360 degress, in direction specified
rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 22.3 * direction];
// Perform the rotation over this many seconds
rotationAnimation.duration = inDuration;
// Set the pacing of the animation
rotationAnimation.timingFunction =
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
// Add animation to the layer and make it so
[inLayer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
// Save animation
//%%%%%%%%%% THIS IS WHERE I AM STUCK!! %%%%%%%%%
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
If someone could just help me retain the spun image so that once the spinLayer method completes, the image stays rotated to the new spin orientation, I would greatly appreciate it. Thanks!
The confusing part is that the animation is only changing the layers appearance, not the properties of the layer.
You can think of the animation as overriding the property when it's being rendered.
As you can see, since the model value never changed, when the animation has finished and removes itself, the layer goes back to rendering the actual value of that property.
The wrong way to solve this problem would be to keep the animation around forever. It does produce the correct rendering, but has some unwanted side effects since the model value now "permanently" doesn't match the appearance.
Instead, the better solution is to update the value of the property you are animating as you add the animation to the layer. This might feel like it would break the animation, but if the animation is animating from the old value to the new value, it will "override" whatever the model value is why rendered and when the animation removes the end value will match the model value.
For an more in-depth discussion about where to change the model value when adding the animation and about the way animations are applied, see this question and this blog post of mine.
Related
I have a UIView. I applied the animation to its CALayer.
[view.layer addAnimation:groupAnimation forKey:name];
I want the final state of the layer to be the state of the UIView after the animation. Let's say I rotated by 45degrees and moved to a new position using the layer; is it possible for my view to be in that state after the animation? Because right now, after the animation, it goes back to the original state of the UIView. I hope to receive some help with this. Thanks.
The thing to understand is that layer animation is just animation; it's merely a kind of temporary "movie" covering the screen. When the animation ends, the "movie" is removed, thus revealing the true situation. It is up to you to match that situation with the final frame of the movie.
UIView animation does this for you, to a great extent. But layer animation leaves it entirely up to you.
Thus, let's say you animate a position change; doing something so that the layer or view actually is where you animated to is completely up to you.
The usual thing is to perform the changes yourself as a separate set of commands. But be careful not to do anything that might trigger implicit layer animation, as this will conflict with the animation you are trying to perform explicitly.
Here's example code (not related to yours, but it does show the general form):
CAAnimationGroup* group = // ...
// ... configure the animation ...
[view.layer addAnimation:group forKey:nil];
// now we are ready to set up the view to look like the final "frame" of the animation
[CATransaction setDisableActions:YES]; // do not trigger implicit animation by mistake
view.layer.position = finalPosition; // assume we have worked this out
When animating a CALayer or using a CAAnimationGroup, the following properties must be set, e.g.:
groupAnimation.removedOnCompletion = NO;
groupAnimation.fillMode = kCAFillModeForwards;
See also: After Animation, View position resets
Also note that it may be helpful to apply animations directly to the view itself, rather than by accessing the view's layer. This is accomplished using animation blocks, which I have found to be very useful.
Block style animations can be customized in many ways, but here's a basic example, which could be invoked within a function when your view needs to animate:
- (void) animateMyView
{
CGRect newViewFrame = CGRectMake(x,y,w,h);
[UIView animateWithDuration:0.5
delay:0
options: (UIViewAnimationOptionCurveLinear )
animations:^{
self.myView = CGAffineTransformMakeRotation (45);
[self.myView setBounds:newViewFrame];
}
completion:NULL];
}
For more information, see Apple's documentation on View Animation.
I am working on a small application on Mac that I need to create customed cursor and move it. I used NSImageView to implement it. However when I call setFrameOrigin (the same to setFrame) it will leaves images on the previous place.
Here is my code:
#property (nonatomic, strong) NSImageView *eraserView;
this is the define
_eraserView = [[NSImageView alloc] initWithFrame:CGRectMake(100, 100, 32, 32)];
_eraserView.image = [NSImage imageNamed:#"EraserCursor"];
[self.view addSubview:_eraserView];
[_eraserView setHidden:YES];
here is the initialization. Everything goes well until now but:
- (void)setImageatPoint:(NSPoint)point
{
[_eraserView setFrameOrigin:point];
}
- (void)hidePenImage
{
[_eraserView setHidden:YES];
}
- (void)unhidePenImage: (BOOL)isEraser
{
[_eraserView setHidden:NO];
}
These are methods I use to change the state of the NSImageView. They will be called by another class using delegate when corresponding events of trackpad occurs.
However every time I change the state of the NSImageView, it seems like it is drawn on the superview.
I debugged it and found there was no extra subviews. And when I use setHidden it has no effect on those tracks. I think it somehow did something to the CALayer, but I have no idea how to fix it.
Screenshots would help but in general if you move a view or change the area of the view that is drawn, you need to redraw.
To do this it kind of depends on how your drawing happens. Calling setNeedsDisplay may not be enough if your implementation of drawRect only draws a sub rect of the view bounds. Cocoa only draws what it is told to draw.
You can erase sections of the view that should be empty by drawing (filling) where it should be empty. That means drawing a color ( NSColor clearColor if nothing else) in the area that was previously drawn.
I want this to be a background in my app for Ipad.
I'm building everything in objective C (native app)
I need a little help figuring out how to animate each of the triangles over the image (overlay) so it fades in and out independently of each other, the goal to make a constant shimmer like effect so the image doesn't feel sos tatic. Do i have to animate each triangle independently? Is there any algorithm that i should be looking at so it seems kinda random but isn't.
Here is the background image
I need some guidance on where to start and how to approach this problem, and feedback would be appreciated.
a) My advice is to use UIViewAnimationWithBlocks introduced in iOS 4. If you have a solid grasp on blocks, they can be very useful. Here's an example I created in as little as 5 minutes to illustrate:
typedef void(^FadeInOutBlock)(void);
#interface PMViewController ()
#property (nonatomic, copy) FadeInOutBlock fadeInOutBlock;
#end
Here we declare a typedef to save us from doing the block syntax all over again. We also create a property to hold the animation block.
#implementation PMViewController
#synthesize myView;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
__block PMViewController *_self = self;
self.fadeInOutBlock = ^{
[UIView animateWithDuration:0.5f animations:^{
// fade out effect
_self.myView.alpha = 0.0f;
} completion:^(BOOL success){
[UIView animateWithDuration:0.5f animations:^{
// fade in effect
_self.myView.alpha = 1.0f;
} completion:^(BOOL success){
// recursively fire a new animation
if (_self.fadeInOutBlock)
_self.fadeInOutBlock();
}];
}];
};
}
We create the animation, within an animation. You start off with the fade out, where myView's alpha will be reduced to 0.0f in 0.5f seconds. After it's completed, a second animation will be fired, restoring the alpha for myView back to 1.0f and finally, firing out the first animation, again. (Animception)
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (self.fadeInOutBlock)
self.fadeInOutBlock();
}
#end
And finally, in view did appear you fire it off for the first time.
b)
Now, for the shimmering animation that you mention, I suggest you separate each triangle into it's own UIView and use the technique above, using different durations and alphas.
If you have to many small UIViews, group them up into a bigger UIView (by using the addSubview method) and apply the animation to those 'container' UIViews.
For instance, you could have four separate UIView containers, that have a bunch of separate UIViews as their children. You could then create four block animations, one for each container, and then apply the animation to them. I bet experimenting with that, you would be able to create pretty good effects.
You can do this without any special librarys. So you need to create a black UIView with an alpha of 0. Then create an NSTimer that increases the alpha of the UIView.
I have multiple animate-able/interactive UIImageViews on my UIView which would animate/interact based on user touches. I am trying to add a feature which would "wiggle" these animate-able/interactive UIImageViews on touch of an Icon which would help user in identifying which objects are interactive on screen. I am trying to use following code to achieve "wiggle" movement from this blog:
-(void)doWiggle:(UIView *)touchView {
// grabbing the layer of the tocuhed view.
CALayer *touchedLayer = [touchView layer];
// here is an example wiggle
CABasicAnimation *wiggle = [CABasicAnimation animationWithKeyPath:#"transform"];
wiggle.duration = 0.1;
wiggle.repeatCount = 1e100f;
wiggle.autoreverses = YES;
wiggle.toValue = [NSValue valueWithCATransform3D:CATransform3DRotate(touchedLayer.transform,0.1, 0.0 ,1.0 ,1.0)];
// doing the wiggle
[touchedLayer addAnimation:wiggle forKey:#"wiggle"];
// setting a timer to remove the layer
[NSTimer scheduledTimerWithTimeInterval: 2.0 target:self selector:#selector(endWiggle:) userInfo:touchedLayer repeats:NO];
}
-(void)endWiggle:(NSTimer*)wiggleTimer {
// stopping the wiggle now
[((CALayer*)wiggleTimer.userInfo) removeAllAnimations];
}
I call it like this in my routine :[self doWiggle:imageAnimation]; where imageAnimation is an UIImageView. I see that while wiggling, half of the image is disappearing. I read somewhere that I need to set the z-index properly in order for this to work. What do I need to set for z-index? and I have 3 to 4 UIImageViews per page where I need to call doWiggle: routine, do I need to fix z-indexes of all those UIImageViews?
Before you call doWiggle, you may need to do this code:
[[touchView superview] bringSubviewToFront:touchView];
If you can see the entire view before doing the animation then this will probably not fix your problem. It is possible that your image is being scaled by the image view and that is causing a problem during the animation. You may need to rescale your image, with an image editing program, to fit inside the view without having the view scale it. You can also do this programmatically, if you don't have a static image (i.e. loading off the network, etc).
I have an application where, in one window, there is an NSImageView. The user should be able to drag and drop ANY FILE/FOLDER (not only images) into the image view, so I subclassed NSImageView class to add support for those types.
The reason why I chose an NSImageView instead of a normal view is because I also wanted to display an animation (say an arrow pointing downwards and going up and down) when the user hovers over with files ready to drop. My question is this: what would be the best way (most efficient, quickest, least CPU usage, etc) to do this?
In fact, I have already done it, but what made me ask this question is the fact that when I set the images to change at a rate below 0.02 sec it starts to lag. Here is how I did it:
In the NSImageView subclass:
have an ivar: NSTimer* animTimer;
override awakeFromNib, calling [super awakeFromNib] and loading the images into an array (about 45 images) using NSImage
whenever user enters with files, start animTimer with frequency = 0.025 (less and it lags), and a selector that sets the next image in the array (called drawNextImage)
whenever the user exits or ends the drag and drop, call [animTimer invalidate] to stop updating images
Here is how I set the image in the subclass:
- (void)drawNextImage
{
currentImageIndex++; // ivar / kNumberDNDImages is a constant defined as 46
if (currentImageIndex >= kNumberDNDImages) { currentImageIndex = 0;}
[super setImage: [imagesArray objectAtIndex: currentImageIndex]]; // imagesArray is ivar
}
So, how would I do this quick enough? I'd like the frequency to be about 0.01 secs, but less than 0.025 lags, so that is what I have set for the moment. Oh, and my images are the correct size (+ or - one pixel or something) and they are in .png (I need the transparency - jpegs, for example, won't do it).
EDIT:
I have tried to follow NSResponder's suggestion, and have updated my method to this:
- (void)drawNextImage
{
currentImageIndex++;
if (currentImageIndex >= kNumberDNDImages) { currentImageIndex = 0;}
NSRect smallImgRect;
smallImgRect.origin = NSMakePoint(kSmallImageWidth * currentImageIndex, [self.bigDNDImage size].height); // Up left corner - ??
smallImgRect.size = NSMakeSize(kSmallImageWidth, [self.bigDNDImage size].height);
// Bottom left corner - ??
NSPoint imgPoint = NSMakePoint(([self bounds].size.width - kSmallImageWidth) / 2, 0);
[bigDNDImage drawAtPoint: imgPoint fromRect: smallImgRect operation: NSCompositeCopy fraction: 1];
}
I have also moved this method and the other drag and drop methods from the NSImageView subclass to an NSView subclass I already had. Everything is exactly the same, except for the superclass and this method. I also modified some constants.
In my early testing of this, I got some error/warning messages that didn't stop execution talking abou NSGraphicsContext or something. These have disappeared now, but just so you know. I have absolutely no idea why they were appearing and what they mean. If they ever appear again I'll worry about them, not now :)
EDIT 2:
This is what I'm doing now:
- (void)drawNextImage
{
currentImageIndex++;
if (currentImageIndex >= kNumberDNDImages) { currentImageIndex = 0;}
[self drawCurrentImage];
}
- (void)drawCurrentImage
{
NSRect smallImgRect;
smallImgRect.origin = NSMakePoint(kSmallImageWidth * currentImageIndex, 0); // Bottom left, for sure
smallImgRect.size = NSMakeSize(kSmallImageWidth, [self.bigDNDImage size].height);
// Bottom left as well
NSPoint imgPoint = NSMakePoint(([self bounds].size.width - kSmallImageWidth) / 2, 0);
[bigDNDImage drawAtPoint: imgPoint fromRect: smallImgRect operation: NSCompositeCopy fraction: 1];
}
And the catch here is to call drawCurrentImage when drawRect is called (see, it actually was easier to solve than I thought).
Now, I must say I haven't tried this with the composite image, because I couldn't find a good and quick way to merge 40+ images the way I wanted (one next to the other). But for the ones ineterested, I modified this to do the same thing as my NSImageView subclass (reading 40+ images from an array and displaying them) and I found no speed bump: NSView is as laggy below 0.025 as NSImageView. Also I found some problems when using core animation (the image is drawn in weird places instead of the place I tell her to) and some warnings talking about NSGraphicsContext, which I don't know how to solve at all (I'm a complete noob when it comes to drawing and such with Objective-C's tools). So for the time being I'm using NSImageView, unless I find a way to merge all those images and try it with NSView.
Core Animation would probably be quickest, since it'll do everything on the GPU. Create a layer for each image, setting each layer's contents to the CGImage you can make from each image, add them all as sublayers of a single top-level layer, host the top-level layer in a plain NSView, and then just toggle each image layer's hidden property in turn.
I'd probably draw all of the component images into one long image, and draw segments into a view using -drawAtPoint:fromRect:operation:fraction:. I'm sure you could make it faster than that by resorting to OpenGL, though.