動畫實作

UI Transition 教學:一起來學習 Whatsapp 也在用的 UI 轉場技巧吧!

觀察其他非常成功的 App 的實作技巧,對你開發自己的 App 十分有幫助。如果你想為自己的 App 創建 Whatsapp 也在用、流暢又一致的 UITabBar 和 UIToolBar 動畫,這篇教程就非常適合你了!快來一起實作 Whatsapp 的 UI Transition (轉場) 技巧吧!
UI Transition 教學:一起來學習 Whatsapp 也在用的 UI 轉場技巧吧!
UI Transition 教學:一起來學習 Whatsapp 也在用的 UI 轉場技巧吧!
In: 動畫實作, iOS App 程式開發, Swift 程式語言, UI, Xcode
本篇原文(標題:Creating WhatsApp-like animations in iOS)刊登於作者 Medium,由 Alberto Taiuti 所著,並授權翻譯及轉載。

這是 WhatsApp 從 UITabBar 轉換到 UIToolBar 的過程:
whatsapp-ui-transition-1

本教學的實作結果:
whatsapp-ui-transition-2

自從我開始開發 iOS apps 後,我就一直關注那些非常成功的 App 如何實作 UI 轉場 (UI transition) ,希望可以跟著實作在我的 App 內。

最近我正在做一個 App,它的 UICollectionView 嵌入在 UIViewController 內,而 UIViewController 又嵌入在 UICollectionViewController 內。而我希望可以將導覽欄 (tabBar) 藏在螢幕下方,進入編輯模式時,才讓它出現。

whatsapp-ui-transition-3

我最常用的 App 就是 WhatsApp,但之前都沒試過剖析它管理 UI 轉場的方法。我注意到在編輯模式下,它處理螢幕下方的 UIToolBar 做轉場與位置變化,非常符合我想要實現的內容。

在這篇文章,我將展示如何在 App 內實作這個功能,並且解釋我設計的方向。事實證明,要實作這功能並不簡單,我花了許多時間來理解它。最後,我不得不自己編寫這個範例程式碼,並做了很多測試。所以如果你想要開發這個功能的話,我希望這篇文章和範例程式碼可以為你省下許多時間。

分析

首先,我們應該了解 WhatsApp 如何處理這個功能的。以下是 WhatsApp 的轉場動作,我將它的速度放慢了 10% 來呈現:

whatsapp-ui-transition-4

如我觀察所得,WhatsApp 在來回轉換編輯模式時,把 UITabBar 淡出或淡入,同時把 UIToolBar 上移或下移。

所以我們的計劃就是要呈現上述這個動作。

規劃

首先,列出我們的需求:

  1. 我們要讓 UINavigationBar 使用 iOS 11 的大標題
  2. UIToolBar 沒在使用時,我們需要隱藏它
  3. 我們要呈現一樣的 UITabBar 動畫

開始吧!

這就是我完全搞砸了的地方。我以為要實作這項功能非常簡單,但結果卻和我想像的完全相反。

為什麼?

因為使用大標題預設的動畫和系統,就會搞砸所有你為 UIToolBar 編寫的簡單螢幕位置程式碼。

天真的做法

原本我先嘗試的方法,就是利用 UINavigationController 預設的 UIToolBar,並根據 App 是否在編輯模式的情況下改變它的位置。

然而,如我剛提到的,若你要使用大標題的話,這招就完全不管用了。

請來看一下在有大標題動畫需求下,UIToolBar 會這樣移動:
whatsapp-ui-transition-5

加上,我發現如果我希望 Collection View 覆蓋 UIToolBarUITabBar 之間的整個空間,我就必須手動調整它的邊界屬性。

解決方案

為了要解決這個問題,我們不應使用 UINavigationController 物件預設的 UIToolBar,而是需要手動增加一個 UIToolBar 到我們的視圖控制器 (View controller) 中。

首先我們先簡單建立一個 UIToolBar

private lazy var toolBar: UIToolbar = {
  return UIToolbar()
}()

然後在 viewDidLoad 設計它的邊界:

private var toolBarConstraints = [NSLayoutConstraint]()
...

