Swift: создание контроллера представления для пользовательского перехода в текущем стеке навигации - PullRequest
0 голосов
/ 19 декабря 2018

Введение

Я создаю приложение, которое в своем rootViewController имеет UITableView и UIPanGestureRecognizer, прикрепленные к маленькому UIView, выступающему в роли «дескриптора», который позволяет настраивать представлениепереход контроллера для UIViewController, называемого «SlideOutViewController», который нужно панорамировать справа.

Проблема

Я заметил две проблемы с моим подходом.Но фактический пользовательский переход работает должным образом.

  1. Когда SlideOutViewController создан, он, я полагаю, не присоединен к стеку навигации, поэтому у него нет ассоциированной навигационной панели.И если я использую navigationController, чтобы поместить его в стек, я теряю интерактивный переход.

  2. Примечание: я не нашел способа подключить дескриптор к SlideOutViewController, которыйинтерактивно растягивается.Таким образом, перевод дескриптора не согласуется с положением SlideOutViewControllers.

Вопрос

  • Как добавить SlideOutViewController в стек навигации?Так что SlideOutViewController переходит с панели навигации, когда я запускаю UIPanGestureRecognizer?

Мой код

В rootViewController.

class RootViewController: UIViewController {

    ...

    let slideControllerHandle = UIView()
    var interactionController : UIPercentDrivenInteractiveTransition?

    override func viewDidLoad() {
        super.viewDidLoad()

        ... // Setting up the table view etc...

        setupPanGForSlideOutController()
    }

    private func setupPanGForSlideOutController() {
         slideControllerHandle.translatesAutoresizingMaskIntoConstraints = false
         slideControllerHandle.layer.borderColor = UIColor.black.cgColor
         slideControllerHandle.layer.borderWidth = 1
         slideControllerHandle.layer.cornerRadius = 30
         view.addSubview(slideControllerHandle)
         slideControllerHandle.frame = CGRect(x: view.frame.width - 12.5, y: view.frame.height / 2, width: 25, height: 60)
         let panGestureForCalendar = UIPanGestureRecognizer(target: self, action: #selector(handlePanGestureForSlideOutViewController(_:)))
         slideControllerHandle.addGestureRecognizer(panGestureForCalendar)
    }

    @objc private func handlePanGestureForSlideOutViewController(_ gesture: UIPanGestureRecognizer) {

         let xPosition = gesture.location(in: view).x
         let percent = 1 - (xPosition / view.frame.size.width)

         switch gesture.state {
         case .began:
             guard let slideOutController = storyboard?.instantiateViewController(withIdentifier: "CNSlideOutViewControllerID") as? SlideOutViewController else { fatalError("Sigh...") }
             interactionController = UIPercentDrivenInteractiveTransition()
        slideOutController.customTransitionDelegate.interactionController = interactionController
             self.present(slideOutController, animated: true)
         case .changed:
             slideControllerHandle.center = CGPoint(x: xPosition, y: slideControllerHandle.center.y)
             interactionController?.update(percent)
         case .ended, .cancelled:
             let velocity = gesture.velocity(in: view)
             interactionController?.completionSpeed = 0.999
             if percent > 0.5 || velocity.x < 10 {
                 UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
                     self.slideControllerHandle.center = CGPoint(x: self.view.frame.width, y: self.slideControllerHandle.center.y)
                 })
                 interactionController?.finish()
             } else {
                 UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
                     self.slideControllerHandle.center = CGPoint(x: -25, y: self.slideControllerHandle.center.y)
                 })
                 interactionController?.cancel()
             }
             interactionController = nil
         default:
             break
         }
    }

SlideOutViewController

class SlideOutViewController: UIViewController {

    var interactionController : UIPercentDrivenInteractiveTransition?
    let customTransitionDelegate = TransitionDelegate()

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        modalPresentationStyle = .custom
        transitioningDelegate = customTransitionDelegate
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .red
        navigationItem.title = "Slide Controller"
        let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addNewData(_:)))
        navigationItem.setRightBarButton(addButton, animated: true)
    }

}

