iOS 7學習:定制View Controllers之間的切換動畫 – iPhone手機開發技術文章 iPhone軟體開發教學課程

iOS 7新出瞭一個非常好玩的特性,就是View Controllers之間的切換動畫,開發者也可以在程序中自行定制切換動畫,例如翻轉視圖、縮放視圖、旋轉視圖等等。另一方面,對於iPhone的使用來說,通過pan手勢pop回到之前的視圖,使用起來無疑是非常方便的。最開始使用這個特性時感覺非常驚喜。

本文就來說說如何定制我們自己的動畫切換方案。

首先定義一個實現UINavigationControllerDelegate協議的類NavigationPerfomer。

故事板非常簡單,就是一個UINavigationController和兩個View Controller。但是要在UINavigationController中加入一個Object(這個貌似是Xcode 5的新特性吧,以前比較少用故事板,所以不是很確定),該對象的類為NavigaitonPerfomer類:

隨後要為NavigationPerformer添加一個outlet:當前的UINavigatinController。並將UINavigationController的委托設為NavigationPerformer類。vcD4KPHA+PGJyPgo8L3A+CjxwPrnKysKw5dbQxuTL+7XEVmlldyBDb250cm9sbGVy0rvH0NXVvsmjrNXiwO/X9rXDt8ezo7rDo6zU2szhuanBy9DCtcS2r7utvdO/2tauuvOjrNTaVUlOYXZpZ2F0aW9uQ29udHJvbGxlcsDg1tDKtc/W1eLQqb3Tv9qyorK7u+HTsM/stb3G5Mv7tcRWaWV3IENvbnRyb2xsZXK1xMTayN2hozwvcD4KPHA+vdPXxbao0uXBvbj2tq+7rcDgo7pQdXNoQW5pbWF0aW9uus1Qb3BBbmltYXRpb26jrLfWsfC21NOmUHVzaLrNUG9wtq+7raGjPC9wPgo8cD60+sLryOfPwqO6PC9wPgo8cD5QdXNoQW5pbXRhaW9uwOCjujwvcD4KPHA+PC9wPgo8cHJlIGNsYXNzPQ==”brush:java;”>#import

@interface PushAnimation : NSObject

@end

#pragma mark - UIViewControllerAnimatedTransitioning Delegate

/* 動畫持續的時間 */
- (NSTimeInterval)transitionDuration:(id)transitionContext {
    return 1;
}

/* 動畫的內容 */
- (void)animateTransition:(id)transitionContext {
    UIViewController *desController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController *srcController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    [[transitionContext containerView] addSubview:desController.view];
    desController.view.alpha = 0.0;
    
    [UIView animateWithDuration:[self transitionDuration:transitionContext]
                     animations:^{
                         srcController.view.transform = CGAffineTransformMakeTranslation(-srcController.view.bounds.size.width, 0.0);
                         desController.view.alpha = 1.0;
                     }
                     completion:^(BOOL finished) {
                         srcController.view.transform = CGAffineTransformIdentity;
                         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                     }];
}

動畫類必須實現UIViewControllerAnimatedTransitioning協議。

協議中的兩個必選方法:

// This is used for percent driven interactive transitions, as well as for container controllers that have companion animations that might need to
// synchronize with the main animation. 
- (NSTimeInterval)transitionDuration:(id )transitionContext;
// This method can only  be a nop if the transition is interactive and not a percentDriven interactive transition.
- (void)animateTransition:(id )transitionContext;

第一個方法指定動畫持續的時間,以秒為單位。

第二個方法定制動畫的內容。通過transitionContext這個上下文環境可以獲取源視圖控制器,目標視圖控制器:

    UIViewController *desController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController *srcController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

在進行動畫前,先要將desController的視圖添加到當前容器視圖中。

+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion NS_AVAILABLE_IOS(4_0); // delay = 0.0, options = 0

該方法才是真正定義動畫內容的關鍵,duration參數指定動畫持續的時間,animations代碼塊給出要執行的變換,當動畫完成後調用completion代碼塊通知transition context,在這裡可以進行如設置視圖的alpha、還原仿射變換等動作。

這裡Push Animation的動畫方案是將srcViewController自左向右劃出窗口。

另外一個是PopAnimation類:

@interface PopAnimation : NSObject 

@end

#pragma mark - UIViewControllerAnimatedTransitioning Delegate

- (NSTimeInterval)transitionDuration:(id)transitionContext {
    return 1;
}

