UIView animations completion block firing when an other animation begins - iphone

I use the block base API for my animations on iOS.
One animation has a completion block and that block is called at the end of the animation, nice.
However, that animation can be fired multiple times when the user scrolls (the animation is on a UITableViewCell). When that happens the the completion block is called multiple times. The finished parameter of the block is always YES.
Since the animation is not actually finished (an other animation took place) I thought that the finished parameter would be NO, but it's not.
Did I miss something? How can I avoid the completion block to be called multiple times?

The completion block is called multiple times simply because, in your case, your animation is fired multiple times. What is happening is that iOS invokes your animation block each time it is told so, probably in a separate thread. Then, for each animation it tracks its completion, and upon completion it calls the associated completion block. So basically, you see your completion block firing multiple times, one for each invocation of your animation. Note that the boolean value associated to a completion block is specific of that completion block, it does not refer in any way to a different animation.
To recap, what you are experiencing is simply the effect of concurrency. If this is not your intended behavior, then you need to modify your code accordingly. If you want your animations to fire one at a time, you may use NSLock (NSConditionLock for advanced control using an associate condition variable) or, if you prefer, a mutex and the Posix pthreads library directly, to create a critical section to be executed in a mutually exclusive fashion.

Not sure when you're firing the animations and whether they loop (like a UIActivityView spinner or something) - sounds like it's every single pixel the table view is scrolling?
In any event, perhaps you could use the UIScrollView delegate methods and tell each cell to start animation on scrollViewWillBeginDragging: and tell each cell to stop at scrollViewDidEndDragging:
You could set a boolean isAnimating for your UITableViewCell and if an animation is currently underway, do nothing.
if (isAnimating) {
// ... do nothing
} else {
// Start your animation
}
Or stick with whatever you have now and use a boolean still, but only fire the animation if it's not currently animating. Then in your finished parameter just set isAnimating to NO.
if (isAnimating) {
// ... do nothing
} else {
[UIView animateWithDuration:0.3f
animations:^{
// animations...
isAnimating = YES;
}
completion:^{
isAnimating = NO;
}
];
}

I've resolved this issue by looking if the completion block is relevant at the beginning of that block.
The finished parameter is not relevant right now. I've communicated with Apple and they told me that it's fixed in iOS 4.2.

Related

Dispatch Queues - Nesting, Optimisation & Control

Can I have your advice on the approved approach?
I have four processes that need to run sequentially:
calculateProcess1()
calculateProcess2()
calculateProcess3()
calculateProcess4()
These calculate data and update progress bars (circular) and other screen literals using dispatch queues to make the UI update.
Run them as is and lo and behold they all fire off simultaneously (what a surprise!).
How do I make the top level calls run in sequence (as above) and do I need to make changes to my DispatchQueues in the processes for updating the literals, e.g.:
DispatchQueue.main.sync {
let progressPercentage = (day*100/365)
self.progressLabel.text = String(Int(progressPercentage))+"%"
}
The initiating processes (calculateProcess1-4()) need to run from main. Should this be in ViewDidLoad, a button action or what is the most secure method?
A one approach would be to use Operation and OperationQueue. Setting maxConcurrentOperationCount: Int to 1 on the OperationQueue, will force operations to be performed in a sequence, one after another.
(Previously called NSOperation and NSOperationQueue)
This blogpost can be helpful: https://nshipster.com/nsoperation/
And of course awesome WWDC15 session: Advanced NSOperations
1- Don't sync in main thread as it'll cause a runtime crash , it should be async
DispatchQueue.main.sync {
2-
Should this be in ViewDidLoad, a button action or what is the most secure method?
it's up to you there is no thing related to security here , implement it as your UX
3- to run them in dequence create a custom Queue then dispatch them in it as it' ll run serially if the code inside all these methods run in the same thread of the queue meaning you don't internally dispatch inside another queue , you can also use DispatchGroup to be notified when all are done
If you want to keep it simple you can provide callback blocks to your calculateProcess methods and simply nest them.
calculateProcess1() {
calculateProcess2() {
calculateProcess3() {
calculateProcess4() {}
}
}
}
Should this be in ViewDidLoad, a button action or what is the most
secure method?
viewDidLoad is probably what you want. Keep in mind if you instantiate a new viewController of the same type and show it, it will happen again, once, for that new controller as well. Also, if you are doing anything with view frames, those are not guaranteed to be laid out in viewDidLoad, but if you are simply updating text then it should be fine.

iPhone: Wait until UIView animation is Finished

I'm creating a turn based game for the iPhone that contains animations between turns, and I want to wait on the [UIView animateWithDuration:...] method call inline in code. Is there a way to make this call synchronously instead of asynchronously? Currently what I am doing is...
// Some code...
NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:0];
dispatch_sync(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:1
animations:^{
// some animation
}
completion:^(BOOL finished){
[conditionLock lock];
[conditionLock unlockWithCondition:1];
}];
});
// forces thread to wait until completion block is called
[conditionLock lockWhenCondition:1];
// More code...
Therefore in the above code, "// More code..." is only reached after the animation has completely finished. Obviously this code must run on a secondary thread, and it works as I want to. However, I have a feeling that using an NSConditionLock in combination with gcd is bad form and blocking the secondary thread in this way is not optimal for performance. Is my current code alright, or is there a better way to do this? Thanks.
Edit:
The key point is that "// more code..." is inline, and not in the completion block.
Really what I want to know, is it alright to use NSConditionLock's in combination with GCD, and if not what's the better way?
I would just put "//More code" in another method and call that method in the completion block. This will ensure that your code is only fired once your animation completes.

