Sequential iOS animation *without* blocks? - ios

How might one animate a UIView without using block structure? I just need to force an animation to execute immediately after it is called, and I don't have the luxury of putting the rest of my program in a completion block based on the code structure.
To be specific, right now, here is what is happening
while (actionsRemaining)
{
[self performDesiredAnimation];
....computation ....
[barWidget decreaseValueTo:resultOfComputation];
}
When I run this code, all [performDesiredAnimation] animations happen simultaneously with the 10 or so barWidgets all decreasing simultaneously. What I want is:
performDesiredAnimation1 --> barWidget decreases --> performedDesiredAnimation2 --> barWidget decreases --> performDesiredAnimation3 --> barWidget decreases --> ... however many unknown times.
I don't know how many times because actions will get removed from actionsRemaining if the barWidget decreases past a certain value, i.e. the number of loops will depend on intermediate calculations.
To put it even more simply, what I want is the same result as I am seeing now when I call just one iteration of the loop
[self performDesiredAnimation];
....computation ....
[barWidget decreaseValueTo:resultOfComputation];
, followed by the second iteration, followed by the third, all in isolation. Right now the animation looks fine if I comment out the loop, but they all smash together when I keep the loop there. I just want the animations to execute sequentially from iteration to iteration, not all at once.

Blocks are not what is causing you the problem. Your problem, I think, is your "animations in a for loop" structure.
You need to keep your animating actions in a stack, and when one is finished, pop the next one off the stack and perform that. Alternatively, check in the completion block for the animation if you need to continue, and if so, call the animating method again. Something like:
-(void)performAnimation
{
[UIView animateWithDuration:0.25
animations:^{[self performDesiredAnimations];}
completion:^(BOOL finished){
// It's not clear what your computations are or if they take any significant time
CGFloat result = [self doComputations];
[barWidget decreaseValueTo:resultOfComputation]
if (needToPerformAnotherSet) // Work this out however you need to
{
[self performAnimation];
}
};
}

You can do this:
// Start the work in a background thread.
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (actionsRemaining) {
// Do your calculation in the background if your code will allow you to
[self performDesiredAnimation];
....computation ....
// Back to the main thread for a chunk of code
dispatch_sync(dispatch_get_main_queue(), ^{
// Or do it here if not
[self performDesiredAnimation];
....computation ....
// Finally, update your widget.
[barWidget decreaseValueTo:resultOfComputation];
}
}
}
}
Since your loop enters the async'ed background loop on each iteration the application's main run loop will get a chance to update your UI with the value that you set in the sync'd block.

Related

Call multiple animation, one after another one same UIView object, without using completion

UPATE start
Correct answer is here:
Is it posible to do multiple animations on UIView without using completion block
No need to read this.
UPATE end
I have similar problem as UIView animateWithDuration returns immediately, but I can not use completion block because my animations are in different functions.
But do want to animate same object.
I am making card game, so I am moving card across the screen, but betwean moving I also have some game logic. So that why it is convenient for me to do animations as separate.
How to solve this ?
#Fogmeister
Problem with completion is following:
after method1, method2 is called.
If I want to call method1 52 times (because I have 52 cards), then method2 will also be called 52 times.
But in my scenario I need following, call method1 52 times, than call method2 4 times...
So I do not see how this can be down with competition.
Also sometimes I need to call method2 after method1, but sometimes I need to call method3 after method1...
I was thinking to make deep copy of objects after method1, and then on new object to call method2.
Will that work ?
#Fogmeister 2nd response
I have also come to same conclusion.
But I do not like it duo to following reasons.
I need to put game logic in animation logic, and I would like thing to be loosely coupled.
Animation code should not deal with game logic code.
What I want to do do is to do multiple animation, one after another one same UIView object, without using completion:, because I wont to keep animation code simple and reuse it also.
If you want you can download my code from
http://weborcode.com/dl/Tablic.zip
Animation is done in TBL_CardView.m file method animation*
and it is called from:
TBL_GameViewController.m file method gameStateMachineLoop
all card are shuffled from left to right, and then one card is send to left again.
But problem is that that card is already on the right before shuffle,
If you start the project you will se it.
There isn't really a lot of info to go on but you don't have to animate one things per animation. If you want to animate all the cards then use a single animation and update all the cards in the animations block.
If you want to animate all the cards and then something else then you could do something like this...
- (void)animateCards:(NSArray *)cards
{
// call this once and all of the cards in the array will animate over 5 seconds.
[UIView animateWithDuration:5.0
animations:^{
for (Card *card in cards) {
card.frame = // whatever
}
}
completion:^(BOOL finished) {
if (iNeedToCallMethod2) {
[self doSomethingElse];
} else {
[self somethingCompletelyDifferent];
}
}];
}
- (void)doSomethingElse
{
//this will only run after all the cards have animated
//if the condition requires it to run
[UIView animateWithDuration:5.0
animations:^{
for (Suit *suit in self.suits) {
// not really sure what you want to do here
// but you can do the same as above
}
}
completion:^(BOOL finished) {
}];
}
- (void)somethingCompletelyDifferent
{
//this will run once only if the other method doesn't run
}
All you are doing is controlling the flow. Stop thinking about moving cards around a table and think more about what tools you have available and how you can use them to make things do what you want.

