ContainerViewController的ViewController 轉場

又過去十天了阳液,更新的速度有點慢誓酒,很不好意思诗轻,自己并不是一個高產(chǎn)的程序員嗅义,以后一定要加油了个榕。
上篇文章 Implementing a Container View Controller 翻譯+自我實踐 中解釋了如何實現(xiàn)一個簡單的容器類視圖控制器,這篇文章講講自定義容器類視圖控制器如何實現(xiàn)子視圖控制器的轉場切換的芥喇。

按照慣例,還是說一些基礎知識凰萨,iOS中的轉場继控,指的是視圖控制器的轉場。
官方文檔對Transition(轉場)有專門的章節(jié)進行介紹胖眷,希望初學的開發(fā)者仔細閱讀武通,理解一些基本的概念和原理:
The Presentation and Transition

轉場其實就是使得ViewController顯示在屏幕上,一共有兩種方式:

  1. present 一個視圖控制器珊搀,在日常開發(fā)中冶忱,主要體現(xiàn)在使用一個ViewController 模態(tài)跳轉到另外一個ViewController
  1. 在ContainerViewController 中顯示一個視圖控制器,這種體現(xiàn)在navigationController 和 tabbarController 等容器類視圖控制器中切換視圖控制器境析,例如導航的push 與 tabbarController的select 一個視圖控制器囚枪,當然我們自定義的ContainerController中提供的轉場也是屬于此類。

一般的開發(fā)者有時候只會簡單的使用第一種和第二種中系統(tǒng)提供的容器類視圖控制器的轉場劳淆,對轉場的原來不是很理解链沼,我們要實現(xiàn)一個自定義容器類視圖控制器的轉場,必須要把這些都講明白沛鸵。其實按照我個人的理解括勺,視圖控制器的轉場跟我們自己實現(xiàn)一兩個View之間動畫切換差不多,無非就是ViewController的View在切換曲掰。當然疾捍,幾個View的切換要在同一個父視圖上進行,那ViewController的View的切換則要在上篇文章中提到的ContainerView上進行栏妖,并且需要在轉場前和轉場后處理好子視圖控制器與容器類視圖控制器的父子關系的創(chuàng)建與管理乱豆,也就是調用addChildViewController:didMoveToParentViewController: 底哥、willMoveToParentViewController: 咙鞍、removeFromParentViewController 使得視圖控制器的生命周期函數(shù)在轉場的過程中正確的調用,并且在轉場過程中趾徽,完成視圖控制器的View在ContainerView上的布局续滋。在上面第一種提到的轉場和第二種中系統(tǒng)提供的容器類視圖控制器(navigationController和tabbarController等)的轉場,系統(tǒng)完成了一些操作孵奶,使得視圖控制器的生命周期函數(shù)得以正確調用疲酌,并且在containerView上正確的呈現(xiàn)。

在上篇文章中的例子里,我們在左右切換的方法中朗恳,調用了創(chuàng)建視圖控制器的父子關系的一些函數(shù)湿颅,并且調用了transitionFromViewController(fromViewController: UIViewController, toViewController: UIViewController, duration: NSTimeInterval, options: UIViewAnimationOptions, animations: (() -> Void)?, completion: ((Bool) -> Void)?)來完成視圖控制器的View在ContainerView上的布局和動畫切換,該函數(shù)是iOS5中提供的粥诫,在當時用于自定義視圖控制器轉場的時候來完成動畫的轉場油航,例子里的核心代碼如下:

  fromViewController.willMoveToParentViewController(nil)
    self.addChildViewController(toViewController)
    toViewController.view.bounds = container.bounds
    let endCenter:CGPoint;
    fromViewController.transitioningDelegate = self;
    if orientation == Orientation.Left{
        toViewController.view.center = CGPointMake(self.view.bounds.size.width + toViewController.view.bounds.size.width/2, self.view.bounds.size.height/2)
        endCenter = CGPointMake(-self.view.bounds.size.width - toViewController.view.bounds.size.width/2, self.view.bounds.size.height/2)
    }
    else
    {
        toViewController.view.center = CGPointMake(-self.view.bounds.size.width - toViewController.view.bounds.size.width/2, self.view.bounds.size.height/2)
        endCenter = CGPointMake(self.view.bounds.size.width + toViewController.view.bounds.size.width/2, self.view.bounds.size.height/2)
    }
    self.transitionFromViewController(fromViewController, toViewController: toViewController, duration: 0.25, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
        toViewController.view.frame = fromViewController.view.frame
        fromViewController.view.center = endCenter
        }) { (completion) -> Void in
            toViewController.didMoveToParentViewController(self)
            fromViewController.removeFromParentViewController()
    }