Пользовательский код перехода. На основе описательного ответа Роба на этот вопрос SO

  1. TransitionDelegate

    class TransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
    
        weak var interactionController : UIPercentDrivenInteractiveTransition?
        func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            return CNRightDragAnimationController(transitionType: .presenting)
        }
    
        func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            return CNRightDragAnimationController(transitionType: .dismissing)
        }
    
        func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
            return PresentationController(presentedViewController: presented, presenting: presenting)
        }
    
        func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
            return interactionController
        }    
    
        func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
            return interactionController
        }
    }
    
  2. DragAnimatedTransitioning

    class CNRightDragAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
    
        enum TransitionType {
            case presenting
            case dismissing
        }
    
        let transitionType: TransitionType
    
        init(transitionType: TransitionType) {
            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 .presenting:
                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 .dismissing:
                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)
                })
            }
        }
    
        func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
            return 0.5
        }
    }
    
  3. PresentationController

    class PresentationController: UIPresentationController {
    
        override var shouldRemovePresentersView: Bool { return true }
    }
    

Спасибо за чтение моего вопроса.

1 Ответ

0 голосов
/ 20 декабря 2018

Код анимации, из которого вы его взяли, предназначен для пользовательских «настоящих» (например, модальных) переходов.Но если вам нужна настраиваемая навигация при нажатии / выталкивании при использовании контроллера навигации, вы указываете делегата для вашего UINavigationController, а затем возвращаете соответствующий переходящий делегат в navigationController(_:animationControllerFor:from:to:).А также внедрите navigationController(_:interactionControllerFor:) и верните туда свой контроллер взаимодействия.


Например, я бы сделал что-то вроде:

class FirstViewController: UIViewController {

    let navigationDelegate = CustomNavigationDelegate()

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationController?.delegate = navigationDelegate
        navigationDelegate.addPushInteractionController(to: view)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        navigationDelegate.pushDestination = { [weak self] in
            self?.storyboard?.instantiateViewController(withIdentifier: "Second")
        }
    }
}

Где:

class CustomNavigationDelegate: 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 CustomNavigationAnimator(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 CustomNavigationDelegate {
    func addPushInteractionController(to view: UIView) {
        let swipe = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handlePushGesture(_:)))
        swipe.edges = [.right]
        view.addGestureRecognizer(swipe)
    }

    @objc private func handlePushGesture(_ gesture: UIScreenEdgePanGestureRecognizer) {
        guard let pushDestination = pushDestination else { return }

        let position = gesture.translation(in: gesture.view)
        let percentComplete = min(-position.x / gesture.view!.bounds.width, 1.0)

        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)

        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
        }
    }
}

// MARK: - Pop

extension CustomNavigationDelegate {
    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
        }
    }
}

И

class CustomNavigationAnimator: 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
    }
}

Затем, если второй контроллер представления хотел иметь пользовательское интерактивное всплывающее окно плюс возможность прокрутки к третьему контроллеру представления:

class SecondViewController: UIViewController {

    var navigationDelegate: CustomNavigationDelegate { return navigationController!.delegate as! CustomNavigationDelegate }

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationDelegate.addPushInteractionController(to: view)
        navigationDelegate.addPopInteractionController(to: view)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        navigationDelegate.pushDestination = { [weak self] in
            self?.storyboard?.instantiateViewController(withIdentifier: "Third")
        }
    }

}

Но если последний контроллер представления не может выдвинуть что-либо, а только pop:

class ThirdViewController: UIViewController {

    var navigationDelegate: CustomNavigationDelegate { return navigationController!.delegate as! CustomNavigationDelegate }

    override func viewDidLoad() {
        super.viewDidLoad()

        navigationDelegate.addPopInteractionController(to: view)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        navigationDelegate.pushDestination = nil
    }

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...