Demo GIF
這篇主要講手勢的使用,關於動畫實作的部分,在 上篇 有詳細的code
接下來要在我們建立的 CustomTransition
裡面,跟系統說:我們的互動事件要透過誰來管理互動。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CustomTransition: NSObject, UIViewControllerTransitioningDelegate {
// ...
let interaction = UpToDownIneraction()
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return nil
}
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
print(interation.interacting)
return interaction.interacting ? interaction : nil
}
}
這邊使用 interaction.interacting ? interaction : nil
這樣的寫法,翻成中文大概的意思是:如果已經在處理互動了,那就不用多嘴跟系統說誰來處理互動。所以在 UpToDownIneraction 裡面我們宣告一個 interacting 來負責是否在互動中。
注意這兩個 func 在 UIViewControllerTransitioningDelegate 裡面其實是 optional 的,如果不實做的話,系統會使用原生的手勢,ex: navigation 會有右滑返回的手勢。回傳 nil 同理。 ** 那這邊只使用 interactionControllerForDismissal ,也就是 dismiss 需要的透過誰來管理。因為在出現的部分如果使用手勢,不熟悉的使用者可能會產生疑惑,除非UI上有明顯的提示,例如一個箭頭,加上一個抖動的動畫,不然使用點擊事件來當作出現的觸發條件會是比較好的選擇。
接下來就是實作互動管理了
先把 class 建立起來,並寫好我們需要的 property
1
2
3
4
5
6
7
class UpToDownIneraction: UIPercentDrivenInteractiveTransition {
var interacting: Bool = false
private var couldComplete: Bool = false
private weak var presentingViewController: UIViewController? = nil
}
細心的人可能發現我們繼承的 class 是 UIPercentDrivenInteractiveTransition
而不上面 func 需要回傳的 UIViewControllerInteractiveTransitioning
這是蘋果提供的一個方便的類別,如果動畫中使用的是 UIView.animation
,那我們就只需要告訴系統現在要 run 到第幾 % 。
interacting 是管理是否在互動中
couldComplete 負責的是,如果滑到一半,就放開手指,這時候是要跑完整個動畫,還是恢復動畫前的狀態。
presentingViewController 是我們需要加上手勢的 ViewController
(除了配合手勢,執行動畫,還需要跟系統說我們要 dismiss 還是 pop,所以需要一個 property 存起來方便之後判斷)
然後實作我們需要的方法
首先加上手勢,並做好手勢需要執行的空方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class UpToDownIneraction: UIPercentDrivenInteractiveTransition {
// ...
func wireGesture(on viewController: UIViewController) {
presentingViewController = viewController
let gesture = UIPanGestureRecognizer(target: self, action: #selector(handleGesture(_:)))
viewController.view.addGestureRecognizer(gesture)
}
@objc func handleGesture(_ gestureRecoginizer: UIPanGestureRecognizer) {
// ...
}
}
接著實作手勢執行的方法, UIPercentDrivenInteractiveTransition
類別提供了我們幾個方法
- update(_ percentComplete: CGFloat)
- cancel()
- finish()
很明顯我們只需要算好手勢在畫面上移動的比例,然後使用 update 來更新,並在適當的時機跟系統說我們要 取消 或是 完成 動畫,即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class UpToDownIneraction: UIPercentDrivenInteractiveTransition {
// ...
@objc func handleGesture(_ gestureRecoginizer: UIPanGestureRecognizer) {
let gestureView = gestureRecoginizer.view!
let trainsiton = gestureRecoginizer.translation(in: gestureView)
switch gestureRecoginizer.state {
case .began:
print("began")
interacting = true
if let naviController = presentingViewController?.navigationController {
naviController.popViewController(animated: true)
} else {
presentingViewController?.dismiss(animated: true, completion: nil)
}
case .changed:
var fraction = trainsiton.y / gestureView.frame.height
print("\(fraction)")
fraction = max(fraction, 0.0)
fraction = min(fraction, 1)
couldComplete = fraction > 0.4
update(fraction)
case .cancelled, .ended:
interacting = false
if couldComplete == false || gestureRecoginizer.state == .cancelled {
cancel()
} else {
finish()
}
default:
break
}
}
}
484很簡單?這邊特別提醒一下,如果動畫不是使用 UIView.animation 來實作,那麽 UIPercentDrivenInteractiveTransition
就不適用了。這個坑害我搞了好久。
萬事俱備只欠東風
1
2
3
4
5
6
7
8
@objc func buttonPressed() {
let vc = PresentSecondViewController()
animation.destinationPoint = button.center
vc.transitioningDelegate = animation
vc.modalPresentationStyle = .custom
animation.interaction.wireGesture(on: vc)
present(vc, animated: true, completion: nil)
}
完整的範例在 Github