i'd have to create some animated shapes in a uiview in my iphone app (with xcode), like these ones (circle , rectangle)
The first one should be a circle increasing and decreasing its radius, from 0 to x and viceversa; the second one is a rectangle increasing its length from 0 to x, and viceversa. The shapes should be animated infinitely... until the user taps some button to stop the animations at some point. How can i achieve those particular draws and animations? What's the best way to achieve that? And how to stop them?
Many thanks for any advice.
In my case I had to animate the movement of subviews of my UIView object and I accomplished it like this: break up the continuous animation into logical parts, animate a part and configure this animation to start another animation after it is done animating. Below you see my code as is (what it does is move subviews that are placed on a circle along that circle over a given angle).
- (void) animatePart: (NSString*) animationId finished: (NSNumber*) finished context: (void *) context
{
// the context contains an NSNumber which is the angle remaining to turn the pieces
CGFloat displacement = [trackSet currentDisplacementOnTrack:drag.trackIndex];
NSMutableDictionary* contextDict = (NSMutableDictionary*) context;
Move * move = (Move *) [contextDict objectForKey:ANIMCTX_MOVE];
//NSNumber *direction = (NSNumber *) [contextDict objectForKey:ANIMCTX_DIRECTION];
printf("> EVC animatePart: displ: %3.3f ", displacement);
if (FABS(displacement) < 0.1) {
printf(", ending animation\n");
// this is the last piece of animation
[trackSet completeMoveOnTrack:move.trackRef];
[self updatePiecesAnimatedFromModel];
self.animationContext = nil;
}
else {
CGFloat dir = displacement == 0 ? 0 : displacement / FABS(displacement);
CGFloat delta = -ANIM_DELTASTATION * dir;
printf(" EVC animatePart: delta: %3.3f\n", delta);
CGFloat newDelta = FABS(delta) < FABS(displacement) ? delta : -displacement;
[trackSet registerMoveOnTrack:drag.trackIndex by:newDelta];
[UIView beginAnimations:animationId context:context];
[UIView setAnimationDuration:0.1];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animatePart:finished:context:)];
[self updatePiecesFromTrack:drag.trackIndex];
[UIView commitAnimations];
}
printf("< EVC animatePart\n");
}
As you can see, the UIView methods setDelegate: and animationDidStopSelector: are used to configure this animation to call a certain method after it's done animating. The selector passed here is that of this same method. This sounds like a recursive method (it even has a stop condition, just like recursive methods normally do), but it's not. So no worries about stack overflows ;-)
For completeness, to kickstart this animation I use the method below (copied verbatim from my code).
- (void) animateMove: (Move *) move direction: (int) direction
{
printf("> EVC animateMove: %d\n", direction * move.stations);
NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithObject:move forKey:ANIMCTX_MOVE];
[dict setObject:[NSNumber numberWithInt: direction] forKey:ANIMCTX_DIRECTION];
self.animationContext = dict;
//[trackSet openMoveOnTrack:move.trackRef];
// the move has already been carried out in the model, but is not yet reflected in the UI
// we don't need to call trackSet's openMoveOnTrack and closeMoveOnTrack
// for the animation we initially set the track's current displacement to that of the move
// then in a series of animated steps we decrement that displacement gradually to zero,
// while showing the intermediate results in the UI.
CGFloat moveDisplacement = (CGFloat) (move.stations);
[trackSet registerMoveOnTrack:move.trackRef by:moveDisplacement];
[self animatePart:#"displacementAnimation" finished:[NSNumber numberWithInt:0] context:dict];
printf("< EVC animateMove: \n");
}
Related
Can someone please tell me why this doesn't work?:
//Changing the Images
- (void) squareOneColour {
NSUInteger r = arc4random_uniform(5);
[self.squareOne setBackgroundImage:[self.colorArray objectAtIndex:r] forState:UIControlStateNormal];
}
//Moving buttons
- (void) squareOneMover {
NSUInteger r = arc4random_uniform(3);
[self.squareOne setHidden:NO];
CGPoint originalPosition = self.squareOne.center;
originalPosition.y = -55;
[self.squareOne setCenter:originalPosition];
CGPoint position = self.squareOne.center;
position.y += 760;
[UIView animateWithDuration:r + 3
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
[self.squareOne setCenter:position];
}
completion:^(BOOL complete) {
if (complete) {
[self squareOneColour];
[self squareOneMover];
}
}
];
}
I am trying to get a UIButton to animate down the screen and when the animation is finished, for it to repeat but with a different background. The code above seems it should work but the second animation is never completed. (The UIButton is animated once, then everything stops). I have tried putting the code shown below (into the completion block), with an outside NSUInteger method creating a random number for r but it never works. But it does work when I replace r with a different number, like 1 or 2.
I found the answer. After playing around with it, I found that the function I wanted the UIButton to do was only fit for a UIImageView. I guess I'll have to make an invisible button over the UIImageView
You're stepping on your own feet. Step out to the next cycle of the runloop and all will be well:
[self squareOneColour];
dispatch_async(dispatch_get_main_queue(), ^{
[self squareOneMover];
});
I am new to Objective-C but I have made some progress with my app through the vast amount of information online. My objective is to press a record button, then move an icon representing a ball across a view. I will capture the touches in an array then replay the movement of the ball by animating the touches by stepping through the coordinates in the array on clicking a play button.
The code I am testing is attempting to step through the array and animate each set of co-ordinates in turn. Only the last animation happens. I guess my whole approach might be incorrect. Please offer me some help and thanks for your time.
NSMutableArray *yourCGPointsArray = [[NSMutableArray alloc] init];
[yourCGPointsArray addObject:[NSValue valueWithCGPoint:CGPointMake(300, 001)]];
[yourCGPointsArray addObject:[NSValue valueWithCGPoint:CGPointMake(300, 300)]];
[yourCGPointsArray addObject:[NSValue valueWithCGPoint:CGPointMake(001, 300)]];
int i;
i=0;
while (i < 3) {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationDelay:1.0];
CGPoint point = [[yourCGPointsArray objectAtIndex:i] CGPointValue];
player3.center = CGPointMake(point.x , point.y);
[UIView commitAnimations];
NSLog (#"i array %d", i);
NSLog (#"cgpoint x %f", point.x);
NSLog (#"cgpoint y %f", point.y);
i = i + 1;
}
}
Yes, your approach is incorrect. Right now, your code fires off three animations at essentially the same time -- the while loop won't wait for one animation to complete before starting the next. Instead, you should start one animation, wait for it to complete, start the next, wait for it to complete, and so on. You can do this by using a completion block with each animation to start the next animation. Take a look at +animateWithDuration:animations:completion:, for example. It allows you to specify the completion block for the animation.
I have an array of UIViews which I animate with the following code:
for (int i = 0; i<(int)viewArray.count; i++) {
UIView *view = [viewArray objectAtIndex:i];
CALayer *layer = view.layer;
//animate the layer
}
My question is, is there any way to have a delay between every animation, so that, for example, it animates one UIView in the array and the next animation starts after 0.2 seconds or so? Any help would be greatly appreciated!
You may want to try the following
float timeDelay = 0.2
float duration = YOUR_DURATION
for (int i = 0; i<(int)viewArray.count; i++) {
UIView *view = [viewArray objectAtIndex:i];
//animate the layer
[UIView animateWithDuration:duration delay:(i+1)*timeDelay
options: {Check UIView Class Reference on developer.apple.com}
animations:^{
[view.layer fantastic animation here];
} completion^(BOOL finised){
if(finished){
//Leave blank if nothing, good practice to implement debuging here or post animation processes here (like removing a subview from a super)
}
}];
}
Keep in mind that if u send your program the the background it may break your animations so make sure u recall this method in ur app Delegates:
- (void)applicationWillEnterForeground:(UIApplication *)application;
Hope it helps
Use this guy:
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
use the second parameter. Inside your loop, call that function, passing an every-increasing number to that parameter.
i have this type of code....
// create a UIImageView
UIImageView *rollDiceImageMainTemp = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"rollDiceAnimationImage1.png"]];
// position and size the UIImageView
rollDiceImageMainTemp.frame = CGRectMake(0, 0, 100, 100);
// create an array of images that will represent your animation (in this case the array contains 2 images but you will want more)
NSArray *savingHighScoreAnimationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"rollDiceAnimationImage1.png"],
[UIImage imageNamed:#"rollDiceAnimationImage2.png"],
nil];
// set the new UIImageView to a property in your view controller
self.viewController.rollDiceImage = rollDiceImageMainTemp;
// release the UIImageView that you created with alloc and init to avoid memory leak
[rollDiceImageMainTemp release];
// set the animation images and duration, and repeat count on your UIImageView
[self.viewController.rollDiceImageMain setAnimationImages:savingHighScoreAnimationImages];
[self.viewController.rollDiceImageMain setAnimationDuration:2.0];
[self.viewController.rollDiceImageMain.animationRepeatCount:3];
// start the animation
[self.viewController.rollDiceImageMain startAnimating];
// show the new UIImageView
[self.viewController.view addSubview:self.rollDiceImageMain];
Instead of startAnimation directly..there any way to control this code using touchesMoved??
Actually, what you want is the - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event. Below is a sample of code that will allow a UIView to move on the x-axis only. Adapt to whatever you need your code to do.
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
CGPoint original = self.center;
UITouch* touch = [touches anyObject];
CGPoint location = [touch locationInView:self.superview];
// Taking the delta will give us a nice, smooth movement that will
// track with the touch.
float delta = location.x - original.x;
// We need to substract half of the width of the view
// because we are using the view's center to reposition it.
float maxPos = self.superview.bounds.size.width
- (self.frame.size.width * 0.5f);
float minPos = self.frame.size.width * 0.5f;
float intendedPos = delta + original.x;
// Make sure they can't move the view off-screen
if (intendedPos > maxPos)
{
intendedPos = maxPos;
}
// Make sure they can't move the view off-screen
if (intendedPos < minPos)
{
intendedPos = minPos;
}
self.center = CGPointMake(intendedPos, original.y);
// We want to cancel all other touches for the view
// because we don't want the touchInside event firing.
[self touchesCancelled:touches withEvent:event];
// Pass on the touches to the super
[super touchesMoved:touches withEvent:event];
}
Notice here that I am taking the delta of the movement and not with the finger. If you track with the finger you will get very erratic behavior that is very undesirable. Applying the delta will give nice, fluid movement of the view that will track perfectly with the touch input.
Update: Also, for those wondering why I chose to multiply by 0.5f rather than divide by 2, the ARM processor doesn't support division in hardware, so there is a minuscule performance bump by going with multiplication. This performance optimization may only get called only a few times during the life of a program so it might not make a difference, but this particular message is called many, many times when dragging. Because it is in this case, it might be worth the multiplication, instead.
You can detect touches by using UIResponder methods like - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event in that you can call the [self.viewController.rollDiceImageMain startAnimating]; method.
And once it starts then you can stop after some time and restrict the touches.
I have two UIImageViews located about center of a horizontal screen and when the user clicks a button another UIImageView, located off screen, slides in from the right. I'm am just trying to detect when the view being brought onto the screen collides with the two static views. The problem is when I run my code and check the CGRect frames they are returned from where the views start, not end, regardless of where I place the frame calls in my method or even if I place them outside the method in a separate one. I'm a bit new to Obj-C and I understand that Core Animation runs on a separate thread and I am guessing that is why I am getting the starting values. (Correct me if I am wrong here).
So, I guess the question here is how do I detect collisions when one item is static and another is animated. Here's my code (feel free to clean it up):
- (IBAction)movegirl
{
//disabling button
self.movegirlbtn.enabled = NO;
//getting graphics center points
CGPoint pos = bushidogirl.center;
CGPoint box1 = topbox.center;
CGPoint box2 = bottombox.center;
//firing up animation
[UIView beginAnimations: nil context: NULL];
//setting animation speed
[UIView setAnimationDuration:2.0 ];
//setting final position of bushidogirl, then resetting her center position
pos.x = 260;
bushidogirl.center = pos;
//running animations
[UIView commitAnimations];
//playing gong sound
[self.audioplayer play];
//enabling button
self.movegirlbtn.enabled = YES;
[self collisiondetection: bushidogirl : topbox];
}
- (void)collisiondetection:(UIImageView *)item1 : (UIImageView *)item2
{
CGRect item1frame = item1.frame;
CGRect item2frame = item2.frame;
NSLog(#"Item1 frame = %d and Item 2 frame = %d", item1frame, item2frame);
}
How about:
if (CGRectIntersectsRect(item1frame, item2frame))
{
// collision!!
}