iOS 轉場動畫探究(二)


這篇文章是接着第一篇寫的,要是有同行剛看到的話建議從前面第一篇看,這是第一篇的地址:iOS 轉場動畫探究(一)

接着上一篇寫的內容:

       上一篇iOS 轉場動畫探究(一)我們說到了轉場要素的第四點,把那個小實例解釋完,這篇還有一點我們接着總結:

       Demo的下載地址這里再發一次: 這里是Demo的下載地址

 

5、  轉場協調器協議 UIViewControllerTransitionCoordinator

       可以通過需要產生動畫效果的視圖控制器的transitionCoordinator屬性來獲取轉場協調器,轉場協調器只在轉場動畫的執行過程中存在。也正是因為有了UIViewControllerTransitionCoordinator ,我們才可在轉場動畫發生的同時並行執行其他的動畫。比如像我們第三個小例子里面后面半透明背景動畫,就是通過這個UIViewControllerTransitionCoordinator我們來做的,主要在 Modal 轉場和交互轉場取消時使用,其他時候很少用到,我們看看它里面的幾個方法:

// Any animations specified will be run in the same animation context as the
// transition. If the animations are occurring in a view that is a not
// descendent of the containerView, then an ancestor view in which all of the
// animations are occuring should be specified.  The completionBlock is invoked
// after the transition completes. (Note that this may not be after all the
// animations specified by to call complete if the duration is not inherited.)
// It is perfectly legitimate to only specify a completion block. This method
// returns YES if the animations are successfully queued to run. The completions
// may be run even if the animations are not. Note that for transitioning
// animators that are not implemented with UIView animations, the alongside
// animations will be run just after their animateTransition: method returns.
//
- (BOOL)animateAlongsideTransition:(void (^ __nullable)(id <UIViewControllerTransitionCoordinatorContext>context))animation
                        completion:(void (^ __nullable)(id <UIViewControllerTransitionCoordinatorContext>context))completion;

// This alternative API is needed if the view is not a descendent of the container view AND you require this animation
// to be driven by a UIPercentDrivenInteractiveTransition interaction controller.
- (BOOL)animateAlongsideTransitionInView:(nullable UIView *)view
                               animation:(void (^ __nullable)(id <UIViewControllerTransitionCoordinatorContext>context))animation
                              completion:(void (^ __nullable)(id <UIViewControllerTransitionCoordinatorContext>context))completion;

// When a transition changes from interactive to non-interactive then handler is
// invoked. The handler will typically then do something depending on whether or
// not the transition isCancelled. Note that only interactive transitions can
// be cancelled and all interactive transitions complete as non-interactive
// ones. In general, when a transition is cancelled the view controller that was
// appearing will receive a viewWillDisappear: call, and the view controller
// that was disappearing will receive a viewWillAppear: call.  This handler is
// invoked BEFORE the "will" method calls are made.
- (void)notifyWhenInteractionEndsUsingBlock: (void (^)(id <UIViewControllerTransitionCoordinatorContext>context))handler NS_DEPRECATED_IOS(7_0, 10_0,"Use notifyWhenInteractionChangesUsingBlock");

// This method behavior is identical to the method above. On 10.0, however, the behavior has
// changed slightly to account for the fact that transitions can be interruptible. For interruptible transitions
// The block may be called multiple times. It is called each time the transition moves from an interactive to a 
// non-interactive state and vice-versa. The block is now also retained until the transition has completed.
- (void)notifyWhenInteractionChangesUsingBlock: (void (^)(id <UIViewControllerTransitionCoordinatorContext>context))handler NS_AVAILABLE_IOS(10_0);

 

       結合上面的英文注釋看看這幾個方法,在博客上看到關於這個協議的翻譯,肯定翻譯,再吧一些地方自己總結了一下,直接寫出來,對照着上面的理解一下這個協議:

       1、 你可以使用一個轉場協調器對象執行一個與轉場相關的任務,它將分離動畫控制器正在做的事。在轉場期間,動畫控制器對象負責把視圖控制器的內容呈現在屏幕上,但是可能也有一些其他的可視元素同樣需要被展示。比如,一個顯示控制器可能想執行顯示或者使一些裝飾視圖消失從視圖控制器內容里分離出的動畫。這種情況下,可以使用轉場協調器來執行這些動畫。

       2、轉場協調器和動畫控制器對象一塊工作,確保任何額外動畫被執行在同樣的動畫組中,就像轉場動畫一樣。在一樣的組擁有動畫,意味着它們在同樣的時間執行,並且可以響應一個動畫控制器對象提出的任何時間改變。這些時間調整會自動發生,不需要寫額外的代碼在你的項目中。

       3、使用轉場協調器處理視圖層次動畫比在viewWillappear:方法中做出同樣的改變,或者相同的方法在你的視圖控制器中要好很多。你用這個協議中的方法所注冊的block會確保執行一樣的轉場動畫。更重要的是,轉場協調器會提供重要的信息關於轉場的狀態,比如是否它會被取消,對於你的動畫block而言,通過

