使用視圖控制器的切換建立下拉菜單欄

做者:Simon NG,時間:2016/7/29
翻譯:BNCoding, 若有錯誤歡迎指出。原文連接git

導航是用戶交互的一個重要組成部分,有不少種方式提供一個菜單欄讓用戶來自由切換想訪問的功能。在以前的教程中咱們介紹了其中的一種就是側滑欄。下拉菜單則是另外一種經常使用的菜單設計。當用戶點擊菜單按鍵的是時候主屏幕中下拉顯示出菜單選項。若是你不知道下拉菜單是如何實現的話,不須要憂慮。繼續閱讀文章立刻你就能看見一個演示動畫。github

在展現下拉菜單的實現以前,這篇文章已經假設你對自定義視圖切換有必定的瞭解。若是你對這個視圖切換有關的內容不太熟悉的話,那麼你能夠先去看下Joyce寫的這篇文章swift

好了如今進入正題。app

下拉菜單功能的開始演示

在這篇教程中咱們會用Swift語言來實現下拉菜單,下面就是最終效果的一個快速展現:ide

工程模版

和以往同樣,我不但願你從頭開始,你能夠先去下載起始工程。該起始工程裏面包含了storyboard和一些view controller的類。你能夠找到兩個tableview,其中一個用於主屏幕(嵌在導航控制器中),另外一個用於導航菜單。若是你運行程序的話,主頁會展現一些虛擬的數據。函數

再繼續下一步以前,咱們先花點時間去瀏覽一些工程熟悉一下代碼。動畫

模態展現菜單視圖

首先,咱們打開Main.storyboard文件,找到裏面的兩個tableview,由於二者尚未進行segue連接。爲了讓用戶點擊menu按鍵的時候可以展現出菜單,咱們按住control鍵點擊menu按鍵拖動到菜單的tableview。鬆開按鍵,在action segue裏面選擇」present modally「。ui

若是你如今運行app的話,菜單界面會以模態的形式展示出來。爲了退出下拉菜單視圖,咱們添加一個unwind segue編碼

打開NewsTableViewController.swift文件,而後添加一個unwind action方法:spa

@IBAction func unwindToHome(segue: UIStoryboardSegue) {
    let sourceController = segue.sourceViewController as! MenuTableViewController
    self.title = sourceController.currentItem
}

接下來,咱們回到storyboard,按住control鍵連接Menutableview中的prototype cellexit圖標,在selection segue選項下面選擇unwindToHome

若是用戶如今點擊任意一個菜單的話,那麼當前視圖會消失而主視圖將會呈現出來。經過unwindToHome函數,主視圖控制器(即NewsTableViewController)會根據用戶選擇的菜單相應的改變其標題。爲了簡單其間咱們除了標題咱們不會修改主視圖中的內容。

除此以外,咱們還將設置當前的選中項爲白色。在實現這些但願的結果以前咱們還要實現一系列的函數方法。

將下面的函數插入到MenuTableViewController類裏面:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    let menuTableViewController = segue.sourceViewController as! MenuTableViewController
    if let selectedRow = menuTableViewController.tableView.indexPathForSelectedRow()?.row {
        currentItem = menuItems[selectedRow]
    }
}

在新版本的Swift中上面的代碼編譯沒法經過,後面我會將附上本身修改後的完整代碼

這個函數裏面咱們只是正確設置了當前選擇的菜單。

NewsTableViewController.swift文件裏面插入下面這個函數,該函數會將當前的標題傳遞給menu controller。

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    let menuTableViewController = segue.destinationViewController as! MenuTableViewController
    menuTableViewController.currentItem = self.title!
}

如今編譯並運行程序的話,當你點擊菜單按鍵的時候菜單選擇視圖會模態的出現,當你選擇其中的一個菜單項的時候程序會回到主視圖而且相應的修改主視圖標題。

建立帶動畫過渡的下了菜單

