仿iOS 9系統(tǒng)日歷動畫

先看看系統(tǒng)日歷的Transition效果

系統(tǒng)日歷效果

這里面包含了好幾個動畫

  1. 在push的時候错邦,前面一個Controller從中間截斷店煞;后面的Conntroller則從中間位置上升蘑志。
  2. 在pop的時候手蝎,以相反的動畫顯示
  3. navigationBar包含了一個淡入淡出的效果

得益于iOS 7新增UIViewControllerAnimatedTransitioning趁餐,用現(xiàn)有的Core Animation就可用輕松實現(xiàn)散怖。值得一提的是菇绵,《iOS 7 by Tutorials》對這方面點講解非常透徹肄渗;還有VCTransitionsLibrary這個庫,實現(xiàn)了很多特效,我很多代碼就是參考他里面的實現(xiàn)。更深層次疤苹,WWDC 2013 #218——Custom Transitions Using View Controllers腺逛,然而對初學(xué)者并不適用。

我的效果

要實現(xiàn)一個自定義的Transition,概括起來有3個步驟:

  1. 創(chuàng)建一個animation controller。這個controller只是一個普通NSObject對象,但要實現(xiàn)UIViewControllerAnimatedTransitioning協(xié)議硝桩,這個協(xié)議有一個重要的方法- (void)animateTransition:(id<UIViewControllerContextTransitioning> _Nonnull)transitionContext,參數(shù)transitionContext對象包含了所有動畫需要的UIView
  2. 設(shè)置self.navigationController.delegate枚荣。它的作用就是碗脊,在push、pop橄妆、present Controller時衙伶,返回上面的animation controller,替換系統(tǒng)默認(rèn)的效果
  3. 最后害碾,在animation controller實現(xiàn)動畫矢劲。

animation controller是transition的核心』潘妫可以理解它是一個臨時controller芬沉,在這個里面有一個containerView,以及前阁猜、后controller的view丸逸。把前、后的view放在containerView里做動畫剃袍,這就是Transition黄刚。

下面從0開始,一步一步完成這個自定義Transition民效。

首先建一個Single View的工程calenderAnimator隘击。我不太習(xí)慣用storyboard,一般我會刪掉它研铆,然后在AppDelegate.m加上下面代碼

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    ViewController *mainViewController = [[ViewController alloc] init];
    
    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:mainViewController];
    
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    [self.window setRootViewController:navigationController];
    [self.window setBackgroundColor:[UIColor whiteColor]];
    [self.window makeKeyAndVisible];
    
    return YES;
}

增加一個UINavigationController,方便實現(xiàn)push和pop動作州叠。然后在ViewConrtoller.m里添加代碼

- (void)viewDidLoad {
    [super viewDidLoad];
    CGFloat r = RND_COLOR;
    CGFloat g = RND_COLOR;
    CGFloat b = RND_COLOR;
    self.view.backgroundColor = [UIColor colorWithRed:r green:g blue:b alpha:1];
    
    self.title = [NSString stringWithFormat:@"#%0X%0X%0X", (int)(r*255), (int)(g*255), (int)(b*255)];
    
    UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"CatInBin"]];
    imageView.center = self.view.center;
    [self.view addSubview:imageView];
    
}

- (void)viewDidAppear:(BOOL)animated
{
    NSUInteger count = self.navigationController.viewControllers.count;
    if (count > 1) {
        self.navigationController.navigationBar.tintColor = self.navigationController.viewControllers[count - 2].view.backgroundColor;
    }
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self.navigationController pushViewController:[ViewController new] animated:YES];
}

給ViewController加上隨機背景色和一張圖片棵红。任意點擊一次就push一個新的ViewController。設(shè)置navigationController的tintColor咧栗,方便觀察上一個ViewController的背景色逆甜。
到目前為止虱肄,這些代碼都是我們熟悉的操作。Transition效果也是默認(rèn)的左右滑入交煞,下面就來實現(xiàn)這3個步驟咏窿。

步驟1:創(chuàng)建CalenderAnimationController

@interface CalenderAnimationController : NSObject <UIViewControllerAnimatedTransitioning>

@property (nonatomic, assign) BOOL reverse;

