SlideOutViewController

SlideOutViewController

介绍

我正在创建一个应用程序,该应用程序的rootViewController中有一个UITableView和一个UIPanGestureRecognizer附加到充当“句柄”的一个小的UIView上,从而可以从右侧平移名为“SlideOutViewController”的UIViewController的自定义视图控制器过渡。

问题

我注意到我的方法有两个问题。但是实际的自定义过渡可以按预期工作。

  • 创建SlideOutViewController时,它没有连接到我认为的导航堆栈,因此它没有关联的navigationBar。如果我使用navigationController将其压入堆栈,则会失去交互式转换。
  • 注意:我还没有找到一种方法来将手柄连接到以交互方式拖出的SlideOutViewController。因此,句柄的平移与SlideOutViewControllers位置不一致。


  • 如何将SlideOutViewController添加到导航堆栈?这样,当我触发UIPanGestureRecognizer时,SlideOutViewController就可以通过NavigationBar过渡了吗?

  • 我的密码

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

    定制过渡代码。 Based on Rob's descriptive answer on this SO question
  • 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
        }
    }
    
  • 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
        }
    }
    
  • PresentationController
    class PresentationController: UIPresentationController {
    
        override var shouldRemovePresentersView: Bool { return true }
    }
    

  • 感谢您阅读我的问题。

    最佳答案

    您从中获取的动画代码是用于自定义“呈现”(例如模式)过渡的。但是,如果在使用导航控制器时希望在推入/弹出时进行自定义导航,则可以为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")
            }
        }
    
    }
    

    但是,如果最后一个视图控制器不能推到任何东西,而只能弹出:
    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
        }
    
    }
    

    10-07 19:55