1. 程式人生 > >iOS開發 tabbar自定義轉場動畫

iOS開發 tabbar自定義轉場動畫

1.小記

  • 關於自定義轉場動畫,只要你理清他的”套路”,你就可以隨心所欲地自定義了.
  • 大體思路就是:遵守對應的代理協議,然後設定對應的代理,實現代理方法,這個代理方法要返回的值就是你要實現的動畫.(如果返回nil,就是預設效果)
  • 以UITabBarController為例的簡單轉場動畫demo地址  gitHub地址

2.基本介紹

在此介紹一下基本知識:

1.在哪裡寫我們自定義的動畫.

蘋果給我們提供了UIViewControllerAnimatedTransitioning協議,這個協議提供了我們需要的介面,遵守這個協議的物件實現動畫基本內容. 
讓我們跳轉進去看看都有什麼:

@protocol UIViewControllerAnimatedTransitioning <NSObject>

// 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:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
// This method can only  be a nop if the transition is interactive and not a percentDriven interactive transition.
// 這個介面返回的值為具體動畫內容,也就是說,自定義的動畫操作都通過這個介面來實現
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;

@optional

/// A conforming object implements this method if the transition it creates can
/// be interrupted. For example, it could return an instance of a
/// UIViewPropertyAnimator. It is expected that this method will return the same
/// instance for the life of a transition.
- (id <UIViewImplicitlyAnimating>) interruptibleAnimatorForTransition:(id <UIViewControllerContextTransitioning>)transitionContext NS_AVAILABLE_IOS(10_0);

// This is a convenience and if implemented will be invoked by the system when the transition context's completeTransition: method is invoked.
- (void)animationEnded:(BOOL) transitionCompleted;

@end
1.通過註釋的解釋,我們能夠知道,遵守UIViewControllerAnimatedTransitioning協議的物件就可以實現我們自定義的動畫.
2.通常我們會自定義NSObject的子類,遵守UIViewControllerAnimatedTransitioning協議,然後實現協議方法來自定義轉場動畫.
3.這個子類的物件就是我們的"自定義動畫".如果把自定義轉場動畫比作為做菜的話,那麼現在我們準備的就是食材.
  • 在這裡要對一些概念進行下解釋,避免在自定義動畫時蒙圈
1.From和To
在自定義轉場動畫的程式碼中,經常會出現fromViewController和toViewController。如果錯誤的理解它們的含義會導致動畫邏輯完全錯誤。
fromViewController表示當前檢視容器,toViewController表示要跳轉到的檢視容器。如果是從A檢視控制器present到B,則A是from,B是to。從B檢視控制器dismiss到A時,B變成了from,A是to。
2.Presented和Presenting
這也是一組相對的概念,它容易與fromView和toView混淆。簡單來說,它不受present或dismiss的影響,如果是從A檢視控制器present到B,那麼A總是B的presentingViewController, B總是A的presentedViewController。

2.在哪裡用我們自定義的動畫.

這裡要介紹三個協議: 注意每個協議方法的返回值,都是遵守UIViewControllerAnimatedTransitioning的物件

1.協議一: UIViewControllerTransitioningDelegate
// 實現present/dismiss動畫的介面.
// 令我們需要自定義動畫的控制器遵守UIViewControllerTransitioningDelegate協議,並設定代理,實現協議方法,返回遵守UIViewControllerAnimatedTransitioning協議的類物件即可
// 在這裡需要清楚一點,假設由控制器A present 到B, A遵守UIViewControllerTransitioningDelegate協議,則設定B.transitioningDelegate = A,並設定B.modalPresentationStyle = UIModalPresentationCustom(或UIModalPresentationFullScreen);
// 一定要設定modalPresentationStyle,不然還是預設的轉場動畫.

// present動畫
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;

// dismiss動畫
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;
# modalPresentationStyl
 // 這是一個列舉型別,表示present時動畫的型別。
 // 其中可以自定義動畫效果的只有兩種:FullScreen和Custom,兩者的區別在於FullScreen會移除fromView,而Custom不會。
2.協議二:UINavigationControllerDelegate
// 實現push/pop動畫的介面
// 這裡同樣是要遵守協議,設定代理,實現協議方法.
// 注意這裡設定的是navigationController.delegate, self.navigationController.delegate = self.
// 我在其他的部落格中看到: (注意: 這裡的 self.navigationController.delegate = self 最好寫在當前控制器的viewDidAppear方法中, 不然會導致在此push時無動畫效果),為什麼會失效我還不清楚,希望讀者能夠找到並分享一下~
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                   animationControllerForOperation:(UINavigationControllerOperation)operation
                                                fromViewController:(UIViewController *)fromVC
                                                  toViewController:(UIViewController *)toVC
