iOS動畫教學:自定義視圖控制器轉場動畫
是否當你想要顯示攝像視圖控制器呆抑,地址欄或者你自定義的場景控制器時岂嗓,你都是在調用相同的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。在這本書里,你會學習到更多視圖控制器的過渡展示,方向改變動畫峡继,導航控制器和交互過渡,等等匈挖。
我們希望你享受這個教程碾牌,如果你有任何問題或者建議康愤,可以在下方留言!