这两天偶然发现系统设置里 tableView deselectRow 的时机和效果都很特别,正常状况下咱们的 deselect 操做都会在 didSelect 代理方法里执行,抑或者是更加细致一点,在 viewDidAppear
里完成。html
但 iOS 原生的 App 说不,我还能够作得更好,这是系统设置里的效果:git
侧滑返回时,deselect 动画会随着滑动手势的进度而改变,搜了一下,国内彷佛没有太多相关的文章,而且我手头经常使用的几款软件都作到没有相似的效果。github
搜了一下以后,发现国外的记录也不多,只有三篇文章记录了这个交互,其中写的比较详细的是这篇 The Hit List Diary #17 – clearsSelectionOnViewWillAppear。swift
这个交互实际上是经过 UIViewController
的 transitionCoordinator
属性实现的,它的类型是 UIViewControllerTransitionCoordinator
。api
简单来讲,它能够帮助咱们在转场动画里加入一些自定义的动画,自定义动画的进度和生命周期会与转场动画保持一致,使用它能够达到更加天然和一致的转场效果,例如 push 动画里 navigationBar 背景颜色的变化,它提供了这几个方法供咱们注册动画生命周期的回调:app
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
这个协议的文档,这里摘录一段我以为比较有趣的描述:iview
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.ide
比起 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 而且规范使用的话,那要加入这个效果就很简单了。
这是完整的示例。
参考连接:
以为文章还不错的话能够关注一下个人博客