errors when trying to use dispatch_async with cocos2d functions

I'm building a scrolling menu that generates new rows of buttons on the fly, and must generate each button from a large number of sprites. Because this is processor intensive, the menu sticks for about a quarter second each time it needs to load a new row of buttons. I realized I needed to add multi-threading so the button load could be handled in a different thread than the scroll animation, but when I do it crashes when it tries to load new buttons. Here is the code I'm using:
-(void)addRowBelow{
_rowIndex--;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSMutableArray *row = [self addRow:_rowIndex];
[_buttonGrid addObject:row];
[self removeRow:[_buttonGrid objectAtIndex:0]];
});
_nextRowBelowPos += _rowHeight;
_nextRowAbovePos += _rowHeight;
}
Each time I test it I get a different error, sometimes it's a memory error or an assertion failure. I suspect it has to do with calling cocos2d functions asynchronously?
You are probably getting crashing issues because you are multithreading access to the cocos managed objects (sprites, layers, nodes, etc). Since the engine expects to use the internals of these objects for display, GPU operations, etc., and is NOT thread safe, you are probably not going to have good outcomes with multi-threading. You may be changing stuff right in the middle of when it is using it.
Creating/destroying sprites on the fly is probably the reason for your slow down. Cocos2d can display lots (I think it is on the order of 2k) objects on the screen at 60 fps...as long as you don't throttle it down by doing a lot of creation/destruction or AI.
I suggest you preload all your sprites before your scene goes on the stage. You can do this in an intro scene or in the init of the scene itself and let the sprites be owned by the scene. Then you can iterate over them during the update() call and change their positions, make the visible/invisible, etc.
For reference, I usually create different "sprite layers" that load up all their sprites on addition to the scene. If I am going to have dynamic objects, I try to allocate some up front and recycle them when possible. This also allows me to control the order of "what is in front of what" on the screen (see example here). Each layer also draws elements of specific "entity types", giving a nice "MVC" character to a lot of the display.
This is analogous to the way iPhone Apps recycle table cells.
Only create them the first time you need them and have a stash on hand before you need them at all.
Was this helpful?
The pattern you probably want to use is
Dispatch work to a background thread. (Note that the work must be safe to execute on a background thread.)
Dispatch back to the main thread to update your UI.
Here's an example of what that looks like in code:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do work that is safe to execute in the background.
// For example, reading images from disk.
dispatch_async(dispatch_get_main_queue(), ^{
// Do work here that must execute on the main thread.
// For example, calling Cocos2D objects' methods.
NSMutableArray *row = [self addRow:_rowIndex];
[_buttonGrid addObject:row];
[self removeRow:[_buttonGrid objectAtIndex:0]];
});
});

Make UIView Appear Before Network Operation