@end
@implementation CalenderAnimationController

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
    return 1.0;
}

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIView *toView = toVC.view;
    UIView *fromView = fromVC.view;
    
    if(self.reverse){
        [self executeReverseAnimation:transitionContext fromVC:fromVC toVC:toVC fromView:fromView toView:toView];
    } else {
        [self executeForwardsAnimation:transitionContext fromVC:fromVC toVC:toVC fromView:fromView toView:toView];
    }
}
@end

reverse屬性用于區(qū)分push還是pop,對應(yīng)不同的動畫實現(xiàn)素征。CalenderAnimationController實現(xiàn)了兩個方法集嵌。transitionDuration:返回的時動畫時長;animateTransition:是動畫的回調(diào)方法御毅,這里我們通過2個key已經(jīng)拿到了fromView和toView根欧。

步驟2:設(shè)置delegate

在ViewController加上代碼

@interface ViewController () <UIViewControllerTransitioningDelegate, UINavigationControllerDelegate>

@property CalenderAnimationController *animationController;

@end

@implementation ViewController

- (void)viewDidLoad {
    //保持原樣
    self.animationController = [[CalenderAnimationController alloc] init];
}

- (void)viewDidAppear:(BOOL)animated
{
    self.navigationController.delegate = self;
    //保持原樣
}
- (id<UIViewControllerAnimatedTransitioning>) navigationController:(UINavigationController *)navigationController
                                   animationControllerForOperation:(UINavigationControllerOperation)operation
                                                fromViewController:(UIViewController *)fromVC
                                                  toViewController:(UIViewController *)toVC
{
    self.animationController.reverse = (operation == UINavigationControllerOperationPop);
    return self.animationController;
}
@end

viewDidAppear:設(shè)置navigationController.delegate = self。不能在viewDidLoad設(shè)置端蛆,因為所有的ViewController共享一個navigationController凤粗,當(dāng)pop出去的controller被回收時,navigationController.delegate就會被置為nil今豆,也就沒有了動畫嫌拣。
ViewController新加了一個方法

- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                   animationControllerForOperation:(UINavigationControllerOperation)operation
                                                fromViewController:(UIViewController *)fromVC
                                                  toViewController:(UIViewController *)toVC  NS_AVAILABLE_IOS(7_0);

這個方法返回前面創(chuàng)建出來的animationController,并通過operation得到當(dāng)前的操作類型呆躲。步驟2的主要目的异逐,就是讓系統(tǒng)回調(diào)這個方法。

步驟3:實現(xiàn)動畫

前面的所有工作只是鋪墊歼秽,為的是讓系統(tǒng)使用我們自己實現(xiàn)的動畫应役,而非默認(rèn)動畫。我們要的動畫有3個:1燥筷、前面的View分為兩半對開移動箩祥;2、后面的View從中間開始向上移動肆氓;3袍祖、導(dǎo)航條的Fade效果。
動畫3其實不需要寫任何代碼谢揪,當(dāng)我們接管了系統(tǒng)的transition蕉陋,導(dǎo)航條就自動有這個效果。動畫2用簡單的隱式動畫就能完成拨扶。動畫1沒有對應(yīng)的隱式動畫凳鬓,麻煩一點。如果把前面的View拆分為2個單獨的View患民,各自完成一個移動的動畫就簡單很多了缩举。而拆分的過程,用UIView的snapshot來截屏即可。