其實我們也可以不調用transitionFromViewController: `這個方法,自己書寫視圖控制器View在ContainerView上的添加怀浆、刪除和布局谊囚,但一定要注意做這些事情的順序,以保證視圖控制器的生命周期方法正常調用执赡,雖然這是復雜的镰踏,但是確實可行的。

等到iOS7之后沙合,蘋果為了滿足開發(fā)者在這方面的需求奠伪,將轉場的過程進行了協(xié)議化,提出了如下幾個概念:

1.動畫控制器 (Animation Controllers) 遵從 UIViewControllerAnimatedTransitioning 協(xié)議首懈,用來專門處理轉場中的動畫切換
2.交互控制器 (Interaction Controllers) 通過遵從 UIViewControllerInteractiveTransitioning 協(xié)議绊率,用來處理可交互的轉場邏輯
3.轉場代理 (Transitioning Delegates) 遵守UIViewControllerTransitioningDelegate協(xié)議,在代理方法中究履,針對不同類型(模態(tài)跳轉還是導航push即舌,是可交互的還是不可交互)轉場,為轉場提供對應的動畫控制器或者是交互控制器挎袜。
4.轉場上下文 (Transitioning Contexts) 定義了轉場時需要的元數(shù)據(jù)顽聂,比如在轉場過程中所參與的視圖控制器和視圖的相關屬性。 轉場上下文對象遵從 UIViewControllerContextTransitioning 協(xié)議盯仪,并且這是由系統(tǒng)負責生成和提供的紊搪。
轉場協(xié)調器(Transition Coordinators) 可以在運行轉場動畫時,并行的運行其他動畫全景。 轉場協(xié)調器遵從 UIViewControllerTransitionCoordinator 協(xié)議耀石。

以上幾個概念,都是使用定義在UIViewController 文件里的幾組協(xié)議爸黄,利用這幾種概念滞伟,我們通過協(xié)議能更好的將轉場的邏輯合理的進行分離,使得耦合度降低并且使得每一塊都專注于本身要處理的邏輯之中炕贵,并且能夠隨意復用梆奈,比方說,相同的轉場上下文中称开,我可以在轉場代理中提供不同的動畫控制器或交互控制器實現(xiàn)不同的轉場亩钟。同樣乓梨,我們可以使用如上概念,來自定義模態(tài)跳轉或者是navigationcontroller和tabbarcontroller的轉場清酥,只需要設置好轉場代理扶镀,在代理中提供不同的動畫控制器和交互控制器,而轉場上下文焰轻,在系統(tǒng)控件的轉場中由系統(tǒng)生成臭觉,提供給動畫或者交互控制器來使用。
以上的流程我們可以總結為:

  1.無論是在任何類型下的轉場將要發(fā)生的時候辱志,我們在此之前胧谈,需要設置轉場的代理,例如在模態(tài)跳轉之前我們需要使用發(fā)起模態(tài)跳轉的視圖控制器設置transitioningDelegate來提供轉場代理荸频。
  2.在UIViewControllerTransitioningDelegate 代理方法中提供
 //視圖控制器消失的時候,需要提供的動畫控制器
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return nil
}
//視圖控制器顯示的時候客冈,需要提供的動畫控制器
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return nil
}
//可交互的轉場中旭从,視圖控制器消失的時候,需要提供的動畫控制器
  func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
    return nil
}
 //可交互的轉場中场仲,視圖控制器顯現(xiàn)的時候和悦,需要提供的動畫控制器
func interactionControllerForPresentation(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
    return nil
}`

以上適用于模態(tài)跳轉的流程,navigationController的自定義轉場渠缕,需要設置的導航視圖控制器的delegate(遵守UINavigationControllerDelegate)鸽素,在代理方法中如上第二部提供不同的動畫控制器和交互控制器,在此就不一一贅述了亦鳞。

那么我們在回過頭來馍忽,看看我們自定義的容器類視圖控制器如何實現(xiàn)轉場,前面已經(jīng)提到燕差,我們可以自己調用構建父子關系的函數(shù)并且操作ContainerView來做動畫轉場遭笋,或者借助transitionFromViewController來完成,但這次我們使用動畫控制器來實現(xiàn)徒探,好處很明顯瓦呼,在上面已經(jīng)介紹過了。我將上篇文章中的Demo在github上做了一個分支测暗,來說明這個做法央串,我注釋掉左右切換處理的函數(shù)中老的實現(xiàn)方法,來自定義動畫控制器實現(xiàn)切換動畫碗啄,新Demo的地址如下:
ContainerViewController

 func   swipeFromViewController(fromViewController:UIViewController,ToViewController toViewController:UIViewController, WithOrientation orientation:Orientation)`中质和,我注釋掉之前的邏輯,新的邏輯如下:
  //第一步稚字,在轉場之前侦另,構建fromViewController toViewController和containerViewController的父子關系
 fromViewController.willMoveToParentViewController(nil)
 self.addChildViewController(toViewController)
  //第二步,因為我們是自定義的容器控制器,我們需要提供轉場上下文
    let context = CustomTransitionContext(containerView: container, toViewController: toViewController, fromViewController: fromViewController)
  //第三步褒傅,設置轉場完成之后弃锐,調用一個回調的閉包,來處理父子關系的重新構建
    context.completeHandle = {
        (isComplete : Bool) -> Void in
        toViewController.didMoveToParentViewController(self)
        fromViewController.removeFromParentViewController()
    }
    let animator = CustomTransitionAnimtor(context: context)
    animator.orientaion = orientation
    animator.animateTransition(context)`