當前的菜單視圖示使用系統標準的動畫進行轉場的,如今咱們須要建立一個自定義的轉場動畫。正如我在以前的文章中介紹的那樣,自定義視圖控制器的轉場動畫的核心是動畫對象,該對象同時遵照UIViewControllerAnimatedTransitioningUIViewControllerTransitioningDelegate協議。咱們將會在類中使用這二者來實現動畫轉場,可是在此以前咱們咱們先來看看下拉菜單是怎麼工做的。當用戶點擊菜單按鍵的時候,主視圖會慢慢的往下面移動直到它到達指定的位置,也就是視圖底部在往下180個單位。

建立下拉菜單動畫

爲了實現下拉菜單的動畫效果,咱們會建立一個名爲MenuTransitionManager動畫管理器。在工程導航中右擊建立一個新文件。文件中的建立一個NSObject的子類MenuTransitionManager

類的代碼以下:

class MenuTransitionManager: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
    var duration = 0.5
    var isPresenting = false
    
    var snapshot:UIView?
    
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
        return duration
    }
    
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        
        // Get reference to our fromView, toView and the container view
        let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
        let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
        
        // Set up the transform for sliding
        let container = transitionContext.containerView()
        let moveDown = CGAffineTransformMakeTranslation(0, container.frame.height - 150)
        let moveUp = CGAffineTransformMakeTranslation(0, -50)
        
        // Add both views to the container view
        if isPresenting {
            toView.transform = moveUp
            snapshot = fromView.snapshotViewAfterScreenUpdates(true)
            container.addSubview(toView)
            container.addSubview(snapshot!)
        }
        
        // Perform the animation
        UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.3, options: nil, animations: {
            
            if self.isPresenting {
                self.snapshot?.transform = moveDown
                toView.transform = CGAffineTransformIdentity
            } else {
                self.snapshot?.transform = CGAffineTransformIdentity
                fromView.transform = moveUp
            }
            
            
            }, completion: { finished in
                
                transitionContext.completeTransition(true)
                if !self.isPresenting {
                    self.snapshot?.removeFromSuperview()
                }
        })
        
    }
    
    
    func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        isPresenting = false
        return self
    }
    
    func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        isPresenting = true
        return self
    }
 
}

該類同時遵循了UIViewControllerAnimatedTransitioningUIViewControllerTransitioningDelegate協議。由於之前的文章中已經介紹了,這裏我就不會講解其中的細節了。咱們仔細查看其中的animation block(例如animateTransition方法)。

參考以前顯示過程,在整個轉場動畫其間main viewfromViewmenu viewtoView

爲了實現咱們要的效果,咱們配置了兩個轉場動畫。第一個是向下移動main view,另外一個則是上移menu view這樣當該視圖回到原先的位置的時候能夠再次響應事件並進行動畫轉場。稍後運行程序的時候就明白個人意思了。

從iOS7開始,你能夠經過UIView-Snapshotting API快速、簡單的建立一個輕量級的視圖快照。

snapshot = fromView.snapshotViewAfterScreenUpdates(true)

經過調用snapshotViewAfterScreenUpdates函數,你就得到的main view的快照了。有了快照以後,咱們就能夠在動畫轉場的容器裏添加該快照了。該快照在menu view以後添加以保證該快照在容器的最頂部。

菜單視圖的出場動畫的實現其實很簡單。咱們僅僅在主視圖的snapshot上面使用moveDown轉場將菜單欄視圖設置爲默認位置。

self.snapshot?.transform = moveDown
toView.transform = CGAffineTransformIdentity

當菜單欄消失的時候採起的動做是相反的。主視圖向上滑動而且恢復到默認的位置。另外咱們會將快照移出這樣真正的主視圖就會展示出來。

如今咱們打開NewsTableViewController.swift文件並聲明一個MenuTransitionManager對象:

var menuTransitionManager = MenuTransitionManager()

