I have a segue being called by 5 different methods when a UIView animateWithDuration is complete. I find that if I just call the segue once (animating just one UIImageView) everything works fine, but by calling multiple this error appears:
Warning: Attempt to present <GameOver: 0x7ffc7b714280> on <Playing_Page: 0x7ffc7b7128b0> whose view is not in the window hierarchy!
The completion block (Same for all animations)
//In completion block
if (a!=b) {
[self squareOneColour];
[self squareOneMover];
}
if (a==b) {
if (self.squareOne.hidden==NO) {
[self performSegueWithIdentifier:#"gameOver" sender:self];
}
}
//Conditions must be included in answer. The are unique to each animation
Therefore when all five animations are complete (and meet conditions) it calls the segue 5 times and creates the error (I'm pretty sure, cause I did a lot of testing)
What I want is for Playing_Page to transition to GameOver without errors when the animations (and meet conditions) are finished. Any help?
You can increment an integer property in the completion block of each animation, and inspect that value in the overridden setter for that property. In the following example, that property is called counter, and I had three animations that were triggered from buttons. I'm just showing the code for one button, but the code in the completion blocks is identical.
-(void)setCounter:(NSInteger)counter {
_counter = counter;
if (_counter == 3) {
NSLog(#"All three animations are done");
// do your segue here
}
}
- (IBAction)leftButton:(id)sender {
self.leftCon.constant = 300;
[UIView animateWithDuration:5 animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
self.counter +=1;
}];
}
Related
I have a method that programmatically will take a screenshot [self makeScreenshot]. But sometimes, when a rotation happens for example, the result can be very ugly with black parts in it. So I’m trying to make a method with a completion that will wait for the view controllers animations to finish, so that the screenshots can be made safely and always look nice. The call would maybe look something like this:
[self methodWithCompletionWhenAnimationsIsDone:^(BOOL finished) {
// now it's safe to make the screenshot.
UIImage *myScreenshot = [self makeScreenshot];
}];
But I cant figure out how the code for such a method would look like. Any suggestions?
I’ve tried to place screenshot code the - (void)viewDidLayoutSubviews-method and it didn't work. And I don't want to use any rotation callback methods either, because the problem occurs at other occasions as well.
I don't know which method for animation you are using, but I will give you an example of the default UIView animation with completion:
[UIView animateWithDuration:1.0 animations:^{
/* Some animation here */
} completion:^(BOOL finished) {
UIImage *myScreenshot = [self makeScreenshot];
}];
Maybe you try to perform it in background thread
[self methodWithCompletionWhenAnimationsIsDone:^(BOOL finished) {
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *myScreenshot = [self makeScreenshot];
});
}];
I have a method which can create and animate view
- (void) draw {
UILabel* label = [[UILabel alloc] initWithFrame:CGRectMake(57, 353, 350, 80)];
[self.view addSubview:label];
[UIView animateWithDuration:3
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
[UIView animateWithDuration:2
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
label.alpha = 0;
} completion:^(BOOL finished) {
[self.view willRemoveSubview:label];
}];
} completion:^(BOOL finished) {
}];
}
This method called when button was clicked. But if I click fast, the new views appear without waiting when the previous has finished. I want to create a queue of methods, like, when I tap on button, the new event is pushing to the queue, and all events are waiting when the previous one is finished, to start. Maybe I can do this with NSOperationQueue or GCD ?
As was already mentioned, what's up with the nested animateWithDuration:. Also and more to the point, You probably don't want to do anything with NSOperationQueue or GCD because all animations must be done on the main thread. If you did really want to do some threading you'd end up with something like
-(void)draw
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
dispatch_sync(dispatch_get_main_queuem ^{
//do your animations and pray you don't cause deadlock
}
}
}
What you should probably do is disable the button when you press it, and re-enable it in the completion block of the animation
Why are you nesting the animateWithDuration:... call? I believe you can eliminate some of that complexity by only having a single call to animateWithDuration:...
To answer your actual question, you can use a simple queue made with a NSMutableArray. You can encapsulate your animation call as an object (either full custom or simply as a block or an NSInvocation) and push that object into the array. In the scenario you describe everything is running on the main thread, so you don't have to do any synchronization. Simply have a flag (isRunning) that you set when you start animating, and on completion try to see if there's another animation to start. Otherwise set the flag to false.
That should avoid any GCD calls.
I'm trying to build an animation around a UIButton. The UIButton has a UIImageView that contains an image that I'd like to shrink when the UIButton is held down and then when the UIButton is let go, I'd like to play a separate animation that does a bounce.
The issue I'm experiencing right now is that the 2nd part of the animation doesn't seem to play if I press down and then up very quickly. If I press and hold (wait for the first animation to finish), then let go, it seems to work fine.
Here's the relevant code:
-(void)pressedDown
{
[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.heartPart.layer.transform = CATransform3DMakeScale(0.8, 0.8, 1);
} completion:nil];
}
-(void)pressedUp
{
[UIView animateWithDuration:0.8
delay:0.0
usingSpringWithDamping:20
initialSpringVelocity:200
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
self.heartPart.layer.transform = CATransform3DIdentity;
}
completion:nil];
}];
}
In my ViewDidLoad I add the following:
[self.heartButton addTarget:self
action:#selector(pressedUp)
forControlEvents:UIControlEventTouchUpInside];
[self.heartButton addTarget:self
action:#selector(PressedDown)
forControlEvents:UIControlEventTouchDown];
Any idea how I can get the two animations to sequence and not interrupt each other even on a quick press?
Here's a potential plan of action. Keep two global variables, BOOL buttonPressed and NSDate *buttonPressDate. When the button is pressed, you should set buttonPressed to true and set buttonPressDate to the current date. Now in the touch up method, you would set buttonPressed to false and check whether the time interval between buttonPressDate and the current date is greater than the duration of the animation. If it is, then run the touch up animation; if not, return. In the completion block of the touch down method, you would check if the button was still pressed. If it is, then do nothing; if it's not pressed anymore, then run the touch up animation.
The effect you'll get using this approach should be the following: if you tap the button quickly, it will run the full 0.2-second shrink animation and then run the enlarge animation in sequence.
Now, if you don't want the touch down animation to run in the case of a quick touch, you should probably delay the first animation and check if the button is still pressed when you start it. If it was a quick touch, you would run a modified animation that covered both the touch down and touch up phases.
You could try out the following :-
//Set only one target with TouchUpInside.
[self.heartButton addTarget:self
action:#selector(pressedUp)
forControlEvents:UIControlEventTouchUpInside];
-(void)pressedDown
{
[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.heartPart.layer.transform = CATransform3DMakeScale(0.8, 0.8, 1);
}
completion:
[self pressedUp];
//Or
[self performSelector:#selector(pressedUp) withObject:self afterDelay:0.5];
];
}
-(void)pressedUp
{
[UIView animateWithDuration:0.8
delay:0.0
usingSpringWithDamping:20
initialSpringVelocity:200
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
self.heartPart.layer.transform = CATransform3DIdentity;
}
completion:nil];
}];
}
This way as first animation is completed then next animation will start But my concern is that it won't look appropriate and it would be a bit long animation to show to a user. Rest it's upto your app design and what all it is going to achieve with it.
I assume you are familiar with the concept of operations. If not then NSOperations allow you to keep your code modular and enable you to set the order of execution.
You could create a custom subclass of NSOperation and run animation within its execution block, then add operations with animations on queue each time user interacts with the button.
Apart from documentation from Apple, there is a great example of how to subclass NSOperation available on Github:
https://github.com/robertmryan/AFHTTPSessionOperation/blob/master/Source/AsynchronousOperation.m
Based on that "blueprint" for your custom operations, it's very trivial to achieve what you want, i.e.:
#interface PressDownAnimationOperation : AsynchronousOperation
- (instancetype)initWithView:(UIView *)view;
#end
#implementation PressDownAnimationOperation {
UIView *_view;
}
- (instancetype)initWithView:(UIView *)view {
self = [super init];
if(self) {
_view = view;
}
return self;
}
- (void)main {
// dispatch UIKit related stuff on main thread
dispatch_async(dispatch_get_main_queue(), ^{
// animate
[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
_view.layer.transform = CATransform3DMakeScale(0.8, 0.8, 1);
} completion:^(BOOL finished){
// mark operation as finished
[self completeOperation];
}];
});
}
#end
Now in order to run animations you need to create NSOperationQueue. You can keep it within your view controller, i.e.:
#implementation ViewController {
NSOperationQueue *_animationQueue;
}
- (void)viewDidLoad {
[super viewDidLoad];
_animationQueue = [[NSOperationQueue alloc] init];
// limit queue to run single operation at a time
_animationQueue.maxOperationCount = 1;
}
#end
Now when you want to chain animations, you simply create new operation, add it to queue and that's it, i.e.:
- (void)pressedDown {
NSOperation *animationOperation = [[PressDownAnimationOperation alloc] init];
[_animationQueue addOperation:animationOperation];
}
If user taps too fast and you notice that your operation queue is clogged with animations, you can simply cancel all animations before adding new one, i.e.:
[_animationQueue cancelAllOperations];
Hi everyone,
small question.
I have an UIView class that I created, AnimatedUIView.
In the init, I create a subview that I animate like this:
- (void)animateView{
[UIView animateWithDuration:4.0
delay:1.0
options: nil
animations:^{
// Animation PART 1
}
completion:^(BOOL completed){
[UIView animateWithDuration:4.0
animations:^{
// Animation PART 2
}
completion:^(BOOL completed){
[self animateView];
}
];
}
];
}
I have to call [self animateView] myself in the second completion block rather than using option repeat because I want to delay 1 second only the first animation.
Everything works fine, I alloc and init my view and add it as a subview, and it is animated as should.
But, when I close my superview, I guess ARC does its work and deallocs the animatedView, but my CPU goes 100 % !
I have investigated and if I comment the call to [self animateView], my CPU doesn't skyrocket when I close the superview.
So I've managed to solve the problem (by putting a condition before the call and changing a boolean value before closing the superview, keepAnimating = NO) , I just wanted to understand WHY it does this?
Why does it try to keep animating, and why does it use so much CPU?
If I put an NSLog in the completion block, I first see it every 8 seconds, but when I close the superview, the NSLog just keeps appearing every ms...
BTW: it relates to this question : UIView animation using 90%CPU , which was not really answered. Thanks a lot!
CPU going to 100% almost always means infinite recursion.
We can only guess because only Apple knows what's inside the animation source code.
In this case I guess this could be caused by triggering the animation when the animation cannot run anymore (e.g. there is no layer since we are in dealloc state). That would mean the completion handler is called immediately and that leads to infinite recursion.
There are safer ways to create infinite animations (e.g. using CAAnimation on the layer).
You should not use self in side the block. It will create a retain cycle. If it is necessary to call self create a weak self pointer. Code like this
__weak typeof(self) weakself = self;
[UIView animateWithDuration:4.0
delay:1.0
options: nil
animations:^{
// Animation PART 1
}
completion:^(BOOL completed){
[UIView animateWithDuration:4.0
animations:^{
// Animation PART 2
}
completion:^(BOOL completed){
[weakself animateView];
}
];
}
];
But main issue with your code is you calling animateView recursively without any base case. So i consuming CPU cycles... do not use animateView without base case.
Try using timer
Write following line in your viewDidLoad or any where else from where you want animation to start.
[NSTimer scheduledTimerWithTimeInterval:9 target:self selector:#selector(animateView) userInfo:nil repeats:YES];
// 9 is total animation duration.....
update your animateView method as follows:
- (void)animateView{
[UIView animateWithDuration:4.0
delay:1.0
options: nil
animations:^{
// Animation PART 1
}
completion:^(BOOL completed){
[UIView animateWithDuration:4.0
animations:^{
// Animation PART 2
}
completion:^(BOOL completed){
}
];
}
];
}
I have a tabbed application and when I click on the second tab, an animation should be performed and then other operations should be executed.
I have implemented the animation with [imageView startAnimating]; and everything works well. The problem is that I don't know how to intercept the end of the animation process, in order to perform other operations.
I know that UIImageView has no delegate methods. I know that the imageView.isAnimating property is not useful. I know that using a NSTimer, the operation is executed at the end of timer, even if, for example, in my case the user has changed tab.
Any ideas on how to solve this issue? How to intercept the end of the animation?
I believe using an NSTimer is the only way to get the behavior you want. However, when the timer fires, I would include a check in whatever method gets called to make sure the tab you're talking about is still open; or you could invalidate the timer once the user changes tabs.
Try thse when you are loading your UIImageView:
[UIView transitionWithView: destinationView
duration:0.2f
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^ {
//Funky Animation
}
completion:^(BOOL finished) {
if(finished) {
//Funky Completion Code
}
}];
or
[UIView transitionFromView: startView
toView: destinationView
duration:0.2
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^ {
//Funky Animation
}
completion:^(BOOL finished) {
if(finished) {
//Funky Completion Code
}
}];
completion:NULL];