UIViewControllerTransitionCoordinatorContext對象。

       4、除了在轉場期間執行注冊動畫,你可以調用notifyWhenInteractionChangesUsingBlock: 方法注冊一個block來清理和用戶交互的轉場動畫。清理非常重要,當用戶取消轉場交互時,當取消的時候,你需要返回一個原始的視圖層次狀態,就像之前轉場存在的一樣。

 

我們在協議的最上面會看到這樣一句話:

 

        翻譯說明:一個采用UIViewControllerTransitionCoordinator協議的對象可以給控制器轉場動畫提供相關支持。一般情況下,你不需要采用這個協議在自己定義的類中。當presentation/dismissal一個視圖控制器時,UIKit會自動創建一個轉場協調器對象,並且給視圖控制器的transitionCoordinator屬性賦值(這一點在接下來的實例中,你會看的到的),這個轉場協調器對象是短暫的,並且延續到轉場動畫的結束。

       說第三個小例子之前我們還得熟悉一下這個:UIPresentationController,它提供了四個函數來定義present和dismiss動畫開始前后的操作:

       1、presentationTransitionWillBegin: present將要執行時

       2、presentationTransitionDidEnd:    present執行結束后

       3、dismissalTransitionWillBegin:    dismiss將要執行時

       4、dismissalTransitionDidEnd:         dismiss執行結束后

      這四個方法在我們的實例中有用到,我們在下面代碼里面還會說。

 

EXAMPLE-THREE:

 

看上面效果圖的第三個實例:

      在第三個Demo中,也就是底部卡片的呈現形式中,我們把UIViewControllerTransitioningDelegate和UIViewControllerAnimatedTransitioning都寫在了CustomPresentationController當中,這個CustomPresentationController就是集成與我們前面提到過的UIPresentationController,這個UIPresentationController前面提到的時候說的什么可以回憶一下,再在代碼中去理解:

      從初始化方法開始了解,說說我們需要注意的地方:

1、初始化

/**
 初始化

 @param  presentedViewController  presentedViewController  跳轉到這個控制器
 @param  presentingViewController presentingViewController 由這個控制器開始跳轉
 @return return value description
 */
- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(nullable UIViewController *)presentingViewController{
 
        self =[super initWithPresentedViewController:presentedViewController presentingViewController:presentingViewController];
        if (self) {
                
                // 自定義modalPresentationStyle
                presentedViewController.modalPresentationStyle= UIModalPresentationCustom;
                
        }
        return self;
}

這里主要的只有一點:presentedViewController.modalPresentationStyle= UIModalPresentationCustom

 

2、presentationTransitionWillBegin 這個方法

     在這個方法里面背景圖的動畫就是在我們第五點強調的UIViewControllerTransitionCoordinator當中

/* present將要執行時 */
- (void)presentationTransitionWillBegin
{
       
        // 設置presentationWrappingView和dimmingView的UI效果
        UIView * presentedViewControllerView = [super presentedView];
        presentedViewControllerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        
        {
                // 
                UIView *presentationWrapperView = [[UIView alloc] initWithFrame:self.frameOfPresentedViewInContainerView];
                presentationWrapperView.layer.shadowOpacity = 0.44f;  //設置陰影的透明度(0~1之間,0表示完全透明)
                presentationWrapperView.layer.shadowRadius  = 13.f;   //設置陰影的圓角
                
                //設置陰影的偏移量,如果為正數,則代表為往右邊偏移
                presentationWrapperView.layer.shadowOffset  = CGSizeMake(0, -6.f);
                self.presentationWrappingView = presentationWrapperView;
                
                // 圓角View
                UIView *presentationRoundedCornerView = [[UIView alloc] initWithFrame:UIEdgeInsetsInsetRect(presentationWrapperView.bounds, UIEdgeInsetsMake(0, 0, -CORNER_RADIUS, 0))];
                
                // autoresizingMask 這個屬性 自動調整與父視圖之間的邊界距離
                presentationRoundedCornerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
                presentationRoundedCornerView.layer.cornerRadius = CORNER_RADIUS;
                presentationRoundedCornerView.layer.masksToBounds = YES;
                [presentationRoundedCornerView addSubview:presentedViewControllerView];
                
                //*** presentedViewControllerView
                presentedViewControllerView.frame = UIEdgeInsetsInsetRect(presentationRoundedCornerView.bounds, UIEdgeInsetsMake(0, 0, CORNER_RADIUS, 0));

                // ******************
                [presentationWrapperView addSubview:presentationRoundedCornerView];

        }
        
        {
                // 背景圖
                UIView *dimmingView = [[UIView alloc] initWithFrame:self.containerView.bounds];
                dimmingView.backgroundColor = [UIColor blackColor];
                dimmingView.opaque = NO;
                dimmingView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
                [dimmingView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dimmingViewTapped:)]];
                self.dimmingView = dimmingView;
                [self.containerView addSubview:dimmingView];
                
                id<UIViewControllerTransitionCoordinator> transitionCoordinator = self.presentingViewController.transitionCoordinator;
                
                self.dimmingView.alpha = 0.f;
                [transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
                        
                        self.dimmingView.alpha = 0.5f;
                        
                } completion:NULL];
        }
}