接下來我們看一下我們自定義的轉場上下文殿托,一個遵守UIViewControllerContextTransitioning協(xié)議的NSObject對象:

 class CustomTransitionContext: NSObject,
  UIViewControllerContextTransitioning 
{

weak var customContainerView : UIView?
private weak var toViewController:UIViewController?
private weak var fromViewController:UIViewController?
internal var completeHandle : ((isComplete : Bool)->Void)?;
var animating : Bool = true
func isAnimated() -> Bool {
    return animating
}
func isInteractive() -> Bool {

    return false
}
func transitionWasCancelled() -> Bool {
    return false
}
func presentationStyle() -> UIModalPresentationStyle {
    return UIModalPresentationStyle.Custom
}
//完成轉場之后要做的操作
func completeTransition(didComplete: Bool) {
    if let handler = completeHandle {

        animating = false
        handler(isComplete: didComplete)
    }

}
func updateInteractiveTransition(percentComplete: CGFloat) {

}
func finishInteractiveTransition() {

}
func cancelInteractiveTransition() {
    
}
//轉場上下文提供的UITransitionContextFromViewController與UITransitionContextToViewController
func viewControllerForKey(key: String) -> UIViewController? {
    switch key{
    case UITransitionContextFromViewControllerKey:
        return fromViewController
    case UITransitionContextToViewControllerKey:
        return toViewController
    default:
        return nil
    }
}

@available(iOS 8.0,*)
func viewForKey(key: String) -> UIView? {
    switch key{
    case UITransitionContextFromViewKey:
        return fromViewController?.view
    case UITransitionContextToViewKey:
        return toViewController?.view
    default:
        return nil
    }
}

func finalFrameForViewController(vc: UIViewController) -> CGRect {
    return CGRectZero
}

func initialFrameForViewController(vc: UIViewController) -> CGRect {
    return CGRectZero
}
func containerView() -> UIView? {
    return self.customContainerView
}

//提供一個便利構造方法霹菊,能夠獲取轉場相關的視圖控制器與ContainerView
convenience init(containerView : UIView? ,toViewController : UIViewController , fromViewController : UIViewController){
    self.init()
    self.toViewController = toViewController
    self.fromViewController = fromViewController
    self.customContainerView = containerView
}

  func targetTransform() -> CGAffineTransform {
    return CGAffineTransformIdentity
  }

    }

以上就是轉場上下文的構建,帶有注釋的方法是比較核心的幾個方法支竹,而其他的都是為了滿足協(xié)議而補全的方法旋廷。核心就是使得我們自定義的轉場上下文擁有ContainerView和要切換的視圖控制器,提供給動畫控制器使用礼搁。
下面是重點饶碘,動畫控制器的實現(xiàn):

   class CustomTransitionAnimtor: NSObject ,  UIViewControllerAnimatedTransitioning {
internal var orientaion : ContainerViewController.Orientation = ContainerViewController.Orientation.Left
var toViewController : UIViewController?
var fromViewController : UIViewController?
var privateContext : CustomTransitionContext;
//該方法提供轉場動畫需要的時間
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
    return 0.25
}
//自定義的構造函數(shù)用來獲取自定義的轉場上下文
init(context : CustomTransitionContext){
    privateContext = context
    super.init()
}
//該方法是用來處理動畫邏輯的
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
    //獲取containerView的引用
    let containerView = privateContext.containerView()
    //獲取要轉場的相關視圖控制器,通過自定義的轉場上下文來獲取
    let toViewController = privateContext.viewControllerForKey(UITransitionContextToViewControllerKey)
    let fromViewController = privateContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
    //配置相關控制器的View的信息馒吴,大小與在Container的添加和刪除
    toViewController?.view.bounds = (fromViewController?.view.bounds)!
    var endCenter : CGPoint;
    if orientaion == ContainerViewController.Orientation.Left{
        endCenter = CGPointMake(-(fromViewController?.view.bounds.size.width)!, (containerView?.bounds.size.height)!/2)
        toViewController?.view.center = CGPointMake((containerView?.bounds.size.width)! + (toViewController?.view.bounds.width)!/2, (containerView?.bounds.height)!/2)
    }
    else
    {
        endCenter = CGPointMake((containerView?.bounds.size.width)! + (fromViewController?.view.bounds.size.width)!/2, (containerView?.bounds.size.height)!/2)
        toViewController?.view.center = CGPointMake(-(containerView?.bounds.size.width)! - (toViewController?.view.bounds.width)!/2, (containerView?.bounds.height)!/2)
    }
    containerView?.addSubview((toViewController?.view)!)

            //按照預定的來實現(xiàn)動畫切換UIView.animateWithDuration(self.transitionDuration(privateContext), animations: { () -> Void in
        toViewController?.view.center = (fromViewController?.view.center)!
        fromViewController?.view.center = endCenter
        }) { (isComplection) -> Void in
            fromViewController?.view.removeFromSuperview()
            self.animationEnded(true)
    }
}
    //在完成轉場動畫的時候扎运,調用自定義轉場上下文的completeTransition方法,來告訴上下文轉場已經(jīng)完成