- (void)executeForwardsAnimation:(id<UIViewControllerContextTransitioning>)transitionContext fromVC:(UIViewController *)fromVC toVC:(UIViewController *)toVC fromView:(UIView *)fromView toView:(UIView *)toView
{
    UIView *containerView = [transitionContext containerView];
    
    [containerView addSubview:fromView];
    fromView.frame = CGRectOffset(toView.frame, toView.frame.size.width, 0);
    [containerView addSubview:toView];
    
    CGRect upRegion = CGRectMake(0, 0, fromView.frame.size.width, fromView.frame.size.height/2);
    UIView *upView = [fromView resizableSnapshotViewFromRect:upRegion afterScreenUpdates:NO withCapInsets:UIEdgeInsetsZero];
    upView.frame = upRegion;
    [containerView addSubview:upView];
    
    CGRect downRegion = CGRectMake(0, fromView.frame.size.height/2, fromView.frame.size.width, fromView.frame.size.height/2);
    UIView *downView = [fromView resizableSnapshotViewFromRect:downRegion afterScreenUpdates:NO withCapInsets:UIEdgeInsetsZero];
    downView.frame = downRegion;
    [containerView addSubview:downView];
    
    toView.frame = CGRectOffset(toView.frame, 0, toView.frame.size.height/2);

    NSTimeInterval duration = [self transitionDuration:transitionContext];
    
    [UIView animateWithDuration:duration
                          delay:0.0
                        options:UIViewAnimationOptionCurveEaseOut
                     animations:^{
                         upView.frame = CGRectOffset(upView.frame, 0, -upView.frame.size.height);
                         downView.frame = CGRectOffset(downView.frame, 0, downView.frame.size.height);

                         toView.frame = CGRectOffset(toView.frame, 0, -toView.frame.size.height/2);
                     }
                     completion:^(BOOL finished) {
                         [upView removeFromSuperview];
                         [downView removeFromSuperview];
                         fromView.frame = containerView.frame;
                         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                     }];
}

第一步仅孩,先獲得containerView托猩,再把fromView和toView添加進來。這一步是需要手動完成的辽慕,containerView默認(rèn)是沒有subview京腥。fromView我們先隱藏起來,等動畫完成了再顯示溅蛉。

接下來創(chuàng)建upView和downView公浪,利用iOS 7新加的- (UIView *)resizableSnapshotViewFromRect:(CGRect)rect afterScreenUpdates:(BOOL)afterUpdates withCapInsets:(UIEdgeInsets)capInsets,它可以截取UIView任意的一部分温艇,拷貝到另一個UIView因悲。
真正的動畫部分就只有upView、downView和toView的frame操作勺爱,3行代碼晃琳。等動畫完成后,upView和downView就沒有存在的意義琐鲁,從contrainerView移除卫旱。再調(diào)用[transitionContext completeTransition:![transitionContext transitionWasCancelled]],通知系統(tǒng)transition全部完成围段。至此顾翼,animation controller就完成了它的使命。
- (void)executeReverseAnimation:(id<UIViewControllerContextTransitioning>)transitionContext fromVC:(UIViewController *)fromVC toVC:(UIViewController *)toVC fromView:(UIView *)fromView toView:(UIView *)toView只是上一個動畫的反轉(zhuǎn)奈泪,在此就不貼代碼了适贸。

完整代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市涝桅,隨后出現(xiàn)的幾起案子拜姿,更是在濱河造成了極大的恐慌,老刑警劉巖冯遂,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蕊肥,死亡現(xiàn)場離奇詭異,居然都是意外死亡蛤肌,警方通過查閱死者的電腦和手機壁却,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來裸准,“玉大人展东,你說我怎么就攤上這事〕淳悖” “怎么了琅锻?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵卦停,是天一觀的道長。 經(jīng)常有香客問我恼蓬,道長,這世上最難降的妖魔是什么僵芹? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任处硬,我火速辦了婚禮,結(jié)果婚禮上拇派,老公的妹妹穿的比我還像新娘荷辕。我一直安慰自己,他們只是感情好件豌,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布疮方。 她就那樣靜靜地躺著,像睡著了一般茧彤。 火紅的嫁衣襯著肌膚如雪骡显。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天曾掂,我揣著相機與錄音惫谤,去河邊找鬼。 笑死珠洗,一個胖子當(dāng)著我的面吹牛溜歪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播许蓖,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼蝴猪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了膊爪?” 一聲冷哼從身側(cè)響起自阱,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蚁飒,沒想到半個月后动壤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡淮逻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年琼懊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片爬早。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡哼丈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出筛严,到底是詐尸還是另有隱情醉旦,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站车胡,受9級特大地震影響檬输,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜匈棘,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一丧慈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧主卫,春花似錦逃默、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瘩将,卻和暖如春吟税,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鸟蟹。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工乌妙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人建钥。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓藤韵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親熊经。 傳聞我的和親對象是個殘疾皇子泽艘,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

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