- (void)animateTransition:(id)transitionContext {
    UIViewController *desController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController *srcController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    [[transitionContext containerView] addSubview:desController.view];
    desController.view.alpha = 0.0;
    
    [UIView animateWithDuration:[self transitionDuration:transitionContext]
                     animations:^{
                         srcController.view.transform = CGAffineTransformMakeScale(-1.0, 1.0);
                         desController.view.alpha = 1.0;
                     }
                     completion:^(BOOL finished) {
                         srcController.view.transform = CGAffineTransformIdentity;
                         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                     }];
}

@end

這裡pop的動畫方案是翻轉srcController的視圖。

接著是關鍵的NavigationPerformer類:

#import 

@class PushAnimation;
@class PopAnimation;

@interface NavigationPerfomer : NSObject 

@property (weak, nonatomic) IBOutlet UINavigationController *navigationController;

@property (strong, nonatomic) PushAnimation *pushAnimation;

@property (strong, nonatomic) PopAnimation *popAnimation;

@property (strong, nonatomic) UIPercentDrivenInteractiveTransition *interactionController;

@end

註意,interactionController類非常有用,隻有通過該類才能使視圖響應用戶的手勢輸入。

首先看看UINavigationControllerDelegate的方法:

#pragma mark - UINavigationController Delegate

- (id)navigationController:(UINavigationController *)navigationController
                                  animationControllerForOperation:(UINavigationControllerOperation)operation
                                               fromViewController:(UIViewController *)fromVC
                                                 toViewController:(UIViewController *)toVC
{
    if (operation == UINavigationControllerOperationPush) {
        return self.pushAnimation;
    }
    else if (operation == UINavigationControllerOperationPop) {
        return self.popAnimation;
    }
    
    return nil;
}

- (id)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id)animationController {
    return self.interactionController;
}

第一個方法指定不同的動作下的動畫方案,在返回對象時,這些對象必須實現UIViewControllerAnimatedTransitioning協議。

第二個方法指定提供交互性的控制器對象,如果返回nil,那麼視圖將無法響應用戶的手勢,如滑動等。

下面為導航控制器的視圖添加一個Pan手勢:

- (void)awakeFromNib {
    // 在導航控制器的視圖上添加pan手勢
    UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [self.navigationController.view addGestureRecognizer:panGesture];
    
    // 初始化動畫方案
    self.pushAnimation = [[PushAnimation alloc] init];
    self.popAnimation  = [[PopAnimation alloc] init];
}

- (void)pan:(UIPanGestureRecognizer *)pan {
    UIView *view = self.navigationController.view;
    if (pan.state == UIGestureRecognizerStateBegan) { // 隻有從左向右滑動的手勢才作出響應:pop
        CGPoint location = [pan locationInView:view];
        if (location.x  1) {
            self.interactionController = [[UIPercentDrivenInteractiveTransition alloc] init];
            [self.navigationController popViewControllerAnimated:YES];
        }
    }
    else if (pan.state == UIGestureRecognizerStateChanged) { // 以當前的位移作為進度執行動畫
        CGPoint translation = [pan translationInView:view];
        CGFloat progress = fabs(translation.x / CGRectGetWidth(view.bounds));
        [self.interactionController updateInteractiveTransition:progress];
    }
    else if (pan.state == UIGestureRecognizerStateEnded) {
        if ([pan velocityInView:view].x > 0) {
            [self.interactionController finishInteractiveTransition];
        }
        else { // 如果手勢的終點相對於起點在x正向上的位移小於0,即為取消手勢
            [self.interactionController cancelInteractiveTransition];
        }
        self.interactionController = nil; // 最後必須將interactionController清空,確保不會影響到後面的動畫執行
    }
}

在pan:方法的執行過程中,程序會根據手勢位移的百分比作為進度播放動畫。

在切換動畫完畢後,一定要將interactionController設為nil,因為某些動畫是不需要手勢交互的,這樣會帶來奇怪的行為。

最後是一些運行結果:

Push時的效果:

使用Pan手勢Pop時的效果:

通過這個新的Transition接口我們可以實現一些比較好玩的動畫,Demo已經上傳,有興趣的可以下載看看。

這部分還是挺好玩的,Just enjoy.

參考資料:

iOS 7 新特性:視圖控制器切換API

objc.io #5 View Controller Transitions

objcio / issue5-view-controller-transitions

發佈留言