iPhone SDK: Block Animation Ignore Error with Curved Path

I'm creating an application where the user can draw a line on a screen from an object to the location they want to send it and the object will then follow the line to the final location. In order to do this, I've already created working methods to allow the user to draw the lines and then to store the coordinates of the line in a MutableArray. However, I'm having some trouble when I try to animate. As I'm pretty new to the iPhone OS, this could be a simple problem, but I haven't been able to find a solution yet.
I am NOT using Bezier Paths as the user is drawing the line manually, I'm not drawing it programmatically.
Here's the code that I've tried
-(void)animateButtonWasPressed
{
for (int f = 0; f < [cordArrayY count]; f++) {
NSString *newY = [cordArrayY objectAtIndex:f];
NSString *newX = [cordArray objectAtIndex:f];
[self myAnimate:newX :newY];
}
}
-(void)myAnimate:(NSString *)PntX :(NSString *)PntY
{
[UIView animateWithDuration:.5 animations:
^{
object.center = CGPointMake([PntX floatValue], [PntY floatValue]);
}];
}
SYNTAX:
object - the object I am trying to move
cordArray - the mutable array containing the x-coordinates
cordArrayY - the mutable array containing the y-coordinates
Everything else is either defined within the code or Apple methods
The problem: the object moves instantly from its original location directly to the final location. I get a NSLog which tells me this:
-[UIApplication endIgnoringInteractionEvents] called without matching -beginIgnoringInteractionEvents. Ignoring.
Any help would be appreciated!
The method you're using to animate "object" seems to be ok. I believe the problem is the loop in which you are invoking that method. You are trying to animate the same property of the object over and over in every step of that loop. I think this causes that "jump".
Take a look at this quote from Apple's docs:
Important: Changing the value of a property while an animation
involving that property is already in progress does not stop the
current animation. Instead, the current animation continues and
animates to the new value you just assigned to the property.
http://developer.apple.com/library/ios/#documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/AnimatingViews/AnimatingViews.html#//apple_ref/doc/uid/TP40009503-CH6
As a consequence of invoking the animation in each step, you will end up animating object in approximately 0.5 secs to the last position of your coordinates array.
I think you should link those animations together, but you should wait for each animation to finish to start the following one. Take a look at this
Another thing that both the animateWithDuration:animations:completion:
and animateWithDuration:delay:options:animations:completion: methods
support is the ability to specify a completion handler block. You
might use a completion handler to signal your application that a
specific animation has finished. Completion handlers are also the way
to link separate animations together.
Hope this helps,
Cheers.
I was finally able to solve this problem:
At first I tried to continue using the method that I had above. Lio's advice to use the "completion" block was perfect, but as I needed it to loop for an undefined number of times, I would have to use a counter variable. iPhone block programming doesn't allow the modification of external variables or the use of the _block declaration, so this didn't work out for me.
However, I eventually created my entire animation using NSTimer in the method described here just using my array coordinates instead:
http://www.icodeblog.com/2008/10/28/iphone-programming-tutorial-animating-a-ball-using-an-nstimer/