prepareForSegue方法裏面添加一行代碼設置好該動畫委託對象。

如今編譯運行代碼,點擊菜單按鍵你就能看見一個下拉的菜單欄視圖了。

檢查用戶的手勢

到如今爲止咱們只能經過選擇某一個菜單選項來使菜單欄視圖消失。從用戶的角度來考慮的話,其實點擊主視圖的快照該菜單欄視圖也應該消失。可是主視圖的快照是沒有進行響應的。

事實上snapshot也是一個UIView對象,因此咱們能夠建立一個UITapGestureRecognizer對象而且將其添加到snapshot上。

當咱們對UITapGestureRecognizer對象進行初始化的時候,咱們須要傳遞目標對象和須要被調用的函數名。很明顯你能夠硬編碼一個特定的對象來讓菜單欄視圖消失,可是爲了咱們的設計更加的靈活咱們聲明一個協議對象,而且讓委託對象實現該協議中的方法。

MenuTransitionManager.swift裏面,咱們以下聲明該協議:

@objc protocol MenuTransitionManagerDelegate {
    func dismiss()
}

在這裏咱們定義了一個MenuTransitionManagerDelegate協議,該協議裏面還有一個必需要實現的方法。委託對象必須實現dismiss()方法來完成邏輯上的視圖消失操做。

MenuTransitionManager類裏面,聲明一個委託變量:

var delegate:MenuTransitionManagerDelegate?

而後須要處理該點擊事件的對象將會被設置爲委託對象。

最後,咱們須要建立一個UITapGestureRecognizer對象而且添加多snapshot中。一個好的解決方法就是在snapshot變量中使用didset方法。咱們將snapshot定義聲明進行修改:

var snapshot:UIView? {
    didSet {
        if let _delegate = delegate {
            let tapGestureRecognizer = UITapGestureRecognizer(target: _delegate, action: "dismiss")
            snapshot?.addGestureRecognizer(tapGestureRecognizer)
        }
    }
}

屬性觀察器(Property observer)是Swift中一個很是強大的特性。在設置屬性的值的時候觀察器(willSet/didSet)都將會被調用。該特性讓咱們在變量賦值的先後均可以很方便的馬上採起一些特定的操做。willSet方法會在屬性值賦值設置以前馬上被調用,而didSet方法會在屬性值賦值設置完成後馬上被調用。

在上面的代碼中,咱們在屬性觀察器方法中建立了一個UITapGestureRecognizer對象而且將它添加給了snapshot。因此,每次咱們對snapshot變量進行賦值以後都會立馬配置一個UITapGestureRecognizer對象。

大部分的工做都差很少完成了。如今咱們回到NewsTableViewController.swift中設置該類遵循MenuTransitionManagerDelegate協議而且實現裏面的方法。

首先,咱們以下修改相關的聲明:

class NewsTableViewController: UITableViewController, MenuTransitionManagerDelegate

接下來咱們實現其中的方法:

func dismiss() {
    dismissViewControllerAnimated(true, completion: nil)
}

在上面的代碼中,咱們使用dismissViewControllerAnimated方法來將退出當前的視圖。

最後,咱們在NewsTableViewController類的prepareForSegue方法中添加一行代碼來設置委託對象:

self.menuTransitionManager.delegate = self

任務完成,如今你編譯運行程序並點擊主視圖的snapshot的話,菜單欄視圖將會消失。

經過自定義的視圖轉換動畫,你能夠大大的提升用戶的體驗讓你的應用脫穎而出。下拉菜單欄緊急是一個示例而已,在你本身的下一個應用中你能夠作出本身的動畫實現。

爲了讀者進行參照,你能夠下載完整的代碼

由於教程的編寫的時間較早以及Swift版本的更迭,文章的部分代碼已經不在適用或者部分代碼有更好的方法來實現。附上個人代碼

相關文章
相關標籤/搜索