One call function for UIView animation - iphone

In my project, I've got 10 of these code here:
- (void) makeAnim1{
//downward animation for carousel
[UIView animateWithDuration:**3.5**
delay:0.25
options: UIViewAnimationCurveLinear
animations:^{
carousel.frame = CGRectOffset(carousel.frame, 0, **490**);
}
completion:^(BOOL finished){ //task after an animation ends
[self performSelector:#selector(makeAnim1_1) withObject:nil afterDelay:3.0];
NSLog(#"Done!");
}];
}
- (void) makeAnim1_1{
//upward animation for carousel
[UIView animateWithDuration:**3.5**
delay:0.2
options: UIViewAnimationCurveLinear
animations:^{
carousel.frame = CGRectOffset(carousel.frame, 0, **-480**);
}
completion:^(BOOL finished){
//timer for the reveal button
[NSTimer scheduledTimerWithTimeInterval:0.1 //this arranges the duration of the scroll
target:self
selector:#selector(revealButton)
userInfo:nil
repeats:NO];
NSLog(#"Done!");
}];
}
I wanted to make them in just one short implementation. So they will be just one function.
I've tried putting the needed values and assigning them in the bold version of the code.
I need to have two arrays that corresponds to each other. For example:
arrayA[3] = A,B,C;
arrayB[3] = a,b,c;
When A is pick it is equal to a.

Try this below code :
You can convert Array Values to Uppercase or lowercase like below code:
A array contains first index is A to convert a
B array contains first index is a
if([A objectAtIndex:0] lowercaseString].isEqualString([B objectatindex:0])
{
}
Thanks...!

Related

performSelector:withObject:afterDelay method not working properly?

I have an app with a small uiimageview circle that fades in and out into two different locations. When the user touches the screen I want the circle to fade out immediately, and i want it to stop fading in and out into moving locations. So basically touching the screen kills the circle.
After the circle is killed though I want another circle to spawn and do the same thing (fading in and out into 2 different locations) until the user touches the screen, then i want that circle killed, and another circle to spawn and etc...
here's my simplified code:
- (void)spawnCircle {
self.circle = [[UIImageView alloc]initWithFrame:self.rectCircle];//alocate it and give it its first frame
self.circle.image=[UIImage imageNamed:#"circle.png"];//make its image the circle image
[self.view addSubView:self.circle];
[self performSelector:#selector(fadeCircleOut)withObject:self afterDelay:2];//the circle will fade out after 2 seconds
self.isFadeCircleOutNecessary=YES;
}
- (void)fadeCircleOut {
if (self.isFadeCircleOutNecessary){//because after the circle fades in this method is scheduled to occur after 2 seconds, well, if the user has touched the screen within that time frame we obviously don't want this method to be called because we already are fading it out
[UIView animateWithDuration:.5 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{//the circle will fade out for a duration of .5 seconds
self.circle.alpha=0;
} completion:^(BOOL finished) {
if (finished) {
if (self.circle.frame.origin.x==self.rectCircle.origin.x) {//if its in the first location go to the second
self.circle.frame=self.rectCircle2;
}else{//if its in the second location go to the first
self.circle.frame=self.rectCircle;
}
[self fadeCircleIn];//now were going to immediately fade it in its new location
}
}];
}
}
- (void)fadeCircleIn {
[UIView animateWithDuration:.5 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{//fade it in with a duration of .5 seconds
self.circle.alpha=1;
} completion:^(BOOL finished) {
if (finished) {
[self performSelector:#selector(fadeCircleOut) withObject:self afterDelay:2];//after 2 seconds the object will fade out again
}
}];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[UIView animateWithDuration:.5 delay:0 options:UIViewAnimationCurveLinear|UIViewAnimationOptionBeginFromCurrentState animations:^(){ self.circle.alpha=0;} completion:^(BOOL completion){//begin from current state makes it stop any animations it is currently in the middle of
[self spawnCircle];//now another circle will pop up
self.isFadeCircleOutNecessary=NO;
}];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self spawnCircle];
}
so the first time the circle spawns this works great but when the next circle spawns (after the user touches the screen and kills the first circle) the fade out method doesn't happen exactly after 2 seconds, it varies when it happens but it usually almost instantly fades out. so the delay part of the performSelector:withObject:afterDelay doesn't seem to work properly
I tried out your code, and got the same kind of results you did. I'm not sure what goes on behind the scenes with performSelector:withObject:afterDelay:, but it seems like once you get one going, it may still perform its function, even though you've created another circle.
I changed the code slightly by getting rid of the afterDelay calls, but putting the 2 second delay in the fade out method instead. See if this does what you want:
-(void)spawnCircle{
self.circle=[[UIImageView alloc]initWithFrame:self.rectCircle];//alocate it and give it its first frame
self.circle.image=[UIImage imageNamed:#"circle.png"];//make its image the circle image
[self.view addSubview:self.circle];
[self fadeCircleOut];
}
- (void)fadeCircleOut {
[UIView animateWithDuration:.5 delay:2 options:UIViewAnimationOptionCurveLinear animations:^{//the circle will fade out for a duration of .5 seconds
self.circle.alpha=0;
} completion:^(BOOL finished) {
if (finished) {
if (self.circle.frame.origin.x==self.rectCircle.origin.x) {//if its in the first location go to the second
self.circle.frame=self.rectCircle2;
}else{//if its in the second location go to the first
self.circle.frame=self.rectCircle;
}
[self fadeCircleIn];//now were going to immediately fade it in its new location
}
}];
}
- (void)fadeCircleIn {
[UIView animateWithDuration:.5 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{//fade it in with a duration of .5 seconds
self.circle.alpha=1;
} completion:^(BOOL finished) {
if (finished) {
[self fadeCircleOut];
}
}];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[UIView animateWithDuration:.5 delay:0 options:UIViewAnimationCurveLinear|UIViewAnimationOptionBeginFromCurrentState animations:^{
self.circle.alpha=0;
}
completion:^(BOOL completion){//begin from current state makes it stop any animations it is currently in the middle of
[self.circle removeFromSuperview];
self.circle = nil;
[self spawnCircle];
}];
}

How to attach a click event to an animated button in ios

I am having a moveable button and would like to execute a method when the user click on that button - is that possible in ios?
I have write the following code but the animation needs to be finished in order to be able to trigger that function:
[arrowButton addTarget:self action:#selector(presentMainWindow) forControlEvents:UIControlEventTouchUpInside];
[UIView animateWithDuration:kAnimationDuration
delay:.2f
options:UIViewAnimationOptionCurveEaseInOut
animations:
^{
[arrowButton setTransform:CGAffineTransformMakeTranslation(kButtonShift, 0)];
[arrowButton setFrame:CGRectMake(arrowButton.frame.origin.x + kButtonShift, arrowButton.frame.origin.y, arrowButton.frame.size.width, arrowButton.frame.size.height);
}
completion:nil];
I found out that you can simply add the option UIViewAnimationOptionAllowUserInteraction to the animation to handle both the animation and the interaction:
[UIView animateWithDuration:kAnimationDuration
delay:.2f
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAutoreverse | UIViewAnimationOptionAllowUserInteraction
animations:
^{
[arrowButton setTransform:CGAffineTransformMakeTranslation(kButtonShift, 0)];
}
completion:nil];
as long as the animation is only an effect what you can see
and not can touch you can only click the button while animating if you make your own animation like this
i dont know if this is the best solution but it is an working solution.
- (IBAction)startanimation:(id)sender {
old = CGPointMake(arrowButton.frame.origin.x,arrowButton.frame.origin.x);
[self ani];
}
- (void)ani {
NSLog(#"ani1");
if (arrowButton.frame.origin.x < old.x + kButtonShift ) {
NSTimer*myTimer;
myTimer = [NSTimer scheduledTimerWithTimeInterval:kTimeInterval/kButtonShift*4 target:self selector:#selector(ani2) userInfo:nil repeats:NO];
}
}
- (void)ani2 {
NSLog(#"ani2");
arrowButton.frame = CGRectMake((arrowButton.frame.origin.x + 1), arrowButton.frame.origin.y, arrowButton.frame.size.width, arrowButton.frame.size.height);
[self ani];
}

how to invoke a single completion block for a nested group of UIView animateWithDuration calls?

I have a batch of animation calls, invoked by iterating through an array. All these calls are nested within an encapsulating animation block so that they execute effectively in parallel. I also have a completion block with I only want to fire once all of the nested animations have completed.
The problem is that the nested animations are of unknown durations, so I cannot simply calculate which call will be the last to finish and set the completion block on this call. Similarly I cannot calculate the duration and use a delayed invocation on the completion block.
Hopefully an example will make this clearer. This is a (very simplified) version of what I'm trying to do:
-(void) animateStuff:(CGFloat)animationDuration withCompletionBlock:(void) (^)(BOOL)completionBlock {
// encapsulating animation block so that all nested animations start at the same time
[UIView animateWithDuration:animationDuration animations:^{
for(MyObject* object in self.array) {
// this method contains other [UIView animateWithDuration calls...
[self animationOfUnknownDurationWithObject:object nestedCompletionBlock:^(BOOL finished) {
// what I effectively what here is:
if(last animation to finish) {
completionBlock(YES);
}
}];
}
}]; // cannot use the completion block for the encapsulating animation block, as it is called straight away
}
The functionality provided by using a dispatch_groups and asynchronous invocation as described here:
http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html
would obviously be ideal, however the UIView animateWithDuration call is an asynchronous call in itself and so invoking it within dispatch_group_async will not work properly.
I know I could do something like having a __block count variable that gets decremented within nestedCompletionBlock as a way of determining which is the final one, but in the code that I have this is pretty messy (the above is a simplified example).
Is there a good way of doing this? Perhaps some way of doing animateWithDuration synchronously, so that it will work with dispatch_group_async?
Working on iOS 5.0.
UPDATE:
#Rob-Thankyou for your answer! This nearly solves my problem - hadn't looked into CATransaction before, guess I should have dug a little deeper before asking. :) However one issue remains. As I mentioned before, my example was simplified, and the remaining problem arises from the fact that the animations have nested completion blocks (ie. for chained animations) which I need to include within the enclosing transaction.
So for example if I run the following code:
-(void) testAnim {
NSArray* testArray = [NSArray arrayWithObjects:self.redView, self.blueView, nil];
[CATransaction begin]; {
[CATransaction setCompletionBlock:^{
NSLog(#"All animations complete!");
}];
for(UIView* view in testArray) {
[CATransaction begin]; {
[CATransaction setCompletionBlock:^{
[CATransaction begin]; {
[CATransaction setCompletionBlock:^{
NSLog(#"2nd stage complete");
}];
NSLog(#"Animation 2nd stage");
[UIView animateWithDuration:2 animations:^{
setX(view, 100);
}];
} [CATransaction commit];
}];
NSLog(#"animation 1st stage");
[UIView animateWithDuration:2 animations:^{
setX(view, 150);
}];
}[CATransaction commit];
}
} [CATransaction commit];
}
I get the following output:
2011-12-08 15:11:35.828 testProj[51550:f803] animation 1st stage
2011-12-08 15:11:35.831 testProj[51550:f803] animation 1st stage
2011-12-08 15:11:37.832 testProj[51550:f803] Animation 2nd stage
2011-12-08 15:11:37.834 testProj[51550:f803] Animation 2nd stage
2011-12-08 15:11:37.848 testProj[51550:f803] All animations complete!
2011-12-08 15:11:39.834 testProj[51550:f803] 2nd stage complete
2011-12-08 15:11:39.851 testProj[51550:f803] 2nd stage complete
Whereas what I need is the "All animations complete!" event to be fired only once all the inner operations have ended.
Perhaps something to do with the fact that I'm only setting completion blocks at the class level and thereby can't tie each block to a specific operation?
In any case I'm still not quite getting it, and using the UIView animateWithDuration overload with it's own completion block argument just throws up the same problem.Any ideas?
Wrap the whole thing in a CATransaction, and set the transaction's completionBlock. This was my test:
static void setX(UIView *view, CGFloat x)
{
CGRect frame = view.frame;
frame.origin.x = x;
view.frame = frame;
}
- (IBAction)startAnimation:(id)sender {
label.text = #"Animation starting!";
setX(redView, 0);
setX(blueView, 0);
[CATransaction begin]; {
[CATransaction setCompletionBlock:^{
label.text = #"Animation complete!";
}];
[UIView animateWithDuration:1 animations:^{
setX(redView, 300);
}];
[UIView animateWithDuration:2 animations:^{
setX(blueView, 300);
}];
} [CATransaction commit];
}
You'll need to add the QuartzCore framework if you haven't already.
Regarding your updated question: since you're creating new animations from completion blocks, those new animations won't be part of the original transaction, so the original transaction won't wait for them to finish.
It would probably be simplest to just start each later animation as part of the original transaction by using +[UIView animateWithDuration:delay:options:animations:completion:] with a delay value that starts the later animation at the right time.
It may help you.
-(void) testAnim2 {
// NSArray* testArray = [NSArray arrayWithObjects:self.redView, self.blueView, nil];
[CATransaction begin]; {
[CATransaction setCompletionBlock:^{
[CATransaction begin]; {
[CATransaction setCompletionBlock:^{
NSLog(#"All completion block");
}];
}[CATransaction commit];
NSLog(#"animation 2nd stage");
[UIView animateWithDuration:2 animations:^{
setX(blueView, 150);
}];
}];
NSLog(#"animation 1st stage");
[UIView animateWithDuration:2 animations:^{
setX(redView, 150);
}];
} [CATransaction commit];
}

How to have a handler to repeat UIView animateWithDuration?

I'm using UIView class method animateWithDuration for repeating my view animation. How can I have a handler that could be used to stop this animation later? For example, repeated animation starts in one method and I need to stop it later from another method.
You could do something like this assuming you have created a canceled property. As noted in the comments the completion block's startAnimation call needs to be wrapped in an async call to avoid a stack overflow. Be sure to replace the "id" with whatever class type you actually have.
- (void)startAnimation {
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction
animations:^(void) {
//animate
}
completion:^(BOOL finished) {
if(!self.canceled) {
__weak id weakSelf = self;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[weakSelf startAnimation];
}];
}
}
];
}
The purpose of the animation is to repeatedly animate the bounce of an image. When there is no worry about manually stopping it then you just need to set three properties (UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat) and animation block code for moving the image - self.image.center = CGPointMake(self.image.center.x, self.image.center.y+25); Here is the full code of the animation:
[UIView animateWithDuration:0.5 delay:0 options:( UIViewAnimationOptionCurveEaseIn |
UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat |
UIViewAnimationOptionAllowUserInteraction) animations:^{self.image.center =
CGPointMake(self.image.center.x, self.image.center.y+25);} completion:nil];
That's it. But if you need a manual control then some additional code is required. First, according to jaminguy, you need to have a BOOL property for indication loop/stop (self.playAnimationForImage) the animation and clean separate method with animation code that would be called from elsewhere. Here is the method:
-(void)animateImageBounce{
[UIView animateWithDuration:0.5 delay:0 options:(
UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAutoreverse |
UIViewAnimationOptionAllowUserInteraction) animations:^{self.image.center =
CGPointMake(self.image.center.x, self.image.center.y+25);} completion:^(BOOL finished){if
(finished && self.playAnimationForImage){
self.image.center = CGPointMake(self.image.center.x, self.image.center.y-25);
[self animateImageBounce];
}}];
and here is the start of the animation call from some method
-(void)someMethod{
...
self.playAnimationForFingers = YES;
[self animateImageBounce];
}
The thing that I would like to note is that, in manual control, you need to reset the center.y of the image back right before next recursive call is performed.
Actually, the solution with recursive call didn't worked out for me. The animation started to behave weirdly: every 2- 3 animatation repeat cycle I got animation breaks. After the first bouncing part of item (moving item down) the second part (moving up) was performing almost instantly. I thing it has something to do with the recursive call.
Therefore, I refused to use that. The solution would be to start the animation with autoreverse and repeat options and, in complete block, to check if a flag (self.playAnimationForFingers) indicates to stop the animation.
-(void)animateFingersForLevelCompleteScreen{
//fix: reset the place of bouncing fingers (starting place).
//Otherwise the fingers will slowly move to the bottom at every level.
//This resetting is not working when placed inside UIView
//animate complete event block
self.image.center = CGPointMake(10 + self.image.frame.size.width/2,
95 + self.image.frame.size.height/2);
[UIView animateWithDuration:0.5 delay:0
options:(UIViewAnimationOptionCurveEaseIn |
UIViewAnimationOptionAutoreverse |
UIViewAnimationOptionRepeat |
UIViewAnimationOptionAllowUserInteraction)
animations:^{
self.image.center = CGPointMake(self.image.center.x,
self.image.center.y+25);
}
completion:^(BOOL finished){
/*finished not approapriate: finished will not be TRUE if animation
was interrupted. It is very likely to happen because animation repeats && */
if (!self.playAnimationForFingers){
[UIView setAnimationRepeatAutoreverses:NO];
}
}];
}
U can make use of CABasicAnimation instead.
CABasicAnimation *appDeleteShakeAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
appDeleteShakeAnimation.autoreverses = YES;
appDeleteShakeAnimation.repeatDuration = HUGE_VALF;
appDeleteShakeAnimation.duration = 0.2;
appDeleteShakeAnimation.fromValue = [NSNumber numberWithFloat:-degreeToRadian(5)];
appDeleteShakeAnimation.toValue=[NSNumber numberWithFloat:degreeToRadian(5)];
[self.layer addAnimation:appDeleteShakeAnimation forKey:#"appDeleteShakeAnimation"];
Then when u want to stop it you can just call
[self.layer removeAnimationForKey:#"appDeleteShakeAnimation"];

iPhone Flip Animation using UIViewAnimationTransitionFlipFromLeft in landscape mode

I am working on one iPhone application in which I implemented one animation UIViewAnimationTransitionFlipFromLeft. Here my application works fine in the Portrait mode. It is doing the same animation as specified means Flip from Left to Right.
But when I am doing this UIViewAnimationTransitionFlipFromLeft in landscape mode then it is not rotating from left to right. Instead of it is rotating from top to bottom. This is really critical issue. Can you help me out to solve this.
The code I am using for iPhone application to rotate the view is as follows:
CGContextRef context = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationTransition: UIViewAnimationTransitionFlipFromLeft forView:self.view.window cache:NO];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:1.0];
[UIView commitAnimations];
[self.navigationController pushViewController:objSecond animated:YES];
Thanks,
Best Regards,
Gurpritsingh Saini
If you are using iOS 4.0 or later, the following will do exactly what you want (I just tested it out to make sure)
NewView *myNewView = [[NewView alloc] initWith.....];
[UIView transitionFromView:self.view toView:myNewView.view duration:1 options:UIViewAnimationOptionTransitionFlipFromLeft completion:nil];
//[self.navigationController pushViewController:myNewView animated:NO];
[myNewView release];
EDIT: I'm changing the above code a bit (nothing new, just commenting out the navigation controller because it's not necessary for this).
So there are several ways to go about this (as far as keeping track of the next view), but this is the easiest I can think of. You can already switch from view 1 to 2, so I'm going to explain how to get from 2 to 10 (or however many you need).
Basically, the view transition lasts too long for viewDidLoad to catch a call to go to the next view. So what we need to do is set up a timer that waits and sends a method to switch at a later time. So this is the code you would see in view 2 (and 3 and 4, etc.).
- (void)viewDidLoad {
// this gets called before animation finishes, so wait;
self.navigationController.delegate = self;
// you will need to set the delegate so it can take control of the views to swap them;
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(switchView) userInfo:nil repeats:NO];
}
I only wait 1 second until I call the switch method, but if you are loading a lot into your views, you may want to wait a bit longer. 1.5 seconds should be more than enough, but you can play around with that to see where it works and doesn't work.
Next, you have to call the next view in the switchView method.
- (void)switchView {
NextView *myNextView = [[NextView alloc] initWith ... ];
[UIView transitionFromView:self.view toView:nextView.view duration:1 options:UIViewAnimationOptionTransitionFlipFromLeft completion:nil];
[nextView release];
}
This worked perfectly for me. Just to make sure I was getting new views, I assigned tags to each view and added UILabels as subviews in each view's viewDidLoad method and each showed the number of its view. So hopefully this is what you needed. I'm sure you have more complex things you will need to do, but this will give you the animation and logic you need to get the look you want. (on a side note, viewDidAppear doesn't seem to get called when doing this, so it might be necessary to call it manually from viewDidLoad if you really need to use it, but otherwise it works fine)
You will have to manually add a transformation to your view; the flip transformation always operates as if the view controller were in portrait orientation.
Note that the context argument to +beginAnimations:context: is not meant to be a CGContextRef per se. You probably don't want to pass the current graphics context there. Pass NULL instead.
Try with this :
CABasicAnimation *rotateAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
CGFloat startValue = 0.0;
CGFloat endValue = M_PI;
rotateAnimation.fromValue = [NSNumber numberWithDouble:startValue];
rotateAnimation.toValue = [NSNumber numberWithDouble:endValue];
rotateAnimation.duration = 1.5;
[self.view.layer addAnimation:rotateAnimation forKey:#"rotate"];
CGContextRef context = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationTransition: UIViewAnimationTransitionFlipFromLeft forView:self.view cache:NO];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:1.0];
[UIView commitAnimations];
I think this will work out.
The Accepted answer by slev did not work for me, I got all sorts of errors after trying the code in my custom Segue. I found a method that not only works but is more precise as it doesn't require the use of a Timer. Here is MySegue.m:
#implementation FlipRightSegue
- (id)initWithIdentifier:(NSString *)iden source:(UIViewController *)sour destination:(UIViewController *)dest
{
self = [super initWithIdentifier:iden source:sour destination:dest];
return self;
}
- (void)perform
{
UIViewController *src = [self sourceViewController];
UIViewController *dst = [self destinationViewController];
//[UIView transitionFromView:src.view toView:dst.view duration:1 options:UIViewAnimationOptionTransitionFlipFromRight completion:nil];
//[UIView commitAnimations];
[UIView transitionWithView:src.navigationController.view duration:0.8 options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
[src.navigationController pushViewController:dst animated:NO];
}
completion:NULL];
}
#end
I also have a second class for flips to the right.
Code was taken from this website: http://www.dailycode.info/Blog/post/2012/11/29/iOS-Custom-Flip-Segue-(portrait-and-landscape-layout)-xcode-4.aspx