How can I pause/restart a loop in objective-c

Would like to execute some code in a loop, but I want to pause it while a uiscrollview is scrolling.
I've created a BOOL named isScrolling that is set to "YES" in the scrollViewDidScroll method and is set to "NO" in the scrollViewDidEndDecelerating method.
I'd like to put the loop in the scrollViewDidEndDecelerating method, but have it paused when isScrolling == YES and then restart it when isScrolling == NO.
I am looping through an array, so I'd like the loop to pick up where it left off in the array.
For example, here's a simple loop that continually counts higher by increments of 1 that I placed in the viewDidAppear method:
for (int i = 1; i > 0; i++) {
NSLog(#"i = %d", i);
if (isScrolling == YES) {
NSLog(#"break");
break;
}
}
But, all scrolling is disabled while this loop is running for some reason so I can't cancel it.
How can I do this?
Thanks!
You say this loop is within viewDidLoad, which means it's being executed by the main application thread (and thus the "UI thread). If that's the case, there's nothing you can do. Your UI isn't going to be doing anything at all while that loop is running because it's the same thread executing the loop.
Without knowing more about what it is you're actually doing in that loop, it's hard to suggest what you could do to offload that work into a separate thread.
I'm not entirely sure what you're aiming for in a use case for this - I'm guessing you have something happening in a loop that's slowing down your UIScrollView scrolling and making it stutter a bit more than you'd like.
I'm going to guess that the answer is make the background task that you're working with asynchronous so that it's not taking up so much CPU resource in general. If you really want to bind into reacting if the scroll view is moving, then you want the UIScrollView Delegate methods to be your primary trigger points. The ones that stand out as potential uses:
– scrollViewWillBeginDragging:
– scrollViewDidEndDragging:willDecelerate:
– scrollViewDidEndScrollingAnimation:
...
Don't invoke the looping in viewDidLoad or you'll block the main UI interaction thread. Instead trigger off a background process using classic threading or better yet NSOperationQueue to do whatever looping/background thing you want done. If you have that queue reference in the view controller, you could also hook into that to pause or cancel those operations when a person decides to scroll using the delegate methods above.

EXC_BAD_ACCESS on IPhone Cocos2d

I Have the following code:
-(void) changeAnimation:(NSString*)name forTime:(int) times {
if(currentAnimation != #"attack")
{
id action = [CCAnimate actionWithAnimation:[self animationByName:name]];
id repeatAction = [CCRepeat actionWithAction:action times:times];
currentAction = [self runAction:repeatAction];
lastANimation = currentAnimation;
currentAnimation = name;
}
else if(currentAction.isDone)
{
//Here is where I would change the animation
//but I commented the code for now
}
}
So when I run this and click on the button that changes the animation to "attack" (by calling [mysprite changeAnimation:#"attack" forTime:1];), I get a EXC_BAD_ACCESS error from the "currentAction.isDone" line, the next time the function is called (the joystick will call changeAnimation to try and change the animation to "run" or "idle", but I want the attack animation to finish first). Any thoughts on whyI get this? currentAction is declared in my class.
Edit: there is nothing in the rest of the class that interacts with currentAction, beside a getter. Its declaration is in the .h (CCAction* surrentAction). Do I need to initialize it? I thought the returned value from runAction would be sufficient? ANyways, when I run the debugger, it is not nil, and assigned to the correct action.
Thanks,
Dave
Edit:
I ended up creating a sequence when "attacking" that calls a function that changes the currentAnimation, so i avoided the issue. Still no idea what was happening.
Here's the answer if your interested:
Other Post
More of the class is probably needed to really answer this properly, but the EXC_BAD_ACCESS typically happens because you're accessing something that has been released and is no longer available in memory.
I'm guessing that somewhere in your class you're releasing, either explicitly, or implicitly, the "currentAction" object asynchronously - and when you're checking later, it's done & gone and you're hitting this crasher.
In general, keeping a state variable or two that you always have known values on is a good way to go, and for the "actions" that you're going through, if they're asynchronous and doing their own memory management, leave them as such and work through some state variables that you maintain and control all the memory management around. It's a pretty reasonable pattern for asynchronous callbacks, either with the classic stuff or as you move into using blocks with iOS 4.0