Обычный способ отклонить модальное окно - провести пальцем вниз. Как мы можем позволить пользователю перетащить модальное окно вниз, если оно достаточно далеко, модальное окно закрывается, иначе оно возвращается в исходное положение?
Например, мы можем найти это используемым в просмотрах фотографий в приложении Twitter или в режиме «обнаружения» Snapchat.
Подобные потоки указывают на то, что мы можем использовать UISwipeGestureRecognizer и [self dismissViewControllerAnimated ...], чтобы закрыть модальный VC, когда пользователь проведет пальцем вниз. Но это обрабатывает только одно движение, не позволяя пользователю перетаскивать модальное окно.
Ответы:
Я только что создал учебник для интерактивного перетаскивания модального окна, чтобы его закрыть.
http://www.thorntech.com/2016/02/ios-tutorial-close-modal-dragging/
Поначалу я обнаружил, что эта тема сбивает с толку, поэтому в руководстве она рассматривается поэтапно.
Если вы просто хотите запустить код самостоятельно, это репо:
https://github.com/ThornTechPublic/InteractiveModal
Я использовал такой подход:
Просмотр контроллера
Вы заменяете анимацию закрытия пользовательской. Если пользователь перетаскивает модальное окно,
interactor
срабатывает.import UIKit class ViewController: UIViewController { let interactor = Interactor() override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if let destinationViewController = segue.destinationViewController as? ModalViewController { destinationViewController.transitioningDelegate = self destinationViewController.interactor = interactor } } } extension ViewController: UIViewControllerTransitioningDelegate { func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return DismissAnimator() } func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return interactor.hasStarted ? interactor : nil } }
Закрыть Animator
Вы создаете собственный аниматор. Это настраиваемая анимация, которую вы упаковываете в
UIViewControllerAnimatedTransitioning
протокол.import UIKit class DismissAnimator : NSObject { } extension DismissAnimator : UIViewControllerAnimatedTransitioning { func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { return 0.6 } func animateTransition(transitionContext: UIViewControllerContextTransitioning) { guard let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey), let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey), let containerView = transitionContext.containerView() else { return } containerView.insertSubview(toVC.view, belowSubview: fromVC.view) let screenBounds = UIScreen.mainScreen().bounds let bottomLeftCorner = CGPoint(x: 0, y: screenBounds.height) let finalFrame = CGRect(origin: bottomLeftCorner, size: screenBounds.size) UIView.animateWithDuration( transitionDuration(transitionContext), animations: { fromVC.view.frame = finalFrame }, completion: { _ in transitionContext.completeTransition(!transitionContext.transitionWasCancelled()) } ) } }
Interactor
Вы создаете подкласс,
UIPercentDrivenInteractiveTransition
чтобы он мог действовать как конечный автомат. Поскольку к объекту интерактора обращаются оба виртуальных канала, используйте его для отслеживания прогресса панорамирования.import UIKit class Interactor: UIPercentDrivenInteractiveTransition { var hasStarted = false var shouldFinish = false }
Контроллер модального представления
Это отображает состояние жеста панорамирования на вызовы методов интерактора.
translationInView()
y
Значение определяет , пересек ли пользователь порог. Когда жест панорамирования равен.Ended
, интерактор либо завершает, либо отменяет.import UIKit class ModalViewController: UIViewController { var interactor:Interactor? = nil @IBAction func close(sender: UIButton) { dismissViewControllerAnimated(true, completion: nil) } @IBAction func handleGesture(sender: UIPanGestureRecognizer) { let percentThreshold:CGFloat = 0.3 // convert y-position to downward pull progress (percentage) let translation = sender.translationInView(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) guard let interactor = interactor else { return } switch sender.state { case .Began: interactor.hasStarted = true dismissViewControllerAnimated(true, completion: nil) case .Changed: interactor.shouldFinish = progress > percentThreshold interactor.updateInteractiveTransition(progress) case .Cancelled: interactor.hasStarted = false interactor.cancelInteractiveTransition() case .Ended: interactor.hasStarted = false interactor.shouldFinish ? interactor.finishInteractiveTransition() : interactor.cancelInteractiveTransition() default: break } } }
источник
segue
на,over current context
чтобы избежать черного экрана сзади, когда вы опускаете viewControllerПоделюсь, как я это сделал в Swift 3:
Результат
Реализация
class MainViewController: UIViewController { @IBAction func click() { performSegue(withIdentifier: "showModalOne", sender: nil) } }
class ModalOneViewController: ViewControllerPannable { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .yellow } @IBAction func click() { performSegue(withIdentifier: "showModalTwo", sender: nil) } }
class ModalTwoViewController: ViewControllerPannable { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .green } }
Контроллеры модальных представлений наследуются от
class
созданного мной (ViewControllerPannable
), чтобы сделать их перетаскиваемыми и запрещенными при достижении определенной скорости.ViewControllerPannable класс
class ViewControllerPannable: UIViewController { var panGestureRecognizer: UIPanGestureRecognizer? var originalPosition: CGPoint? var currentPositionTouched: CGPoint? override func viewDidLoad() { super.viewDidLoad() panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panGestureAction(_:))) view.addGestureRecognizer(panGestureRecognizer!) } func panGestureAction(_ panGesture: UIPanGestureRecognizer) { let translation = panGesture.translation(in: view) if panGesture.state == .began { originalPosition = view.center currentPositionTouched = panGesture.location(in: view) } else if panGesture.state == .changed { view.frame.origin = CGPoint( x: translation.x, y: translation.y ) } else if panGesture.state == .ended { let velocity = panGesture.velocity(in: view) if velocity.y >= 1500 { UIView.animate(withDuration: 0.2 , animations: { self.view.frame.origin = CGPoint( x: self.view.frame.origin.x, y: self.view.frame.size.height ) }, completion: { (isCompleted) in if isCompleted { self.dismiss(animated: false, completion: nil) } }) } else { UIView.animate(withDuration: 0.2, animations: { self.view.center = self.originalPosition! }) } } } }
источник
Storyboard segue
изMainViewController
кModalViewController
: установите Представление собственности Over Current ContextВот однофайловое решение, основанное на ответе @wilson (спасибо 👍) со следующими улучшениями:
Список улучшений из предыдущего решения
y
координатыview.frame.origin
let y = max(0, translation.y)
currentPositionTouched
иoriginalPosition
minimumVelocityToHide
: какой скорости достаточно, чтобы скрыть (по умолчанию 1500)minimumScreenRatioToHide
: насколько низко, чтобы скрыть (по умолчанию 0,5)animationDuration
: как быстро мы скрываем / показываем (по умолчанию 0,2 с)Решение
Swift 3 и Swift 4:
// // PannableViewController.swift // import UIKit class PannableViewController: UIViewController { public var minimumVelocityToHide: CGFloat = 1500 public var minimumScreenRatioToHide: CGFloat = 0.5 public var animationDuration: TimeInterval = 0.2 override func viewDidLoad() { super.viewDidLoad() // Listen for pan gesture let panGesture = UIPanGestureRecognizer(target: self, action: #selector(onPan(_:))) view.addGestureRecognizer(panGesture) } @objc func onPan(_ panGesture: UIPanGestureRecognizer) { func slideViewVerticallyTo(_ y: CGFloat) { self.view.frame.origin = CGPoint(x: 0, y: y) } switch panGesture.state { case .began, .changed: // If pan started or is ongoing then // slide the view to follow the finger let translation = panGesture.translation(in: view) let y = max(0, translation.y) slideViewVerticallyTo(y) case .ended: // If pan ended, decide it we should close or reset the view // based on the final position and the speed of the gesture let translation = panGesture.translation(in: view) let velocity = panGesture.velocity(in: view) let closing = (translation.y > self.view.frame.size.height * minimumScreenRatioToHide) || (velocity.y > minimumVelocityToHide) if closing { UIView.animate(withDuration: animationDuration, animations: { // If closing, animate to the bottom of the view self.slideViewVerticallyTo(self.view.frame.size.height) }, completion: { (isCompleted) in if isCompleted { // Dismiss the view when it dissapeared dismiss(animated: false, completion: nil) } }) } else { // If not closing, reset the view to the top UIView.animate(withDuration: animationDuration, animations: { slideViewVerticallyTo(0) }) } default: // If gesture state is undefined, reset the view to the top UIView.animate(withDuration: animationDuration, animations: { slideViewVerticallyTo(0) }) } } override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nil, bundle: nil) modalPresentationStyle = .overFullScreen; modalTransitionStyle = .coverVertical; } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) modalPresentationStyle = .overFullScreen; modalTransitionStyle = .coverVertical; } }
источник
minimumScreenRatioToHide
)self.view.frame.origin
передsliceViewVerticallyTo
первым вызовом : кажется, смещение, которое мы видим, такое же, как высота строки состояния, так что, возможно, ваше исходное происхождение не равно 0?slideViewVerticallyTo
как вложенную функцию вonPan
.создал демонстрацию для интерактивного перетаскивания вниз, чтобы отключить контроллер представления, например режим обнаружения в Snapchat. Проверьте этот github для примера проекта.
источник
Swift 4.x, используя Pangesture
Простой способ
Вертикальный
class ViewConrtoller: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(onDrage(_:)))) } @objc func onDrage(_ sender:UIPanGestureRecognizer) { let percentThreshold:CGFloat = 0.3 let translation = sender.translation(in: view) let newX = ensureRange(value: view.frame.minX + translation.x, minimum: 0, maximum: view.frame.maxX) let progress = progressAlongAxis(newX, view.bounds.width) view.frame.origin.x = newX //Move view to new position if sender.state == .ended { let velocity = sender.velocity(in: view) if velocity.x >= 300 || progress > percentThreshold { self.dismiss(animated: true) //Perform dismiss } else { UIView.animate(withDuration: 0.2, animations: { self.view.frame.origin.x = 0 // Revert animation }) } } sender.setTranslation(.zero, in: view) } }
Вспомогательная функция
func progressAlongAxis(_ pointOnAxis: CGFloat, _ axisLength: CGFloat) -> CGFloat { let movementOnAxis = pointOnAxis / axisLength let positiveMovementOnAxis = fmaxf(Float(movementOnAxis), 0.0) let positiveMovementOnAxisPercent = fminf(positiveMovementOnAxis, 1.0) return CGFloat(positiveMovementOnAxisPercent) } func ensureRange<T>(value: T, minimum: T, maximum: T) -> T where T : Comparable { return min(max(value, minimum), maximum) }
Трудный способ
Обратитесь к этому -> https://github.com/satishVekariya/DraggableViewController
источник
modalPresentationStyle = UIModalPresentationOverFullScreen
чтобы задний экран не находился заview
.Я придумал очень простой способ сделать это. Просто поместите следующий код в свой контроллер представления:
Swift 4
override func viewDidLoad() { super.viewDidLoad() let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognizerHandler(_:))) view.addGestureRecognizer(gestureRecognizer) } @IBAction func panGestureRecognizerHandler(_ sender: UIPanGestureRecognizer) { let touchPoint = sender.location(in: view?.window) var initialTouchPoint = CGPoint.zero switch sender.state { case .began: initialTouchPoint = touchPoint case .changed: if touchPoint.y > initialTouchPoint.y { view.frame.origin.y = touchPoint.y - initialTouchPoint.y } case .ended, .cancelled: if touchPoint.y - initialTouchPoint.y > 200 { dismiss(animated: true, completion: nil) } else { UIView.animate(withDuration: 0.2, animations: { self.view.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height) }) } case .failed, .possible: break } }
источник
Массово обновляет репозиторий Swift 4 .
Для Swift 3 я создал следующее, чтобы представить
UIViewController
справа налево и отклонить его жестом панорамирования. Я загрузил это как репозиторий GitHub .DismissOnPanGesture.swift
файл:// Created by David Seek on 11/21/16. // Copyright © 2016 David Seek. All rights reserved. import UIKit class DismissAnimator : NSObject { } extension DismissAnimator : UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.6 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let screenBounds = UIScreen.main.bounds let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) var x:CGFloat = toVC!.view.bounds.origin.x - screenBounds.width let y:CGFloat = toVC!.view.bounds.origin.y let width:CGFloat = toVC!.view.bounds.width let height:CGFloat = toVC!.view.bounds.height var frame:CGRect = CGRect(x: x, y: y, width: width, height: height) toVC?.view.alpha = 0.2 toVC?.view.frame = frame let containerView = transitionContext.containerView containerView.insertSubview(toVC!.view, belowSubview: fromVC!.view) let bottomLeftCorner = CGPoint(x: screenBounds.width, y: 0) let finalFrame = CGRect(origin: bottomLeftCorner, size: screenBounds.size) UIView.animate( withDuration: transitionDuration(using: transitionContext), animations: { fromVC!.view.frame = finalFrame toVC?.view.alpha = 1 x = toVC!.view.bounds.origin.x frame = CGRect(x: x, y: y, width: width, height: height) toVC?.view.frame = frame }, completion: { _ in transitionContext.completeTransition(!transitionContext.transitionWasCancelled) } ) } } class Interactor: UIPercentDrivenInteractiveTransition { var hasStarted = false var shouldFinish = false } let transition: CATransition = CATransition() func presentVCRightToLeft(_ fromVC: UIViewController, _ toVC: UIViewController) { transition.duration = 0.5 transition.type = kCATransitionPush transition.subtype = kCATransitionFromRight fromVC.view.window!.layer.add(transition, forKey: kCATransition) fromVC.present(toVC, animated: false, completion: nil) } func dismissVCLeftToRight(_ vc: UIViewController) { transition.duration = 0.5 transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) transition.type = kCATransitionPush transition.subtype = kCATransitionFromLeft vc.view.window!.layer.add(transition, forKey: nil) vc.dismiss(animated: false, completion: nil) } func instantiatePanGestureRecognizer(_ vc: UIViewController, _ selector: Selector) { var edgeRecognizer: UIScreenEdgePanGestureRecognizer! edgeRecognizer = UIScreenEdgePanGestureRecognizer(target: vc, action: selector) edgeRecognizer.edges = .left vc.view.addGestureRecognizer(edgeRecognizer) } func dismissVCOnPanGesture(_ vc: UIViewController, _ sender: UIScreenEdgePanGestureRecognizer, _ interactor: Interactor) { let percentThreshold:CGFloat = 0.3 let translation = sender.translation(in: vc.view) let fingerMovement = translation.x / vc.view.bounds.width let rightMovement = fmaxf(Float(fingerMovement), 0.0) let rightMovementPercent = fminf(rightMovement, 1.0) let progress = CGFloat(rightMovementPercent) switch sender.state { case .began: interactor.hasStarted = true vc.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 } }
Простота использования:
import UIKit class VC1: UIViewController, UIViewControllerTransitioningDelegate { let interactor = Interactor() @IBAction func present(_ sender: Any) { let vc = self.storyboard?.instantiateViewController(withIdentifier: "VC2") as! VC2 vc.transitioningDelegate = self vc.interactor = interactor presentVCRightToLeft(self, vc) } func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return DismissAnimator() } func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return interactor.hasStarted ? interactor : nil } } class VC2: UIViewController { var interactor:Interactor? = nil override func viewDidLoad() { super.viewDidLoad() instantiatePanGestureRecognizer(self, #selector(gesture)) } @IBAction func dismiss(_ sender: Any) { dismissVCLeftToRight(self) } func gesture(_ sender: UIScreenEdgePanGestureRecognizer) { dismissVCOnPanGesture(self, sender, interactor!) } }
источник
Вы описываете интерактивную настраиваемую анимацию перехода . Вы настраиваете как анимацию, так и движущий жест перехода, то есть увольнение (или нет) представленного контроллера представления. Самый простой способ реализовать это - объединить UIPanGestureRecognizer с UIPercentDrivenInteractiveTransition.
Моя книга объясняет, как это сделать, и я опубликовал примеры (из книги). Этот конкретный пример представляет собой другую ситуацию - переход сбоку, а не вниз, и он предназначен для контроллера панели вкладок, а не для представленного контроллера - но основная идея точно такая же:
https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch06p296customAnimation2/ch19p620customAnimation1/AppDelegate.swift
Если вы загрузите этот проект и запустите его, вы увидите, что происходит именно то, что вы описываете, за исключением того, что оно происходит в сторону: если перетаскивание больше половины, мы переходим, но если нет, мы отменяем и возвращаемся в место.
источник
Только вертикальное увольнение
func panGestureAction(_ panGesture: UIPanGestureRecognizer) { let translation = panGesture.translation(in: view) if panGesture.state == .began { originalPosition = view.center currentPositionTouched = panGesture.location(in: view) } else if panGesture.state == .changed { view.frame.origin = CGPoint( x: view.frame.origin.x, y: view.frame.origin.y + translation.y ) panGesture.setTranslation(CGPoint.zero, in: self.view) } else if panGesture.state == .ended { let velocity = panGesture.velocity(in: view) if velocity.y >= 150 { UIView.animate(withDuration: 0.2 , animations: { self.view.frame.origin = CGPoint( x: self.view.frame.origin.x, y: self.view.frame.size.height ) }, completion: { (isCompleted) in if isCompleted { self.dismiss(animated: false, completion: nil) } }) } else { UIView.animate(withDuration: 0.2, animations: { self.view.center = self.originalPosition! }) } }
источник
Я создал простое в использовании расширение.
Просто присуще вашему UIViewController с InteractiveViewController, и вы сделали InteractiveViewController
вызовите метод showInteractive () из вашего контроллера, чтобы он отображался как интерактивный.
источник
В цели C: вот код
в
viewDidLoad
UISwipeGestureRecognizer *swipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeDown:)]; swipeRecognizer.direction = UISwipeGestureRecognizerDirectionDown; [self.view addGestureRecognizer:swipeRecognizer]; //Swipe Down Method - (void)swipeDown:(UIGestureRecognizer *)sender{ [self dismissViewControllerAnimated:YES completion:nil]; }
источник
Вот расширение, которое я сделал на основе ответа @Wilson:
// MARK: IMPORT STATEMENTS import UIKit // MARK: EXTENSION extension UIViewController { // MARK: IS SWIPABLE - FUNCTION func isSwipable() { let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:))) self.view.addGestureRecognizer(panGestureRecognizer) } // MARK: HANDLE PAN GESTURE - FUNCTION @objc func handlePanGesture(_ panGesture: UIPanGestureRecognizer) { let translation = panGesture.translation(in: view) let minX = view.frame.width * 0.135 var originalPosition = CGPoint.zero if panGesture.state == .began { originalPosition = view.center } else if panGesture.state == .changed { view.frame.origin = CGPoint(x: translation.x, y: 0.0) if panGesture.location(in: view).x > minX { view.frame.origin = originalPosition } if view.frame.origin.x <= 0.0 { view.frame.origin.x = 0.0 } } else if panGesture.state == .ended { if view.frame.origin.x >= view.frame.width * 0.5 { UIView.animate(withDuration: 0.2 , animations: { self.view.frame.origin = CGPoint( x: self.view.frame.size.width, y: self.view.frame.origin.y ) }, completion: { (isCompleted) in if isCompleted { self.dismiss(animated: false, completion: nil) } }) } else { UIView.animate(withDuration: 0.2, animations: { self.view.frame.origin = originalPosition }) } } } }
ПРИМЕНЕНИЕ
Внутри вашего контроллера представления вы хотите, чтобы его можно было перелистывать:
override func viewDidLoad() { super.viewDidLoad() self.isSwipable() }
и его можно отключить, проведя пальцем по экрану с крайней левой стороны контроллера представления в качестве контроллера навигации.
источник
Это мой простой класс для перетаскивания ViewController с оси . Просто унаследовал свой класс от DraggableViewController.
MyCustomClass: DraggableViewController
Работает только для представленного ViewController.
// MARK: - DraggableViewController public class DraggableViewController: UIViewController { public let percentThresholdDismiss: CGFloat = 0.3 public var velocityDismiss: CGFloat = 300 public var axis: NSLayoutConstraint.Axis = .horizontal public var backgroundDismissColor: UIColor = .black { didSet { navigationController?.view.backgroundColor = backgroundDismissColor } } // MARK: LifeCycle override func viewDidLoad() { super.viewDidLoad() view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(onDrag(_:)))) } // MARK: Private methods @objc fileprivate func onDrag(_ sender: UIPanGestureRecognizer) { let translation = sender.translation(in: view) // Movement indication index let movementOnAxis: CGFloat // Move view to new position switch axis { case .vertical: let newY = min(max(view.frame.minY + translation.y, 0), view.frame.maxY) movementOnAxis = newY / view.bounds.height view.frame.origin.y = newY case .horizontal: let newX = min(max(view.frame.minX + translation.x, 0), view.frame.maxX) movementOnAxis = newX / view.bounds.width view.frame.origin.x = newX } let positiveMovementOnAxis = fmaxf(Float(movementOnAxis), 0.0) let positiveMovementOnAxisPercent = fminf(positiveMovementOnAxis, 1.0) let progress = CGFloat(positiveMovementOnAxisPercent) navigationController?.view.backgroundColor = UIColor.black.withAlphaComponent(1 - progress) switch sender.state { case .ended where sender.velocity(in: view).y >= velocityDismiss || progress > percentThresholdDismiss: // After animate, user made the conditions to leave UIView.animate(withDuration: 0.2, animations: { switch self.axis { case .vertical: self.view.frame.origin.y = self.view.bounds.height case .horizontal: self.view.frame.origin.x = self.view.bounds.width } self.navigationController?.view.backgroundColor = UIColor.black.withAlphaComponent(0) }, completion: { finish in self.dismiss(animated: true) //Perform dismiss }) case .ended: // Revert animation UIView.animate(withDuration: 0.2, animations: { switch self.axis { case .vertical: self.view.frame.origin.y = 0 case .horizontal: self.view.frame.origin.x = 0 } }) default: break } sender.setTranslation(.zero, in: view) } }
источник
Для тех, кто действительно хочет немного глубже погрузиться в Custom UIViewController Transition, я рекомендую это отличное руководство от raywenderlich.com .
Исходный окончательный образец проекта содержит ошибку. Я исправил это и загрузил в репозиторий Github. . Проект находится в Swift 5, поэтому вы можете легко запустить его и поиграть.
Вот предварительный просмотр:
И это тоже интерактивно!
Удачного взлома!
источник
Вы можете использовать UIPanGestureRecognizer для обнаружения перетаскивания пользователем и перемещения модального представления вместе с ним. Если конечная позиция находится достаточно далеко вниз, вид можно закрыть или иным образом анимировать обратно в исходное положение.
Ознакомьтесь с этим ответом для получения дополнительной информации о том, как реализовать что-то подобное.
источник