自定義視圖控制器轉場動畫[譯]

iOS動畫教學:自定義視圖控制器轉場動畫

Note from Ray:這是從iOS Animations by Tutorials Second Edition摘錄的一小節(jié),能從這篇文章中顯露出書籍講述的內容镣陕。教程基于iOS 9谴餐,Xcode 7和Swift 2。

是否當你想要顯示攝像視圖控制器呆抑,地址欄或者你自定義的場景控制器時岂嗓,你都是在調用相同的UIKit方法:presentViewController(_:animated:completion:)。這個方法“丟掉”當前的視圖控制器從而轉向其他的控制器鹊碍。

默認的轉場動畫簡單地從當前的視圖滑入一個新的視圖厌殉,下面這個圖展示了如何從當前聯(lián)系人列表滑入展示一個“新的聯(lián)系人”視圖控制器。

在這個教程里侈咕,你將會創(chuàng)建自定義轉場控制器用以替代默認的動畫公罕,使得教程的初始項目變得更加有生氣。

開始

你可以從初始項目這里下載耀销,解壓zip文件和打開Beginner Cook項目楼眷,選擇Main.Stroyboard開始教程:

第一個視圖控制器(ViewController)包含了app的標題,主要的描述還有一個scroll View在底部用于展示可用的藥草列表熊尉。

當點擊列表當中的任意一張圖片時罐柳,HerbDetailSViewController作為主要的控制器展示。這個視圖控制器圖片作為背景狰住,有標題张吉,文字描述和一些按鈕用作指示圖片的擁有者。

項目里ViewController.swift和HerbDetailsViewController.swift有足夠的代碼支撐這個小項目转晰。構建和運行項目你會看到如下的效果圖:

隨意點擊其中一張藥草的圖片芦拿,詳情頁面會通過標準的轉從動畫(從底部滑動)展示出來。這說明你的app是完好的查邢,但是你的藥草能夠展示得更好蔗崎!

你的工作是為你的app添加自定義的轉場使其綻放!你將會更換當前的動畫扰藕,使其變成一點擊藥草圖片即會伸展鋪滿整個屏幕如下圖這樣:

卷起的衣袖缓苛,戴上你開發(fā)者的圍裙,準備為你的app做一個自定義轉場控制器吧邓深!

在自定義場景過渡的背后

UIKit允許你自定義你的視圖控制器展示方式通過協(xié)議的模式未桥;你需要使你主控制器(或者其他你為了展示而創(chuàng)建的類)適應UIViewControllerTransitionDelegate.

每一次你展示一個新的視圖控制器,UIKit會向協(xié)議請求是否應該使用一個自定義的轉換芥备。這是你自定義轉場動畫的第一步冬耿,如下圖:

UIKit調用animationControllerForPresentedController(:_ presentingController:sourceController:); 如果這個方法返回nil,UIKit會使用內建的轉場方法萌壳。如果UIKit在這個方法接收到一個對象亦镶,UIKit會使用這個對象用作轉場日月。

在UIKit能夠使用自定義動畫控制器之前還有一些步驟:

UIKit在轉場時間內會首先請求你的動畫控制器(你可以理解為動畫設計者)然后會調用他的animateTransition(),這里就是進行你自定義動畫的關鍵步驟缤骨。

在animateTransition(),你會使用當前的視圖控制器和將要顯示的視圖控制器爱咬,你可以漸隱,縮放绊起,旋轉和任意操控存在和即將顯示的視圖精拟。

現(xiàn)在你已經學會了一些關于自定義轉場控制器的工作原理,你可以開始做一個你自己的轉場控制器了虱歪。

實現(xiàn)過渡協(xié)議

由于代理的任務是用于管理展示動畫設計者對象蜂绎,所以在你編寫代理的代碼之前你需要先創(chuàng)建一個動畫設計者的類。

從Xcode的主菜單選擇 File\New\File...然后選擇模版 iOS\Source\Cocoa Touch Class

設置新建類為NSObject的子類笋鄙,名稱為PopAnimator荡碾。

打開PopAnimator.swift然后使其遵循UIViewControllerAnimatedTransitioning protocol如下:

class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {

}

然后你會看到Xcode會向你抱怨,因為你還沒有實現(xiàn)代理的必須方法局装,所以接下來把它們實現(xiàn)。

添加以下方法到類內:

func transitionDuration(transitionContext: UIViewControllerContextTransitioning?)-> NSTimeInterval {
    return 0
}