3.協議三:UITabBarControllerDelegate
// 實現tabBarController切換子控制器的動畫
// 還是老套路,遵守協議,設定代理,實現協議方法
// 只是這裡要設定tabBarController的代理,我的做法就是在UITabBarController的viewDidLoad方法裡設定代理: self.delegate = self;
- (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
            animationControllerForTransitionFromViewController:(UIViewController *)fromVC
                                              toViewController:(UIViewController *)toVC  NS_AVAILABLE_IOS(7_0);
  • 小結: 從上面三個協議的返回值能夠看出,返回的東西就是我們2.1自定義遵守UIViewControllerAnimatedTransitioning協議的類的物件.

3.轉場動畫的思路(純個人理解,起個拋磚引玉作用~)

  • 步驟一: 明確做哪種轉場動畫(做哪種菜,是魯菜,還是川菜?)
自定義present/dismiss動畫要遵守UIViewControllerTransitioningDelegate協議
自定義push/pop動畫要遵守UINavigationControllerDelegate協議
自定義tabbarController轉場動畫要遵守UITabBarControllerDelegate協議

以demo為例: 

// DMMainViewController.m檔案
@interface DMMainViewController ()<UITabBarControllerDelegate>// 遵守協議
@end
@implementation DMMainViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.delegate = self;// 設定代理
    UIViewController *vc = [[UIViewController alloc] init];
    vc.view.backgroundColor = [UIColor whiteColor];
    [self setChildchildViewController:vc index:0 title:@"我是A"];
    [self setChildchildViewController:[[UITableViewController alloc] init] index:1 title:@"我是B"];
    [self setChildchildViewController:[[UIViewController alloc] init] index:2 title:@"我是C"];
}
// 動畫 實現協議方法
- (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{
    return [[AnimationManager alloc] init];
}

這裡其實就是遵守協議,設定代理,實現協議方法.

  • 步驟二: 確定做哪樣轉場動畫(如果選擇了魯菜,是德州扒雞,還是紅燒大蝦?如果選擇了川菜,是四川火鍋,還是水煮魚?)
// AnimationManager.h檔案
// 自定義NSObject的子類,遵守UIViewControllerAnimatedTransitioning協議
@interface AnimationManager : NSObject<UIViewControllerAnimatedTransitioning>

@property (nonatomic, assign) KAnimationType type;
- (instancetype)initWithType:(KAnimationType)type;

@end
// AnimationManager.m檔案
#import "AnimationManager.h"
#import "DMNavigationViewController.h"

@interface AnimationManager ()
@end

@implementation AnimationManager

// 這個是動畫時長
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {

    return 0.5;
}

// 具體動畫,在這裡可以根據你的想象去實現你要的動畫效果了
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {

    // 獲取fromVc和toVc

    DMNavigationViewController *fromVc = (DMNavigationViewController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

    DMNavigationViewController *toVc = (DMNavigationViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

    UIView *fromV = fromVc.view;
    UIView *toV = toVc.view;


    // 轉場環境
    UIView *containView = [transitionContext containerView];
    containView.backgroundColor = [UIColor whiteColor];


    // 判斷滑動方向
    if (toVc.index > fromVc.index) {

        toV.frame = CGRectMake([UIScreen mainScreen].bounds.size.width, 0, containView.frame.size.width, containView.frame.size.height);

        [containView addSubview:toV];
        // 動畫
        [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{

            fromV.transform = CGAffineTransformTranslate(fromV.transform, -[UIScreen mainScreen].bounds.size.width,0);// containView.frame.size.height
            toV.transform = CGAffineTransformTranslate(toV.transform, -[UIScreen mainScreen].bounds.size.width, 0);

        } completion:^(BOOL finished) {

            [transitionContext completeTransition:YES];
        }];


    }else if (toVc.index < fromVc.index) {

        toV.frame = CGRectMake(- [UIScreen mainScreen].bounds.size.width, 0, containView.frame.size.width, containView.frame.size.height);

        [containView addSubview:toV];

        [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{

            fromV.transform = CGAffineTransformTranslate(fromV.transform, [UIScreen mainScreen].bounds.size.width,0);
            toV.transform = CGAffineTransformTranslate(toV.transform, [UIScreen mainScreen].bounds.size.width, 0);

        } completion:^(BOOL finished) {

            [fromV removeFromSuperview];
            [transitionContext completeTransition:YES];


        }];

    }
}
@end

# 這裡面就涉及到前面講的 1.From和To的關係,2.Presented和Presenting的關係.在2.1的底部有介紹

小結:

所謂的自定義轉場動畫,就是把系統預設的換成我們自己寫的而已,關鍵就是在這些協議裡.理清控制器與協議的關係.

簡單的畫了一個結構圖

架構圖

附: 以UITabBarController為例的簡單轉場動畫demo地址  gitHub地址

參考文章:iOS自定義轉場動畫iOS中應該知道的自定義各種Controller的轉場過渡動畫