I have a seemingly simple problem that I cannot for the life of me seem to figure out. In my iOS App, I have a UICollectionView that triggers network operation upon tapping it that can take a few seconds to complete. While the information is being downloaded, I want to display a UIView that fills the cell with a UIActivityIndicatorView that sits in the square until the loading is done, and the segue triggered. The problem is that it never appears. Right now my code looks like:
myLoadView.hidden = NO;
//Network Operation
myLoadView.hidden = YES;
The App simply stops for a couple seconds, and then moves on the the next view. I'd imagine Grand Central Dispatch has somthing to do with the solution, however please keep in mind that this code takes place in prepareForSegue, and the network info needs to be passed to the next View. For this reason not finishing the download before switching scenes has an obvious problem. Any help would be VASTLY appreciated. Thanks!
iOS commits changes in the interfaces after working out a routine. Hence you should perform your network operation in a background thread and then get back back on the main and perform the "show my view now thing". Have a look the below code for reference.
myLoadView.hidden = NO;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//Network Operation
dispatch_async(dispatch_get_main_queue(), ^{
myLoadView.hidden = YES;
});
});
Your network operation seems to be carried out on the main thread, aka UI thread. This blocks all further UI calls, including the call to unhide a view, until completion.
To resolve this, make your call asynchronous.
You should read this in full, if you haven't already.
As mentioned by other answers, the problem is that the UIView change doesn't happen until the current method finishes running, which is where you are blocking. Before GCD was available I would split methods in two and use performSelector:withObject:afterDelay (to run the second part also on the UI loop) or performSelectorInBackground:withObject: at the end of the first method. This would commit all the waiting animaations first, then do the actual tasks in the second method.
Well the better option for this type of indication is by using the custom HUD libraries like SVProgressHUD or MBProgressHUD

How can I handle sequential animations cleanly within an MVC design?

I've written some code that moves some objects around on the screen in sequence. As each object finishes moving, the next starts.
The code is structured similarly to this:
Model:
moveObject
{
// Code to move the object
...
[delegate moved:self];
}
Delegate (Controller):
moved:(MyClass *)o
{
UIView* v = [self viewForObject:o];
[UIView animateWithDuration:1.0
animations:^{
[v setCenter: [model center]];
}
completion:^(BOOL finished){
// Move object
[model moveObject];
}];
}
Aside from the fact that this doesn't actually work correctly, it doesn't seem conceptually right to me. The logic in the model should be free to carry on moving things around without having to wait to be nudged by the controller telling it that the views are ready for more updates.
My plan is to create a queue of pending actions-requiring-animation, and each time the model updates something which needs animating, just add it to the queue and let it carry on. Then I could pluck actions off the queue and animate them one at a time.
This would also mean that I could use the model without any views at all, without having to write a separate controller that just keeps calling back into the model when objects are moved.
So before I start working on my own solution for this, is there already code to handle this in Apple's APIs? It seems like it would be a common enough problem that there would be a standard method of handling it.
Alternatively, is there a better way to handle building chains of animations at run-time? (As opposed to simply hard coding them using the completion block.)

Show a subview immediately / wait until a view is visible before continuing

I have an app that launches to a tab bar controller. When the app either starts up or returns from the background it checks a server for updates to its data. If updates are available, it can take several seconds to get the data and update it.
I would like to simply present an overlay view saying to the user that the app's data is updating and to please wait for a few seconds. The way that I am trying to do this is as follows: in my class that takes care of the updates I have:
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
[delegate.tabBarController.selectedViewController.view addSubview:updatingDataView];
[self runUpdateMethods];
The problem is that the updatingDataView appears on screen only after the update methods have completed. How can I get it to appear before the updating methods start?
It looks like you are running [self runUpdateMethods]; on the main thread (This will block your UI from updating). You would want to run this on a background thread. Something like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
^{
[self runUpdateMethods];
});
or
[self performSelectorInBackground:#selector(runUpdateMethods) withObject:nil];
Update
Since you want to do something after [self runUpdateMethods]; completes you would want to do something like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
^{
[self runUpdateMethods];
dispatch_async(dispatch_get_main_queue(), ^{
[self doSomethingAfterUpdate];
});
});
Doing it this way would still give you the ability to know when runUpdateMethods returns and will not hang the UI.
Your update method (the connection) is probably being executed on the Main Thread, what blocks UI updates. You should use async methods (gcd, NSThread, NSOperationQueue, etc) to run your update.
You need to empty into your run loop so that the views you've added will get drawn on the screen. Drawing on iOs is not done real time. You basically set up your drawing and exit your method, and the run loop actually draws it. So what you need to do is delay the execution of runUpdateMethods until after you've exited your routine. Try instead:
[self performSelector:#selector(runUpdateMethods) withObject:nil afterDelay:0.0];

Resources