返回0僅僅是作為一個轉場時間占位值劳殖,在后面你可以自行替換這個值铐尚。

繼續(xù)添加一下方法到類內:

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {

}

上面的函數(shù)指示你的動畫代碼;添加完之后Xcode不會再向你提示錯誤《咭觯現(xiàn)在你有了動畫設計者的類宣增,接下來該接續(xù)在視圖控制器實現(xiàn)另外一個協(xié)議方法。

打開ViewController.swift添加下列擴展:

extension ViewController: UIViewControllerTransitioningDelegate {

}

這段代碼指示出你的視圖控制器是遵循該協(xié)議的矛缨,待會你會添加一些方法的實現(xiàn)爹脾。

在類內找到didTapImage()這個方法,你會看到展現(xiàn)細節(jié)視圖控制器的代碼箕昭。herbDetails是這個新的視圖控制器的實例灵妨;你需要設置他的轉場協(xié)議代理給主視圖控制器。

在剛才的方法的最后一行添加這段代碼然后再presentViewController:

herbDetails.transitioningDelegate = self

現(xiàn)在每當ViewController展示細節(jié)視圖控制器時落竹,UIKit都會向它請求一個動畫設計者對象泌霍。但是,你現(xiàn)在還沒有實現(xiàn)UIViewControllerTransitioningDelegate的方法述召,所以目前為止還是使用默認的過渡方法朱转。

下一步就是創(chuàng)建一個動畫設計者對象,并當UIKit請求時返回給它积暖。

為ViewController添加一個新的屬性:

let transition = PopAnimator()

這是一個用于驅動你的視圖控制器動畫過渡的PopAnimator實例藤为。只要你希望的過渡的動畫是一樣的,你便能一直使用這個實例來過渡新的視圖控制器夺刑。

接下來實現(xiàn)擴展里面的代理方法:

func animationControllerForPresentedController(
    presented: UIViewController,
    presentingController presenting: UIViewController,
    sourceController source: UIViewController) ->
    UIViewControllerAnimatedTransitioning? {

    return transition
}

這個方法的參數(shù)能夠提供給你判斷是否使用特別的過渡動畫缅疟。在這個教程里你只會返回唯一的這個PopAnimator實例分别。

你已經實現(xiàn)了過渡到其他控制器的方法,但是你是否也想要處理控制器消失的那一個方法窿吩?

添加下面這個方法能解決上面的問題:

func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return nil
}

這個方法和之前那個方法是類似的茎杂,你能夠通過檢查參數(shù)來獲知是哪個控制器將要消失掉,以及是否需要使用自定義的過渡動畫纫雁。暫時來說我們先返回nil煌往,在往后再實現(xiàn)這個小時的動畫。

你已經實現(xiàn)了一個自定義的動畫設計者去管理你的控制器過渡轧邪,但是已經能工作了嗎刽脖?

構建和運行你的項目然后輕觸其中一張藥草的圖片:

創(chuàng)建你自己的過渡動畫設計者

打開PopAnimator.swift;我們將要在這里添加視圖控制器之間的過渡代碼忌愚。

首先曲管,添加以下的屬性到類內:

let duration    = 1.0
var presenting  = true
var originFrame = CGRect.zero

duration會使用在好幾個地方,例如:告知UIKit過渡持續(xù)的時間和當我們創(chuàng)建動畫的時候也需要用到硕糊。

persenting會指示你的動畫設計者是正在展現(xiàn)或者取消一個視圖控制器院水。你需要時刻更新這個屬性,因為是需要它指示動畫是向前發(fā)展或者調轉向后简十。

最后檬某,你會使用originFrame去儲存被點擊圖像的原始frame-你會從這個frame擴展到整個屏幕的尺寸,反之亦然螟蝙。當你選擇了圖像恢恼,你需要獲得該圖像的frame然后傳遞給動畫設計者這個實例。

現(xiàn)在你該繼續(xù)實現(xiàn)UIViewControllerAnimatedTransitioning方法胰默。

替代剛才寫好的transitionDuration()實現(xiàn):

return duration

使用duration屬性使你能輕易試驗和改變動畫的過渡時間场斑。

設置過渡的上下文

是時候添加一下酷炫的東西到animateTransition。這個方法有一個UIViewControllerContextTransitioning類型的參數(shù)用于你獲得需要的參數(shù)和控制器之間的過渡牵署。

在你開始寫代碼之前漏隐,你首先要明白什么是動畫的上下文。

