당겨서 닫을 수있는 모달 구현 (UINavigationController의 경우)
UIKit에서는 ViewController → ViewController 전환을 사용자 정의할 수 있습니다.
위 이미지처럼 버튼을 누르면 아래에서 나와서 당기면 닫을 수 있는 모달을 구현해 봅니다.
당초 이쪽의 튜토리얼을 참고로 진행하고 있었습니다만, 어떤 문제가 발생했습니다.
UINavigationController에 Pan Gesture Recognizer를 연결할 수 없다는 것입니다.
그래서 UINavigationController의 아이의 ViewController로 Pan Gesture를 핸들링해, UINavigationController에 전파하는 방법으로 목적을 달성할 수 있었으므로 코드 조각을 남겨 두고 싶습니다.
파일 구성
코드
ViewController.swift
import UIKit
class ViewController: UIViewController, UIViewControllerTransitioningDelegate {
let interactor = Interactor()
@IBAction func handleButton(_ sender: UIButton) {
let sb = UIStoryboard(name: "ModalViewController", bundle: nil)
let nc = sb.instantiateInitialViewController() as! ModalNavigationController
nc.interactor = interactor
nc.transitioningDelegate = self
present(nc, animated: true, completion: nil)
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return DismissAnimator()
}
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return interactor.hasStarted ? interactor : nil
}
}
ModalNavigationController.swift
import UIKit
class ModalNavigationController: UINavigationController {
var interactor: Interactor!
func handleGesture(_ sender: UIPanGestureRecognizer) {
let percentThreshold: CGFloat = 0.3
let translation = sender.translation(in: view)
let verticalMovement = translation.y / view.bounds.height
let downwardMovement = fmaxf(Float(verticalMovement), 0.0)
let downwardMovementPercent = fminf(downwardMovement, 1.0)
let progress = CGFloat(downwardMovementPercent)
switch sender.state {
case .began:
interactor.hasStarted = true
dismiss(animated: true, completion: nil)
case .changed:
interactor.shouldFinish = progress > percentThreshold
interactor.update(progress)
case .cancelled:
interactor.hasStarted = false
interactor.cancel()
case .ended:
interactor.hasStarted = false
interactor.shouldFinish
? interactor.finish()
: interactor.cancel()
default:
break
}
}
}
ModalViewController.swift
import UIKit
class ModalViewController: UIViewController {
@IBAction func handleDismissButton(_ sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
}
@IBAction func handleGesture(_ sender: UIPanGestureRecognizer) {
weak var nc = navigationController as? ModalNavigationController
nc?.handleGesture(sender)
}
}
DismissAnimator.swift
import UIKit
class DismissAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 1.0
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard
let fromVC = transitionContext.viewController(forKey: .from),
let toVC = transitionContext.viewController(forKey: .to)
else { return }
let containerView = transitionContext.containerView
containerView.insertSubview(toVC.view, belowSubview: fromVC.view)
let screenBounds = UIScreen.main.bounds
let bottomLeftCorner = CGPoint(x: 0, y: screenBounds.height)
let finalFrame = CGRect(origin: bottomLeftCorner, size: screenBounds.size)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
animations: {
fromVC.view.frame = finalFrame
},
completion: { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
)
}
}
Interactor.swift
import UIKit
class Interactor: UIPercentDrivenInteractiveTransition {
var hasStarted = false
var shouldFinish = false
}
GitHub
Reference
이 문제에 관하여(당겨서 닫을 수있는 모달 구현 (UINavigationController의 경우)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://qiita.com/keisei_1092/items/1a4e22a189c2c5dfdf7c텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)