剩下的UIViewControllerAnimatedTransitioning和UIViewControllerTransitioningDelegate里面的東西我們就不在重復去說。

 

我們多看幾個例子:

EXAMPLE-FOUR

      這個彈性POP的例子主要是根據 CGAffineTransform 這個屬性來展開的,你要了解這個就比較好寫了,主要的全都在代碼注釋里面,效果圖就在下面:

      這個例子我就強調下面這一個地方,下面這段代碼:

 BOOL isPresent   = (toViewController.presentingViewController == fromViewController);

        UIView *tempView = nil;
        if (isPresent) {
                
           tempView = [fromViewController.view snapshotViewAfterScreenUpdates:NO];
                
           tempView.frame = fromViewController.view.frame;
           fromViewController.view.hidden = YES;
           [contextView addSubview:tempView];
           [contextView addSubview:toViewController.view];
           toViewController.view.frame = CGRectMake(0, contextView.frame.size.height, contextView.frame.size.width, 400);
                
        }else{
        
                //參照present動畫的邏輯,present成功后,containerView的最后一個子視圖就是截圖視圖,我們將其取出准備動畫
                NSArray *subviewsArray = contextView.subviews;
                tempView = subviewsArray[MIN(subviewsArray.count, MAX(0, subviewsArray.count - 2))];
        }

      注意一下這個方法: snapshotViewAfterScreenUpdates: 剩下的看代碼就OK了,這幾個例子我感覺其實帶的框架都是一樣的,不一樣的就是里面的動畫具體的實現,先看看我們4、5、6說的這幾個Demo的效果:

 

 

EXAMPLE-FIVE

      圓點擴散這個Demo主要的就是靈活的使用了UIBezierPath 和 CABasicAnimation,其實還要掌握了轉場的本質,感覺剩下的真的就看你能想到哪里了!哈哈.....

      這個Demo所有的核心都在下面兩個方法里面:

      -(void)presentViewController:(id <UIViewControllerContextTransitioning>)transitionContext

      -(void)dismissViewController:(id <UIViewControllerContextTransitioning>)transitionContext

      具體的代碼我就不再說了,代碼上面的注釋是足夠了的,很詳細.....還是看代碼吧。

 

EXAMPLE-SIX

      導航控制器的轉場

      最后的這個翻頁效果的Demo,其實你看着它像3D的感覺,你想起了 CATransform3D 感覺就夠了,通過下面這個方法來開始我們的導航轉場:

-(void)presentNextControllerClicked{
    
    // 既然這里是導航控制器的轉場,就要給導航設置代理。然后在第二個控制器遵守相應的協議之后,判斷
    // 是Push還是Pop,執行相應的動畫
    PageToViewController * pushVC = [PageToViewController new];
    self.navigationController.delegate = pushVC;
    [self.navigationController pushViewController:pushVC animated:YES];
}

       下面要注意的就是導航操作的判斷,是Push還是Pop,我們就在導航代理的下面方法里面判斷,重點應用的就是代理方法里面的operation參數:

#pragma mark - UINavigationControllerDelegate
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{

    //分pop和push兩種情況分別返回動畫過渡代理相應不同的動畫操作
    return [[PageAnimation alloc]initWith:operation == UINavigationControllerOperationPush ? Push_type : Pop_type];

}

        

      注意到上面說的兩點,剩下的又回到我們最開始的--動畫了!動畫部分的代碼就不粘貼出來占用篇幅了,還是那句下載下來自己去看看! 

      你看着上面給的效果圖,要有興趣就去下載代碼看看,源碼當中還是有很多的細節的,我也加了注釋,希望上面所有的東西以及源碼里面的內容能幫助到大家!

最后:

      這個上面的暫時就告一段落了,后面有新的動向我會在接着更新,下面是我學習的過程中,看過的相關的博客!感謝作者......

      Demo的下載地址這里再發一次: 這里是Demo的下載地址

      iOS自定義轉場動畫實戰講解

      iOS自定義轉場動畫

      iOS視圖控制器轉場詳解

      WWDC 2013 Session筆記 - iOS7中的ViewController切換

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM