iOS 關(guān)于自定義轉(zhuǎn)場動(dòng)畫,以UITabBarController為例

1.小記

  • 關(guān)于自定義轉(zhuǎn)場動(dòng)畫,只要你理清他的"套路",你就可以隨心所欲地自定義了.
  • 大體思路就是:遵守對應(yīng)的代理協(xié)議,然后設(shè)置對應(yīng)的代理,實(shí)現(xiàn)代理方法,這個(gè)代理方法要返回的值就是你要實(shí)現(xiàn)的動(dòng)畫.(如果返回nil,就是默認(rèn)效果)
  • 以UITabBarController為例的簡單轉(zhuǎn)場動(dòng)畫demo地址 gitHub地址

2.基本介紹

在此介紹一下基本知識:

1.在哪里寫我們自定義的動(dòng)畫.

蘋果給我們提供了UIViewControllerAnimatedTransitioning協(xié)議,這個(gè)協(xié)議提供了我們需要的接口,遵守這個(gè)協(xié)議的對象實(shí)現(xiàn)動(dòng)畫基本內(nèi)容.
讓我們跳轉(zhuǎn)進(jìn)去看看都有什么:

@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.
// 這個(gè)接口返回的值為動(dòng)畫時(shí)長
 - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
// This method can only  be a nop if the transition is interactive and not a percentDriven interactive transition.
// 這個(gè)接口返回的值為具體動(dòng)畫內(nèi)容,也就是說,自定義的動(dòng)畫操作都通過這個(gè)接口來實(shí)現(xiàn)
- (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協(xié)議的對象就可以實(shí)現(xiàn)我們自定義的動(dòng)畫.
2.通常我們會自定義NSObject的子類,遵守UIViewControllerAnimatedTransitioning協(xié)議,然后實(shí)現(xiàn)協(xié)議方法來自定義轉(zhuǎn)場動(dòng)畫.
3.這個(gè)子類的對象就是我們的"自定義動(dòng)畫".如果把自定義轉(zhuǎn)場動(dòng)畫比作為做菜的話,那么現(xiàn)在我們準(zhǔn)備的就是食材.
  • 在這里要對一些概念進(jìn)行下解釋,避免在自定義動(dòng)畫時(shí)蒙圈
1.From和To
在自定義轉(zhuǎn)場動(dòng)畫的代碼中蒋畜,經(jīng)常會出現(xiàn)fromViewController和toViewController。如果錯(cuò)誤的理解它們的含義會導(dǎo)致動(dòng)畫邏輯完全錯(cuò)誤。
fromViewController表示當(dāng)前視圖容器父晶,toViewController表示要跳轉(zhuǎn)到的視圖容器弦追。如果是從A視圖控制器present到B吴趴,則A是from释涛,B是to涝婉。從B視圖控制器dismiss到A時(shí)哥力,B變成了from,A是to墩弯。
2.Presented和Presenting
這也是一組相對的概念吩跋,它容易與fromView和toView混淆。簡單來說渔工,它不受present或dismiss的影響锌钮,如果是從A視圖控制器present到B,那么A總是B的presentingViewController, B總是A的presentedViewController引矩。
2.在哪里用我們自定義的動(dòng)畫.

這里要介紹三個(gè)協(xié)議: 注意每個(gè)協(xié)議方法的返回值,都是遵守UIViewControllerAnimatedTransitioning的對象

1.協(xié)議一: UIViewControllerTransitioningDelegate
// 實(shí)現(xiàn)present/dismiss動(dòng)畫的接口.
// 令我們需要自定義動(dòng)畫的控制器遵守UIViewControllerTransitioningDelegate協(xié)議,并設(shè)置代理,實(shí)現(xiàn)協(xié)議方法,返回遵守UIViewControllerAnimatedTransitioning協(xié)議的類對象即可
// 在這里需要清楚一點(diǎn),假設(shè)由控制器A present 到B, A遵守UIViewControllerTransitioningDelegate協(xié)議,則設(shè)置B.transitioningDelegate = A,并設(shè)置B.modalPresentationStyle = UIModalPresentationCustom(或UIModalPresentationFullScreen);
// 一定要設(shè)置modalPresentationStyle,不然還是默認(rèn)的轉(zhuǎn)場動(dòng)畫.

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

// dismiss動(dòng)畫
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;
# modalPresentationStyl
 // 這是一個(gè)枚舉類型梁丘,表示present時(shí)動(dòng)畫的類型。
 // 其中可以自定義動(dòng)畫效果的只有兩種:FullScreen和Custom脓魏,兩者的區(qū)別在于FullScreen會移除fromView兰吟,而Custom不會通惫。
2.協(xié)議二:UINavigationControllerDelegate
// 實(shí)現(xiàn)push/pop動(dòng)畫的接口
// 這里同樣是要遵守協(xié)議,設(shè)置代理,實(shí)現(xiàn)協(xié)議方法.
// 注意這里設(shè)置的是navigationController.delegate, self.navigationController.delegate = self.
// 我在其他的博客中看到: (注意: 這里的 self.navigationController.delegate = self 最好寫在當(dāng)前控制器的viewDidAppear方法中, 不然會導(dǎo)致在此push時(shí)無動(dòng)畫效果),為什么會失效我還不清楚,希望讀者能夠找到并分享一下~
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                   animationControllerForOperation:(UINavigationControllerOperation)operation
                                                fromViewController:(UIViewController *)fromVC
                                                  toViewController:(UIViewController *)toVC

3.協(xié)議三:UITabBarControllerDelegate
// 實(shí)現(xiàn)tabBarController切換子控制器的動(dòng)畫
// 還是老套路,遵守協(xié)議,設(shè)置代理,實(shí)現(xiàn)協(xié)議方法
// 只是這里要設(shè)置tabBarController的代理,我的做法就是在UITabBarController的viewDidLoad方法里設(shè)置代理: self.delegate = self;
- (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
            animationControllerForTransitionFromViewController:(UIViewController *)fromVC
                                              toViewController:(UIViewController *)toVC  NS_AVAILABLE_IOS(7_0);

  • 小結(jié): 從上面三個(gè)協(xié)議的返回值能夠看出,返回的東西就是我們2.1自定義遵守UIViewControllerAnimatedTransitioning協(xié)議的類的對象.

3.轉(zhuǎn)場動(dòng)畫的思路(純個(gè)人理解,起個(gè)拋磚引玉作用~)

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

以demo為例:

// DMMainViewController.m文件
@interface DMMainViewController ()<UITabBarControllerDelegate>// 遵守協(xié)議
@end
@implementation DMMainViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.delegate = self;// 設(shè)置代理
    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"];
}
// 動(dòng)畫 實(shí)現(xiàn)協(xié)議方法
- (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{
    return [[AnimationManager alloc] init];
}

這里其實(shí)就是遵守協(xié)議,設(shè)置代理,實(shí)現(xiàn)協(xié)議方法.

  • 步驟二: 確定做哪樣轉(zhuǎn)場動(dòng)畫(如果選擇了魯菜,是德州扒雞,還是紅燒大蝦?如果選擇了川菜,是四川火鍋,還是水煮魚?)
// AnimationManager.h文件
// 自定義NSObject的子類,遵守UIViewControllerAnimatedTransitioning協(xié)議
@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

// 這個(gè)是動(dòng)畫時(shí)長
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
    
    return 0.5;
}

// 具體動(dòng)畫,在這里可以根據(jù)你的想象去實(shí)現(xiàn)你要的動(dòng)畫效果了
- (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;
    
    
    // 轉(zhuǎn)場環(huán)境
    UIView *containView = [transitionContext containerView];
    containView.backgroundColor = [UIColor whiteColor];
    
    
    // 判斷滑動(dòng)方向
    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];
        // 動(dòng)畫
        [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的關(guān)系,2.Presented和Presenting的關(guān)系.在2.1的底部有介紹

小結(jié):

所謂的自定義轉(zhuǎn)場動(dòng)畫,就是把系統(tǒng)默認(rèn)的換成我們自己寫的而已,關(guān)鍵就是在這些協(xié)議里.理清控制器與協(xié)議的關(guān)系.

簡單的畫了一個(gè)結(jié)構(gòu)圖

架構(gòu)圖

附: 以UITabBarController為例的簡單轉(zhuǎn)場動(dòng)畫demo地址 gitHub地址

參考文章:iOS自定義轉(zhuǎn)場動(dòng)畫, iOS中應(yīng)該知道的自定義各種Controller的轉(zhuǎn)場過渡動(dòng)畫

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末茂翔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子履腋,更是在濱河造成了極大的恐慌珊燎,老刑警劉巖惭嚣,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異悔政,居然都是意外死亡晚吞,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進(jìn)店門谋国,熙熙樓的掌柜王于貴愁眉苦臉地迎上來槽地,“玉大人,你說我怎么就攤上這事芦瘾“莆茫” “怎么了?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵近弟,是天一觀的道長缅糟。 經(jīng)常有香客問我,道長祷愉,這世上最難降的妖魔是什么窗宦? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮二鳄,結(jié)果婚禮上赴涵,老公的妹妹穿的比我還像新娘。我一直安慰自己订讼,他們只是感情好句占,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著躯嫉,像睡著了一般纱烘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上祈餐,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天擂啥,我揣著相機(jī)與錄音,去河邊找鬼帆阳。 笑死哺壶,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蜒谤。 我是一名探鬼主播山宾,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鳍徽!你這毒婦竟也來了资锰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤阶祭,失蹤者是張志新(化名)和其女友劉穎绷杜,沒想到半個(gè)月后直秆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鞭盟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年圾结,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片齿诉。...
    茶點(diǎn)故事閱讀 40,852評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡筝野,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出粤剧,到底是詐尸還是另有隱情遗座,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布俊扳,位于F島的核電站途蒋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏馋记。R本人自食惡果不足惜号坡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望梯醒。 院中可真熱鬧宽堆,春花似錦、人聲如沸茸习。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽号胚。三九已至籽慢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間猫胁,已是汗流浹背箱亿。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弃秆,地道東北人届惋。 一個(gè)月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像菠赚,于是被迫代替她去往敵國和親脑豹。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內(nèi)容