0
votes

I am building a page browser that animates pages as 'sheets of paper' being pulled on top of off a stack of papers. In order to keep smooth animations I use 3 UIViews which are stacked on top of each other. These three views hold the current page (on top), the previous page (in the middle) and the next page (at the bottom).

In the code below, I want to drag the top view off to the right, revealing the previous page underneath. This works fine. However, after that I need to move the top page to the bottom of the stack, in order to prepare the stack of three views for the next time the user does a page flip. I use the sendSubviewToBack method for this.

My problem is that ViewSample[Top] is sent to the bottom of the stack as soon as the animation starts. How can I enforce the animation to finish (so that ViewSample[Top] has moved out of the screen completely) before it is sent to the bottom of the stack?

ViewSample[Top].center = CGPointMake(x,y);

[UIView animateWithDuration:0.5
        animations:^
{ 
   ViewSample[Top].center = CGPointMake(x+w,y); //slide away to the right 
}
completion:^(BOOL finished)
{ 
}
];

[self.MainView sendSubviewToBack:ViewSample[Top]];

EDIT

i just ran into a very peculiar behaviour which has to do with my problem. I followed your advice, and found that the behaviour in the 'finished' section of the animation depends on the value of the variable 'top' when it is set AFTERWARDS:

ViewSample[Top].center = CGPointMake(x,y);

[UIView animateWithDuration:0.5
        animations:^
{ 
   ViewSample[Top].center = CGPointMake(x+w,y); //slide away to the right 
}
completion:^(BOOL finished)
{ [self.MainView sendSubviewToBack:ViewSample[Top]];
}
];

Top++; // THIS COMMAND AFFECTS THE LINE ABOVE!!!

In other words, when I add the line 'Top++;' another View is moved back on the stack, even though the statement sendSubviewToBack came first. This is very confusing to me. Does this make sense? Is it a bug?

3

3 Answers

2
votes

The other answers correctly identified the issue. What you're running into with your updated code is a problem of execution order:

Because completion is executed only after the animation completes, your code actually executes in this order:

  1. ViewSample[Top].center = CGPointMake(x,y);
  2. ViewSample[Top].center = CGPointMake(x+w,y);
  3. Top++;
  4. [self.MainView sendSubviewToBack:ViewSample[Top]];

There are two possible solutions. You can either store the view in a variable so you have the same view in all your calls, or you can delay setting the value of Top until completion.

Option 1

UIView *viewMovingFromTopToBottom = ViewSample[Top];

viewMovingFromTopToBottom.center = CGPointMake(x,y);

[UIView animateWithDuration:0.5 animations:^{ 
    viewMovingFromTopToBottom.center = CGPointMake(x+w,y); //slide away to the right 
} completion:^(BOOL finished) {
    [self.MainView sendSubviewToBack:viewMovingFromTopToBottom];
}];

Top++;
// Other code that depends on the new value of Top...

Option 2

ViewSample[Top].center = CGPointMake(x,y);

[UIView animateWithDuration:0.5 animations:^{ 
    ViewSample[Top].center = CGPointMake(x+w,y); //slide away to the right 
} completion:^(BOOL finished) {
    [self.MainView sendSubviewToBack:ViewSample[Top]];
    Top++;
    // Other code that depends on the new value of Top...
}];

Which option makes the most sense to you depends on what you're doing. If you're chaining animations together, you may want to move most of your code into the completion block to delay it until the slide animation completes. If you have a lot of work that needs to be done right away without dependencies on animation, you may want to use option 1 to configure you animations and move on. Or you may want a mix.

0
votes

Use the completion block:

 ViewSample[Top].center = CGPointMake(x,y);

[UIView animateWithDuration:0.5
        animations:^
{ 
  ViewSample[Top].center = CGPointMake(x+w,y); //slide away to the right 
}
completion:^(BOOL finished)
{
  [self.MainView sendSubviewToBack:ViewSample[Top]];
}
];
0
votes

Brian Nickel's first suggestion (local variable) did the trick. However, there was a caveat: you have to be careful in the order of statements. This does not work: [self.MainView addSubview:LocalView]; LocalView = View[1]; ...whereas this does: LocalView = View[1]; [self.MainView addSubview:LocalView]; I first had the top version, which just makes the blank view appear.

So a working approach is to use three global views to do the page caching, and use two local views for the animation. The local views are stacked in the appropriate order, copy the data from the global views and then perform the animation.