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)畫