當兩個控制器之間開始過渡的時候奴迅,當前存在的視圖會被添加到一個過渡容器視圖里锁保,然后新的視圖控制器會被創(chuàng)建,但是仍然未可見半沽,就如下圖所示:

因此你的任務是在animateTransition()里向過渡容器天價一個新的視圖爽柒。使其“動畫地進入”,然后有需要的話將舊的視圖“動畫地退出”者填。

默認來說浩村,當動畫完成時,舊的視圖會被移出過渡容器占哟。

添加一個酷炫的過渡

創(chuàng)建一個自己的過渡動畫需要配合過渡上下文的“容器”視圖來工作心墅。這是一個臨時的視圖(更像是一個暫存器)酿矢,它只會在過渡發(fā)生的時候才會添加的屏幕上。你能夠在這個視圖上創(chuàng)建你所有的動畫怎燥。

把下面的代碼插入到animateTransition():

let containerView = transitionContext.containerView()!

let toView =
transitionContext.viewForKey(UITransitionContextToViewKey)!

let herbView = presenting ? toView : transitionContext.viewForKey(UITransitionContextFromViewKey)!

你的動畫會生存在containerView里瘫筐,toView即是將要展示的新的視圖。如果你是在將要顯示的狀態(tài)铐姚,herbView就是toView策肝;能從上下文獲得。不過在這個例子無論是展現(xiàn)或者消失隐绵,herbView總是你進行動畫的對象之众。

當你展示細節(jié)視圖控制器,它會擴張鋪滿整個屏幕依许,當它消息棺禾,它會縮少為原來圖像的frame。

添加下面的代碼到animateTransition():

let initialFrame = presenting ? originFrame : herbView.frame
let finalFrame = presenting ? herbView.frame : originFrame

let xScaleFactor = presenting ?
    initialFrame.width / finalFrame.width :
    finalFrame.width / initialFrame.width

let yScaleFactor = presenting ?
    initialFrame.height / finalFrame.height :
    finalFrame.height / initialFrame.height

上面這段代碼峭跳,你檢測得出初始和最后的動畫frame然后計算出縮放的系數(shù)膘婶,用作待會的動畫。

現(xiàn)在你應該認真地把心得視圖擺放在被點擊的圖像上面蛀醉;這會看起來像被點擊的圖片擴展到整個屏幕竣付。

添加下面的代碼到animateTransition():

let scaleTransform = CGAffineTransformMakeScale(xScaleFactor, yScaleFactor)

if presenting {
  herbView.transform = scaleTransform
  herbView.center = CGPoint(
    x: CGRectGetMidX(initialFrame),
    y: CGRectGetMidY(initialFrame))
  herbView.clipsToBounds = true
}

當展示一個新的視圖,你設置它的縮放比例和位置滞欠,使其盡量合適初始圖片的frame。

現(xiàn)在添加最后一點代碼到animateTransition():

containerView.addSubview(toView)
containerView.bringSubviewToFront(herbView)

UIView.animateWithDuration(duration, delay:0.0,
  usingSpringWithDamping: 0.4,
  initialSpringVelocity: 0.0,
  options: [],
  animations: {
    herbView.transform = self.presenting ?
    CGAffineTransformIdentity : scaleTransform

    herbView.center = CGPoint(x: CGRectGetMidX(finalFrame),
                          y: CGRectGetMidY(finalFrame))

  }, completion:{_ in
    transitionContext.completeTransition(true)
})

首先我們會添加toView到container里面肆良。接著筛璧,你需要確保herbView是在視圖層的頂層,因為這是你想要惹恃。注意當消失視圖控制器時夭谤,看第一行代碼,toView是origin view(就是那個主視圖)巫糙,你的動畫會因此被擋住朗儒,你需要把hertView的視圖層往上提。

然后参淹,你就能開始你的動畫了-使用spring效果的動畫會更好看醉锄!

在動畫的展示過程中,你改變了herbView的大小比例和位置浙值。當展現(xiàn)的時候恳不,你從一個在底部的小尺寸擴展成整個屏幕的大小,因此變換是之前標記的變換开呐。當消失時烟勋,你的動畫需要縮小成原始圖像的尺寸规求。

這里的重點:你設置新的控制器在被點擊的圖像上面;你使用動畫變換著初始和結束的frame卵惦;最后阻肿,你調用completeTransition()方法提交這些邏輯給UIKit。現(xiàn)在是時候讓你的代碼動起來了沮尿!

構建和運行你的項目丛塌;點擊第一個藥草的圖片你會看到你的視圖控制器像你如期一樣過渡。

