Reputation: 3064
I want to do three view animations, staggered a little. To do this I' mixing GCD and the view animation as follows...
EDIT - I'd like to do a flip animation with the children of viewA, then viewB, then viewC. I'd like these animations to overlap...
random external action causes: A-----------A'
B-----------B'
C---------C'
The trouble is, when a triggering action occurs during the A->C' animation, the states of the three get mixed up. I'd like a way of doing the three staggered actions as a single, uninterrupted action.
- (void)flipAnimated:(BOOL)animated {
// views A,B,C are initialized here
// kFLIPDURATION is 0.8
NSTimeInterval interval = (animated)? kFLIPDURATION / 2.0 : 0;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.01 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self flipChildrenOfView:viewA animated:animated];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self flipChildrenOfView:viewB animated:animated];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2*interval * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self flipChildrenOfView:viewC animated:animated];
});
}
- (void)flipChildrenOfView:(UIView *)parent animated:(BOOL)animated {
// some setup here to get visible and hidden views
[UIView transitionFromView:visibleView
toView:hiddenView
duration:((animated)? kFLIPDURATION : 0)
options:UIViewAnimationOptionTransitionFlipFromLeft | UIViewAnimationOptionShowHideTransitionViews
completion:nil];
}
This works fine, except there are timer events and sometimes user actions driving these, and every so often, flipAnimated:
is called while the animations are underway. The result is mixed up views and sadness.
What I tried so far was (1) reading and them becoming confused about GCD alternatives. (2) I tried an instance variable called animating
, added a completion block to my view animation calling method, then did this...
@property(assign,nonatomic) BOOL animating;
- (void)flipAnimated:(BOOL)animated {
if (self.animating) return;
self.animating = YES;
NSTimeInterval interval = (animated)? kFLIPDURATION / 2.0 : 0;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.01 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self flipChildrenOfView:viewA animated:animated completion:nil];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self flipChildrenOfView:viewB animated:animated completion:nil];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2*interval * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self flipChildrenOfView:viewC animated:animated completion:^(BOOL finished) {
self.animating = NO;
}];
});
}
But alas, same behavior. I could observe the block seeming to work with NSLogs, but the views would still get mixed up. (With a second flip starting before a prior one was done). I assume this is because I got in over my head on concurrency.
Would sure appreciate a helping hand, especially a code example. --- Almost forgot: what would be even better than stoping concurrent requests, would be to have it queued and then run after the running is finished.
Upvotes: 0
Views: 108
Reputation: 3064
@GrahamPerks asked me to clarify the question, and doing so clarified my thinking. In case this can help someone else, here's what I did.
The code was initializing the subviews to animate with the same staggered timing as the flips. When the code was re-entered, it was examining the view hierarchy in a partially changed state.
The fix was to decide about transition views up front, not during, the three step animation...
- (void)flipAnimated:(BOOL)animated {
// views A,B,C are initialized here
// FIX: but also initialize six views here
UIView *viewAVisible, *viewAHidden, *viewBVisible, *viewBHidden, *viewCVisible, *viewCHidden;
// FIX: refactor my animation wrapper to take the two views to transition
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.01 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self flipFrom:viewAHidden to:viewAVisible animated:animated];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self flipFrom:viewBHidden to:viewBVisible animated:animated];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2*interval * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self flipFrom:viewCHidden to:viewCVisible animated:animated];
});
}
Now when a new animation request comes in concurrently, the views are in a coordinated state (because I think UIKit makes the change called for by the animation immediately).
Upvotes: 0
Reputation: 23390
The docs for transitionFromView:...
do say "The view transition starts immediately unless another animation is already in-flight, in which case it starts immediately after the current animation finishes." So overlapping animation won't work like this.
Drop to CoreAnimation, e.g. CATransform3D, see Core animation animating the layer to flip horizontally.
Upvotes: 1