Введение
Я создаю приложение, которое запускается на UIViewController
, RootViewController, встроенном в UINavigationController
.RootViewController содержит один UITableView
и пользовательский переход, инициируемый UIPanGestureRecognizer
, прикрепленным к небольшому «handleView» (типа UIView
) на правом краю экрана, что позволяет «SlideOutViewController» UIViewController
для перетаскивания на экран пользователем.
Пользовательский переход и его UIPanGestureRecognizer
работают отлично.Мой пользовательский переход выглядит как UIScreenEdgePanGestureRecognizer
, используемый для поп-взаимодействия по умолчанию, но у меня он есть и для нажатия UIViewController
.Т.е. контроллер выдвижного вида.
Выпуск
Я не могу понять, как прикрепить мой handleView к переднему краю "SlideOutViewController" во времяжест / переход (как push, так и pop), а также всегда привязка к «RootViewController» на правом краю экрана, когда переход не происходит.
Моя текущая попытка заставляет «handleView» двигаться быстрее, чем переход.В идеале handleView должен быть привязан к «SlideOutViewController».
Вопрос
Как мне управлять своим «handleView» для него 1. всегда присутствует на правом краю RootViewController, а также 2. следует за передним краем «SlideOutViewController» во время пользовательского перехода (нажатие и выталкивание)?
Мой код
RootViewController
class RootViewController: UIViewController {
...
let navigationDelegate = CNNavigationDelegate()
let handleView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = navigationDelegate
setupHandleView()
navigationDelegate.addPushInteractionController(to: handleView)
... // Additional view controller setup
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationDelegate.pushDestination = {
[weak self] in
self?.storyboard?.instantiateViewController(withIdentifier: "CNSlideOutCalendarControllerID")
}
}
private func setupHandleView() {
handleView.layer.borderWidth = 1
handleView.layer.borderColor = UIColor.black.cgColor
handleView.layer.cornerRadius = 20
handleView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(handleView)
handleView.frame = CGRect(x: view.frame.width - 12.5, y: view.frame.height / 2, width: 25, height: 60)
}
}
SlideOutViewController
class SlideOutViewController: UIViewController {
var navigationDelegate : CNNavigationDelegate { return navigationController?.delegate as! CNNavigationDelegate}
override func viewDidLoad() {
super.viewDidLoad()
navigationDelegate.addPopInteractionController(to: view)
view.backgroundColor = .red
setupNavigationBar()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationDelegate.pushDestination = nil
}
}
Делегат NavigationController
class CNNavigationDelegate: NSObject, UINavigationControllerDelegate {
var interactionController: UIPercentDrivenInteractiveTransition?
var current: UIViewController?
var pushDestination: (() -> UIViewController?)?
func navigationController(_ navigationController: UINavigationController,
animationControllerFor operation: UINavigationController.Operation,
from fromVC: UIViewController,
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return CNNavigationAnimator(transitionType: operation)
}
func navigationController(_ navigationController: UINavigationController,
interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return interactionController
}
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
current = viewController
}
}
// MARK: - Push
extension CNNavigationDelegate {
func addPushInteractionController(to view: UIView) {
let swipe = UIPanGestureRecognizer(target: self, action: #selector(handlePushGesture(_:)))
view.addGestureRecognizer(swipe)
}
@objc private func handlePushGesture(_ gesture: UIScreenEdgePanGestureRecognizer) {
guard let pushDestination = pushDestination else { return }
let position = gesture.translation(in: gesture.view?.superview)
let percentComplete = min(-position.x / UIScreen.main.bounds.width, 1.0)
let handleView = gesture.view!
switch gesture.state {
case .began:
interactionController = UIPercentDrivenInteractiveTransition()
guard let controller = pushDestination() else { fatalError("No push destination") }
current?.navigationController?.pushViewController(controller, animated: true)
case .changed:
interactionController?.update(percentComplete)
handleView.center = CGPoint(x: handleView.center.x + position.x, y: handleView.center.y)
gesture.setTranslation(.zero, in: gesture.view?.superview)
case .ended, .cancelled:
let speed = gesture.velocity(in: gesture.view)
if speed.x < 0 || (speed.x == 0 && percentComplete > 0.5) {
interactionController?.finish()
UIView.animate(withDuration: 0.1, delay: 0.5, options: .curveLinear, animations: {
handleView.center = CGPoint(x: UIScreen.main.bounds.width, y: handleView.center.y)
})
} else {
interactionController?.cancel()
UIView.animate(withDuration: 0.5, delay: 0, options: .curveLinear, animations: {
handleView.center = CGPoint(x: UIScreen.main.bounds.width, y: handleView.center.y)
})
}
interactionController = nil
default:
break
}
}
}
// MARK: - Pop
extension CNNavigationDelegate {
func addPopInteractionController(to view: UIView) {
let swipe = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handlePopGesture(_:)))
swipe.edges = [.left]
view.addGestureRecognizer(swipe)
}
@objc private func handlePopGesture(_ gesture: UIScreenEdgePanGestureRecognizer) {
let position = gesture.translation(in: gesture.view)
let percentComplete = min(position.x / gesture.view!.bounds.width, 1)
switch gesture.state {
case .began:
interactionController = UIPercentDrivenInteractiveTransition()
current?.navigationController?.popViewController(animated: true)
case .changed:
interactionController?.update(percentComplete)
case .ended, .cancelled:
let speed = gesture.velocity(in: gesture.view)
if speed.x > 0 || (speed.x == 0 && percentComplete > 0.5) {
interactionController?.finish()
} else {
interactionController?.cancel()
}
interactionController = nil
default:
break
}
}
}
NavigationAnimator
class CNNavigationAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let transitionType: UINavigationController.Operation
init(transitionType: UINavigationController.Operation) {
self.transitionType = transitionType
super.init()
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let inView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)!
let fromView = transitionContext.view(forKey: .from)!
var frame = inView.bounds
switch transitionType {
case .push:
frame.origin.x = frame.size.width
toView.frame = frame
inView.addSubview(toView)
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
toView.frame = inView.bounds
}, completion: { finished in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
case .pop:
toView.frame = frame
inView.insertSubview(toView, belowSubview: fromView)
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
frame.origin.x = frame.size.width
fromView.frame = frame
}, completion: { finished in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
case .none:
break
}
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
}
Спасибо, что прочитали мой вопрос.