Heroを使ったmodal viewcontrollerをドラッグ閉じるの実装
この間、画像のプレビュー画面を作った。
よくある、フルスクリーンのズームイン・ズームアウトできる画面。
閉じるときは、一応×ボタンで閉じれる。
でも、やっぱりTwitterみたいに、下スワイプして閉じれる方がかっこいいだね。
Heroとは
HeroはTransitionをいい感じにするライブラリー。
GitHub - lkzhao/Hero: Elegant transition library for iOS & tvOS
実装
例えば、ImageViewerViewControllerがある。
Screen Bをドラッグして、背景は透明になっていく、最後Screen Aになるというinteractiveなtransitionを作る。
Screen BのUI構造としては:
そして、
Step #1 Heroをimport
import Hero
Step #2
override func viewDidLoad() { super.viewDidLoad() hero.isEnabled = true // このviewControllerがhero使えるようにする // transition中透明になっていくので、animation typeをfadeに設定 hero.modalAnimationType = .fade }
Step #3 PanGestureRecognizerをviewにつける
override func viewDidLoad() { super.viewDidLoad() hero.isEnabled = true setupPanGesture() } private func setupPanGesture() { let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:))) view.addGestureRecognizer(panGesture) }
Step #4 Gestureをハンドリング
@objc func handlePanGesture(_ gestureRecognizer: UIPanGestureRecognizer) { let translation = panGesture.translation(in: nil) // transitionがどこまで進んだのかのprogessを計算。 // 上スワイプしても閉じれるようにしたいから、absを使う let progress = abs(translation.y / 2) / view.bounds.height switch panGesture.state { case .began: // transition を始める。 hero.dismissViewController() case .changed: // transitionのprogressを更新 Hero.shared.update(progress) // このままじゃ、ただだんだんfadeして画面が閉じられるだけ // 下にdragすると、画像も下にいくべき // scrollviewのpositionを変える。 let currentPosition = CGPoint(x: scrollView.center.x, y: translation.y + scrollView.center.y) Hero.shared.apply(modifiers: [.position(currentPosition)], to: scrollView) default: let velocity = panGesture.velocity(in: nil).y // dismiss完成の条件のチェック if progress + abs(velocity) / view.bounds.height > 0.5 { // transition animationがfadeだから、scrollviewのtarget positionが元々のposition // transitionが自動的に完了する間も、scrollview(要するに画像)が続いて下に移動して消えていくを実現するために、 // scrollviewのtarget position を変える必要がある // そのため、下ドラッグと上ドラッグそれぞれの場合のpositionを計算。 let destinationY: CGFloat = { if velocity >= 0 { return view.bounds.height + scrollView.bounds.size.height / 2 } else { return -scrollView.bounds.size.height / 2 } }() // そして、「scrollviewの最終状態が変わったよ」ってHeroに伝える // Heroがtargetのpositionによって、続きのtransitionの間で、 // scrollviewをいい感じにanimateしてくれる let destinationPos = CGPoint(x: scrollView.center.x, y: destinationY) Hero.shared.changeTarget(modifiers: [.position(destinationPos)], to: scrollView) Hero.shared.finish(animate: true) } else { // dismissの完成条件が足りなかったら、transitionをキャンセル Hero.shared.cancel() } } }