override func viewDidLoad() {
  super.viewDidLoad()

  ...

  navBar.prefersLargeTitles = true

  navigationController!.isToolbarHidden = true

  view.addSubview(toolBar)
  toolBar.translatesAutoresizingMaskIntoConstraints = false
  toolBarConstraints.append(contentsOf: [
    toolBar.leftAnchor.constraint(equalTo: self.view.leftAnchor),
    toolBar.rightAnchor.constraint(equalTo: self.view.rightAnchor),
    toolBar.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
    toolBar.topAnchor.constraint(equalTo: tabBar.topAnchor)
    ])
}

然後,我們建立一些計算了屬性,以在整個示例視圖控制器中正確設置自定義 UIToolBar

private var screenHeight: CGFloat {
  return UIScreen.main.bounds.height
}

private var toolBarYPos: CGFloat {
  if currentState == .normal {
    return screenHeight
  }
  // Ideally check for other states here, but since we only have two, keep it
  // simple; would do something like: else if currentState == .edit {
  else {
    return screenHeight - toolBar.frame.height
  }
}

最後,我們使用這些計算屬性來正確設置 UIToolBar 和/或 UITabBar 的位置。現在,當使用者點擊 “Edit” 或 “Done” 的按鈕時:
whatsapp-ui-transition-6

最後加入這段程式碼,就差不多完成了:

@IBAction func onEditBtnTouchUp(_ sender: Any) {
    // Switch to the editing mode
    if currentState == .normal {
      currentState = .edit

      fade(tabBar, toAlpha: 0, withDuration: 0.2, andHide: true)
      UIView.animate(withDuration: 0.2, animations: {
        // Set edit to done
        self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self,
                                                                action: #selector(self.onDoneBtnTouchUp))
        // Fade away + btn
        self.plusBtn.isEnabled = false
        self.plusBtn.tintColor = UIColor.clear

        // Position the toolbar
        self.toolBar.frame.origin.y = self.toolBarYPos
      })
    }
  }

  @objc func onDoneBtnTouchUp(_ sender: Any) {
    // Switch to normal state
    if currentState == .edit {
      currentState = .normal

      fade(tabBar, toAlpha: 1, withDuration: 0.2, andHide: false)
      UIView.animate(withDuration: 0.2, animations: {
        // Set edit to done
        self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self,
                                                                action: #selector(self.onEditBtnTouchUp))

        // Fade in + btn
        self.plusBtn.isEnabled = true
        self.plusBtn.tintColor = nil

        // Position the toolbar
        self.toolBar.frame.origin.y = self.toolBarYPos
      })
    }

總結

實作完成的結果應該像這樣:
whatsapp-ui-transition-7

我將這個程式碼存放了在 Github 上,你可以去看看完整的程式碼。

我建議你自行去看看程式碼,因為我為了簡潔起見,故意在文章中忽略了一些重點。

這真的花了我一段時間來理解,因為不同 UIKit 元素的互動未能如我所想。

請記住,當你在 iOS 上以 Swift 開發更進階的 UI 互動功能時,應該跟從以下步驟:

  1. 先嘗試最簡單的解決方案;如果成功就很好了!
  2. 如果不成功,請嘗試其他方式,並繼續實驗!
  3. 過程可能會令人非常沮喪,但請不要放棄!

如果你喜歡這篇文章,歡迎留言或在 Twitter 上追踨我。

文章風格的靈感部份來自 Nathan Gitter 在 Medium 上的文章,我也有使用一些他的 程式碼。謝謝 Nathan 跟我們分享他的程式碼!

本篇原文(標題:Creating WhatsApp-like animations in iOS)刊登於作者 Medium,由 Alberto Taiuti 所著,並授權翻譯及轉載。
作者簡介: Alberto 是一位工程創業家,專注於擴增實境與行動裝置體驗,目前正致力開發室內擴增實境。你可以在他的網站上聯絡他
譯者簡介:Oliver Chen-工程師,喜歡美麗的事物,所以也愛上Apple,目前在iOS程式設計上仍是新手,正研讀Swift與Sketch中。生活另一個身份是兩個孩子的爸,喜歡和孩子一起玩樂高,幻想著某天自己開發的App,可以讓孩子覺得老爸好棒!。聯絡方式:電郵[email protected]
作者
AppCoda 編輯團隊
此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。
評論
更多來自 AppCoda 中文版
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。