UIView block animation transitions with animated c

2019-08-04 07:37发布

问题:

In my app I have a text field on some view which is covered by the keyboard when it shows up. So I have to scroll the view (or even rearrange the subviews). To do this I:

  1. register for keyboard notifications:

    [[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector(moveViewUp)
                                          name:UIKeyboardWillShowNotification
                                          object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector(moveViewDown)
                                          name:UIKeyboardWillHideNotification
                                          object:nil];
    
  2. upon receiving a notification, move the view using block animations like this:

    - (void)moveViewUp {
        void (^animations)(void) = nil;
        oldViewFrame = self.view.frame;
        animations = ^{
            CGRect newViewFrame = oldViewFrame;
            newViewFrame.origin.y -= kViewOffset;
            self.view.frame = newViewFrame;
        };
        [UIView animateWithDuration:1.0
                animations:animations];
    
    }
    
    - (void)moveViewDown {
        void (^animations)(void) = nil;
        animations = ^{
            self.view.frame = oldViewFrame;
        };
        [UIView animateWithDuration:1.0
                animations:animations];
    
    }
    

This works fine, the view scrolls up and down, until I add some more animation. Specifically I'm adding a transition to a next view when the user taps a button:

- (IBAction)switchToNextView:(id)sender {
    // [self presentModalViewController:nextViewController animated:YES];
    [UIView transitionFromView:self.view
                        toView:self.nextView
                      duration:1.0
                       options:UIViewAnimationOptionTransitionFlipFromRight
                    completion:nil];
}

Now we got to the problem.
If the first view was shifted when the button was tapped (that means that the keyboard was visible), the transition to the next view starts simultaneously as the keyboard slides down, but the view itself doesn't move down, so for a split second we can actually see the underlying view. That's not right. When I present the next view modally (see the commented line) all animations go as I want them to: i.e. the keyboard is hiding, the view is flipping from right and scrolling down -- all at the same time. This would be fine, but the problem is that I actually don't have a UIViewController for that view. In fact I'm trying to simulate the modal behavior without UIViewController (why so? perhaps it's just a bad design, I'll post another question on that).

So why does in this case the animation from moveViewDown method is not triggered at the proper time?

Update 1

I added a debug print to each function to check the order of calling, this is what I get:

-[KeyboardAnimationViewController moveViewUp]
__-[KeyboardAnimationViewController moveViewUp]_block_invoke_1 <-- scroll up animation
-[KeyboardAnimationViewController switchToNextView:]
-[KeyboardAnimationViewController moveViewDown]
__-[KeyboardAnimationViewController moveViewDown]_block_invoke_1 <-- scroll down animation

Even if I explicitly move the view down before the transition like this

- (IBAction)switchToNextView:(id)sender {
    // [self presentModalViewController:nextViewController animated:YES];
    NSLog(@"%s", __PRETTY_FUNCTION__);
    if (self.view.frame.origin.x < 0)
        [self moveViewDown];
    [UIView transitionFromView:self.view
                        toView:self.nextView
                      duration:1.0
                       options:UIViewAnimationOptionTransitionFlipFromRight
                    completion:nil];
}

I get exactly the same log.

Update 2

I've experimented some more and made following conclusions:

  1. If I call moveViewDown or resignFirstResponder: explicitly, the animation is postponed until the end of current run loop, when all pending animations actually start to play. Though the animation block logs to the console immediately -- seems strange to me!
  2. The method transitionFromView:toView:duration:options:completion: (perhaps transitionWithView:duration:options:animations:completion: too, didn't check this one) apparently makes a snapshot of the "from-view" and the "to-view" and creates an animation using these snapshots solely. Since the scrolling of the view is postponed, the snapshot is made when the view is still offset. The method somehow disregards even the UIViewAnimationOptionAllowAnimatedContent option.
  3. I managed to get the desired effect using any of animateWithDuration: ... completion: methods. These methods seems to disregard transition options like UIViewAnimationOptionTransitionFlipFromRight.
  4. The keyboard starts hiding (implicitly) and sends corresponding notification when removeFromSuperview is called.

Please correct me if I'm wrong somewhere.

回答1:

If you are trying to simulate the modal behavior without UIViewController I guess you want your next view to show up from the bottom of the screen right?. correct me if I am wrong. If you want such an animation you can try a work-around where you change the frame of the next view within an animation block such that it appears as if its similar to presentModalViewController