現(xiàn)在你的動畫開始在左下角蛹找,這是因為默認的originFrame的值是(0,0)-你還沒有設置它姨伤。

打開ViewController.swift然后添加下面的代碼到animationControllerForPresentedController()的頂部:

transition.originFrame = 
  selectedImage!.superview!.convertRect(selectedImage!.frame, toView: nil)

transition.presenting = true

這段代碼會傳遞被你選中圖像的frame給originFrame。然后設置presenting標志為true庸疾,然后在動畫里被點擊的圖像會被隱藏乍楚。

再次構建和運行你的項目;試著點擊列表里不同的藥草圖片届慈,觀察它們的過渡動畫:

添加消失的過渡

剩下要做的工作就是消失細節(jié)控制器徒溪。你已經為動畫設計者做了足夠多的工作-過渡動畫代碼的邏輯適應了兩種情況下的初始和消失的frame,因此你能非常輕易地完成剩下的工作金顿。

打開ViewController.swift然后代替animationControllerForDismissedController() 這個方法的實現(xiàn):

transition.presenting = false
return transition

這段代碼時在告訴動畫設計者對象:你正在消失一個視圖控制器臊泌,該使用對應的動畫代碼去過渡。

構建和運行項目能夠看到結構揍拆,點擊一個藥草圖片然后再點擊屏幕任意位置取消當前視圖渠概!

最后那一下點擊的過渡有一點太過平整-你能動畫地改變一下四角圓角半徑使其平整地回到一開始的位置。

添加下面的代碼到animateTransition:

let round = CABasicAnimation(keyPath: "cornerRadius")
round.fromValue = presenting ? 20.0/xScaleFactor : 0.0
round.toValue = presenting ? 0.0 : 20.0/xScaleFactor
round.duration = duration / 2
herbView.layer.addAnimation(round, forKey: nil)
herbView.layer.cornerRadius = presenting ? 0.0 : 20.0/xScaleFactor

當你展現(xiàn)細節(jié)視圖控制器時嫂拴,圓角半徑從20.0到0.0播揪,反之從0.0到20.0。一個友好的點擊結束本節(jié)教程筒狠!

接下來做什么猪狈?

你可以從這里下載完成的項目。

之后,你可以繼續(xù)提高這個項目。這里有一些主意:

在過渡的時候隱藏被點擊的圖片使其看起來更像生長到整個屏幕毫痕。
漸入和漸出藥草的描述文字看來會更平整一點前塔。
測試使過渡適應橫屏。

更多處理過渡場景動畫的章節(jié)在iOS Animations by Tutorials。在這本書里,你會學習到更多視圖控制器的過渡展示,方向改變動畫峡继,導航控制器和交互過渡,等等匈挖。

我們希望你享受這個教程碾牌,如果你有任何問題或者建議康愤,可以在下方留言!

原文地址

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末舶吗,一起剝皮案震驚了整個濱河市征冷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌誓琼,老刑警劉巖检激,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異腹侣,居然都是意外死亡叔收,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門傲隶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饺律,“玉大人,你說我怎么就攤上這事跺株「幢簦” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵乒省,是天一觀的道長巧颈。 經常有香客問我,道長袖扛,這世上最難降的妖魔是什么砸泛? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮蛆封,結果婚禮上唇礁,老公的妹妹穿的比我還像新娘。我一直安慰自己娶吞,他們只是感情好,可當我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布械姻。 她就那樣靜靜地躺著妒蛇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪楷拳。 梳的紋絲不亂的頭發(fā)上绣夺,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天,我揣著相機與錄音欢揖,去河邊找鬼陶耍。 笑死,一個胖子當著我的面吹牛她混,可吹牛的內容都是我干的烈钞。 我是一名探鬼主播泊碑,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼毯欣!你這毒婦竟也來了馒过?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤酗钞,失蹤者是張志新(化名)和其女友劉穎腹忽,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體砚作,經...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年葫录,在試婚紗的時候發(fā)現(xiàn)自己被綠了着裹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡压昼,死狀恐怖求冷,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情窍霞,我是刑警寧澤匠题,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站但金,受9級特大地震影響韭山,放射性物質發(fā)生泄漏。R本人自食惡果不足惜冷溃,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一钱磅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧似枕,春花似錦盖淡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至答憔,卻和暖如春味赃,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背虐拓。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工心俗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓城榛,卻偏偏與公主長得像揪利,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子吠谢,可洞房花燭夜當晚...
    茶點故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內容