iOS 原生 App 是怎麼 deselectRow 的

iOS開發2019-08-18 11:24:08

黑客技術
點擊右側關注,瞭解黑客的世界!

Java開發進階
點擊右側關注,掌握進階之路!

Linux編程
點擊右側關注,免費入門到精通!


作者 | 四娘,目前在格隆匯 iOS 組搬磚,Swift 系列語言愛好者,拖延癌末期患者。 
https://juejin.im/post/5c26dfe851882561431a4927


這兩天偶然發現系統設置裏 tableView deselectRow 的時機和效果都很特別,正常情況下我們的 deselect 操作都會在 didSelect 代理方法裏執行,抑或者是更加細緻一點,在 viewDidAppear 裏完成。
但 iOS 原生的 App 説不,我還可以做得更好,這是系統設置裏的效果:
側滑返回時,deselect 動畫會隨着滑動手勢的進度而改變,搜了一下,國內似乎沒有太多相關的文章,並且我手頭常用的幾款軟件都做到沒有類似的效果。
搜了一下之後,發現國外的記錄也很少,只有三篇文章記錄了這個交互,其中寫的比較詳細的是這篇 The Hit List Diary #17 – clearsSelectionOnViewWillAppear[1]

轉場動畫的抽象 transitionCoordinator

這個交互其實是通過 UIViewController 的 transitionCoordinator 屬性實現的,它的類型是 UIViewControllerTransitionCoordinator。
簡單來説,它可以幫助我們在轉場動畫里加入一些自定義的動畫,自定義動畫的進度和生命週期會與轉場動畫保持一致,使用它可以達到更加自然和一致的轉場效果,例如 push 動畫裏 navigationBar 背景顏色的變化,它提供了這幾個方法供我們註冊動畫生命週期的回調:
protocol UIViewControllerTransitionCoordinator {
func animate(
alongsideTransition animation: ((UIViewControllerTransitionCoordinatorContext)
-> Void)?,
completion: ((UIViewControllerTransitionCoordinatorContext) -> Void)? = nil
) -> Bool

func animateAlongsideTransition(
in view: UIView?,
animation: ((UIViewControllerTransitionCoordinatorContext)
-> Void)?,
completion: ((UIViewControllerTransitionCoordinatorContext) -> Void)? = nil
) -> Bool

func notifyWhenInteractionChanges(
_ handler: @escaping (UIViewControllerTransitionCoordinatorContext)
-> Void
)
}
推薦大家去看一下 UIViewControllerTransitionCoordinator 這個協議的文檔[2],這裏摘錄一段我覺得比較有趣的描述:
Using the transition coordinator to handle view hierarchy animations is preferred over making those same changes in the viewWillAppear(_:) or similar methods of your view controllers. The blocks you register with the methods of this protocol are guaranteed to execute at the same time as the transition animations. More importantly, the transition coordinator provides important information about the state of the transition, such as whether it was cancelled, to your animation blocks through the UIViewControllerTransitionCoordinatorContext object.

比起 viewWillAppear 和其它相似的 ViewController 生命週期函數,我們更加推薦使用 transitionCoordinator 處理視圖層級的動畫。你註冊的函數可以保證與轉場動畫同時執行。更重要的是,transitionCoordinator 通過 UIViewControllerTransitionCoordinatorContext 協議提供了轉場動畫的狀態等重要信息,例如動畫是否已被取消等。
我由於最近業務的原因,第一個想起的就是 navigationBar,像是 barTintColor 這種屬性就可以使用 transitionCoordinator 做到更加自然的動畫轉場。

實現與封裝

我看了別人的文章並且嘗試其它集中方式之後,感覺 transitionCoordinator 獲取的最佳時機應該是 viewWillAppear,實現的邏輯大概是這樣:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)

// 判斷是否有被選中的 Row
if let selectedIndexPath = tableView.indexPathForSelectedRow {
// 判斷是否有 transitionCoordinator
if let coordinator = transitionCoordinator {
// 有的情況下,通過 coordinator 註冊 animation block
coordinator.animate(
alongsideTransition: { _ in
self.tableView.deselectRow(at: selectedIndexPath, animated: true)
},
completion: { context in
// 如果轉場動畫被取消了,則需要讓 tableView 回到被選中的狀態
guard context.isCancelled else { return }
self.tableView.selectRow(at: selectedIndexPath, animated: true, scrollPosition: .none)
}
)
} else {
// 沒有的情況下直接 deselect
tableView.deselectRow(at: selectedIndexPath, animated: animated)
}
}
}
如果把 transitionCoordinator 單純地看成是一個動畫抽象(拋開轉場),我們希望跟隨動畫完成的操作就是 deselect,那麼就可以更進一步地把這個 deselect 的操作封裝到 UITableView 的 extension 裏:
extension UITableView {

public func deselectRowIfNeeded(with transitionCoordinator: UIViewControllerTransitionCoordinator?, animated: Bool) {
guard let selectedIndexPath = selectRowAtIndexPath else { return }

guard let coordinator = transitionCoordinator else {
self.deselectRow(at: selectedIndexPath, animated: animated)
return
}

coordinator.animate(
alongsideTransition: { _ in
self.deselectRow(at: selectedIndexPath, animated: true)
},
completion: { context in
guard context.isCancelled else { return }
self.selectRow(at: selectedIndexPath, animated: false, scrollPosition: .none)
}
)
}
}
接着只要在 viewWillAppear 裏調用即可:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)

tableView.deselectRowIfNeeded(with: transitionCoordinator, animated: true)
}
如果大家在項目裏封裝了自己的 TableViewController 並且規範使用的話,那要加入這個效果就很簡單了。

結語

這是完整的示例[3]

參考

[1]http://mikeabdullah.net/thl-diary-17-clearsselectiononviewwillappear.html 
[2]https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioncoordinator 
[3]https://github.com/kemchenj/DeselectRowTheBestWay 
[4]https://www.jianshu.com/p/6ec14f6762e5

 推薦↓↓↓ 

👉16個技術公眾號】都在這裏!

涵蓋:程序員大咖、源碼共讀、程序員共讀、數據結構與算法、黑客技術和網絡安全、大數據科技、編程前端、Java、Python、Web編程開發、Android、iOS開發、Linux、數據庫研發、幽默程序員等。

萬水千山總是情,點個 “在看” 行不行
https://hk.wxwenku.com/d/201182913