func animationEnded(transitionCompleted: Bool) {
    self.privateContext.completeTransition(true)
}

}
以上我們清晰的看到饮戳,動畫控制器有三個方法豪治,依次是提供動畫時間,動畫過程和動畫完成后要做的一些邏輯操作扯罐,非常簡單负拟,動畫控制器就是專一完成切換的動畫的邏輯的,代碼注釋已經(jīng)相當清楚歹河。
以上的步驟很清晰掩浙,但有兩個問題,那就是秸歧,為什么我們沒有使用轉場代理涣脚,在轉場代理的協(xié)議方法中提動畫控制器?第二是為什么我們需要自己構建轉場上下文寥茫?其實這個也是困擾我的問題遣蚀,其實這兩個問題是一體的,如果設置了模態(tài)跳轉的轉場代理纱耻,我們在動畫控制器里獲取的是系統(tǒng)構建的轉場上下文芭梯,但經(jīng)過我的實驗,很明顯系統(tǒng)提供的轉場上下文弄喘,通過viewControllerForKey提取出來的相關控制器不是正確的玖喘,也就是說,我們在容器類視圖控制器中使用模態(tài)跳轉的方式來自定義模態(tài)跳轉的轉場動畫是不可行的蘑志,因為模態(tài)跳轉構建的轉場上下文累奈,fromViewController一直為容器視圖控制器而不是真正的一個子視圖控制器贬派。所以,我們要自定義轉場上下文澎媒,來通過各種協(xié)議完成整個轉場過程搞乏。

Ok,以上就是一個使用動畫控制器和自定義轉場上下文來實現(xiàn)的自定義容器類視圖控制器里子視圖控制器的簡單切換戒努,下個目標请敦,是實現(xiàn)更為復雜的可交互的容器控制器的轉場切換,在此感謝大家的閱讀储玫!

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侍筛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子撒穷,更是在濱河造成了極大的恐慌匣椰,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件端礼,死亡現(xiàn)場離奇詭異禽笑,居然都是意外死亡,警方通過查閱死者的電腦和手機齐媒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纷跛,“玉大人喻括,你說我怎么就攤上這事∑兜欤” “怎么了唬血?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長唤崭。 經(jīng)常有香客問我拷恨,道長,這世上最難降的妖魔是什么谢肾? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任腕侄,我火速辦了婚禮,結果婚禮上芦疏,老公的妹妹穿的比我還像新娘冕杠。我一直安慰自己,他們只是感情好酸茴,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布分预。 她就那樣靜靜地躺著,像睡著了一般薪捍。 火紅的嫁衣襯著肌膚如雪笼痹。 梳的紋絲不亂的頭發(fā)上配喳,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音凳干,去河邊找鬼晴裹。 笑死,一個胖子當著我的面吹牛纺座,可吹牛的內(nèi)容都是我干的息拜。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼净响,長吁一口氣:“原來是場噩夢啊……” “哼少欺!你這毒婦竟也來了?” 一聲冷哼從身側響起馋贤,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤赞别,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后配乓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仿滔,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年犹芹,在試婚紗的時候發(fā)現(xiàn)自己被綠了崎页。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡腰埂,死狀恐怖飒焦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情屿笼,我是刑警寧澤牺荠,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站驴一,受9級特大地震影響休雌,放射性物質發(fā)生泄漏。R本人自食惡果不足惜肝断,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一杈曲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧胸懈,春花似錦鱼蝉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至羔挡,卻和暖如春洁奈,著一層夾襖步出監(jiān)牢的瞬間间唉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工利术, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留呈野,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓印叁,卻偏偏與公主長得像被冒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子轮蜕,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

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