【问题标题】:Interactive Delegate Methods Never Called从未调用过的交互式委托方法
【发布时间】:2014-12-28 02:33:14
【问题描述】:

我想在 ViewController (1) 和 NavigationViewController (2) 之间进行交互转换。

NavigationController 由按钮调用,因此在呈现时没有交互式过渡。它可以通过按钮或 UIPanGestureRecognizer 关闭,因此可以交互方式关闭或不关闭。

我有一个名为 TransitionManager 的对象,用于 UIPercentDrivenInteractiveTransition 的转换子类。

下面代码的问题是两个委托方法interactionControllerFor... 从未被调用。

此外,当我按下按钮或滑动 (UIPanGestureRecognizer) 时,模态转场的基本动画就完成了。所以animationControllerFor...这两个委托方法也不起作用。

有什么想法吗?谢谢

ViewController.swift

let transitionManager = TransitionManager()

override func viewDidLoad() {
    super.viewDidLoad()

    self.transitioningDelegate = transitionManager
}

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

        let dest = segue.destinationViewController as UIViewController
        dest.transitioningDelegate = transitionManager
        dest.modalPresentationStyle = .Custom
}

TransitionManager.swift

class TransitionPushManager: UIPercentDrivenInteractiveTransition,
 UINavigationControllerDelegate, UIViewControllerTransitioningDelegate {


@IBOutlet var navigationController: UINavigationController!

var animation : Animator! // Implement UIViewControllerAnimatedTransitioning protocol


override func awakeFromNib() {
    var panGesture = UIPanGestureRecognizer(target: self, action: "gestureHandler:")
    navigationController.view.addGestureRecognizer(panGesture)

    animation = Animator()
}

func gestureHandler(pan : UIPanGestureRecognizer) {

    switch pan.state {

    case .Began :

        interactive = true

            navigationController.presentingViewController?.dismissViewControllerAnimated(true, completion:nil)


    case .Changed :

        ...            

    default :

        ...

        interactive = false

    }

}


func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return animation
}

func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return animation
}

func interactionControllerForPresentation(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
    return nil
}

func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
    return self.interactive ? self : nil
}

Main.storyboard

  • ViewController 上的按钮触发模态 segue 以呈现 NavigationController

  • NavigationController 的代理出口链接到 TransitionManager 类的对象

  • NavigationController 在TransitionManager 类中由属性“navigationController”引用

【问题讨论】:

    标签: ios swift delegates transition interactive


    【解决方案1】:

    我认为关键问题是您在viewDidLoad 中配置transitionDelegate。在这个过程中,这通常为时已晚。你应该像init导航控制器那样做。

    假设您的根场景(“Root”)呈现导航控制器场景(“Nav”),然后从场景 A 推送到 B 到 C,例如,我想像这样的对象模型,其中导航控制器将拥有自己的动画控制器、交互控制器和手势识别器:

    当“root”呈现“nav”时,这就是您在考虑 (a) 自定义转换(非交互式)时所需要的; (b) 当“nav”自行关闭以返回“根”时的自定义转换(交互或非交互)。所以,我将导航控制器子类化:

    • 在其视图中添加手势识别器;

    • 设置 transitioningDelegate 以在您从根场景过渡到导航控制器场景(并返回)时生成自定义动画:

    • transitioningDelegate 还将返回交互控制器(仅在手势识别器正在进行时存在),如果您在手势上下文之外关闭,则在手势期间产生交互式转换和非交互式转换.

    在 Swift 3 中,它看起来像:

    import UIKit
    import UIKit.UIGestureRecognizerSubclass
    
    class CustomNavigationController: UINavigationController {
    
        public required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            configure()
        }
    
        override init(rootViewController: UIViewController) {
            super.init(rootViewController: rootViewController)
            configure()
        }
    
        private func configure() {
            transitioningDelegate = self   // for presenting the original navigation controller
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            delegate = self                // for navigation controller custom transitions
    
            let left = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleSwipeFromLeft(_:)))
            left.edges = .left
            view.addGestureRecognizer(left)
        }
    
        fileprivate var interactionController: UIPercentDrivenInteractiveTransition?
    
        func handleSwipeFromLeft(_ gesture: UIScreenEdgePanGestureRecognizer) {
            let percent = gesture.translation(in: gesture.view!).x / gesture.view!.bounds.size.width
    
            if gesture.state == .began {
                interactionController = UIPercentDrivenInteractiveTransition()
                if viewControllers.count > 1 {
                    popViewController(animated: true)
                } else {
                    dismiss(animated: true)
                }
            } else if gesture.state == .changed {
                interactionController?.update(percent)
            } else if gesture.state == .ended {
                if percent > 0.5 && gesture.state != .cancelled {
                    interactionController?.finish()
                } else {
                    interactionController?.cancel()
                }
                interactionController = nil
            }
        }
    }
    
    // MARK: - UINavigationControllerDelegate
    //
    // Use this for custom transitions as you push/pop between the various child view controllers 
    // of the navigation controller. If you don't need a custom animation there, you can comment this
    // out.
    
    extension CustomNavigationController: UINavigationControllerDelegate {
    
        func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    
            if operation == .push {
                return ForwardAnimator()
            } else if operation == .pop {
                return BackAnimator()
            }
            return nil
        }
    
        func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
            return interactionController
        }
    
    }
    
    // MARK: - UIViewControllerTransitioningDelegate
    //
    // This is needed for the animation when we initially present the navigation controller. 
    // If you're only looking for custom animations as you push/pop between the child view
    // controllers of the navigation controller, this is not needed. This is only for the 
    // custom transition of the initial `present` and `dismiss` of the navigation controller 
    // itself.
    
    extension CustomNavigationController: UIViewControllerTransitioningDelegate {
    
        func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            return ForwardAnimator()
        }
    
        func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            return BackAnimator()
        }
    
        func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
            return interactionController
        }
    
        func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
            return interactionController
        }
    
        func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
            return PresentationController(presentedViewController: presented, presenting: presenting)
        }
    
    }
    
    // When doing custom `present`/`dismiss` that overlays the entire
    // screen, you generally want to remove the presenting view controller's
    // view from the view hierarchy. This presentation controller
    // subclass accomplishes that for us.
    
    class PresentationController: UIPresentationController {
        override var shouldRemovePresentersView: Bool { return true }
    }
    
    // You can do whatever you want in the animation; I'm just fading
    
    class ForwardAnimator : NSObject, UIViewControllerAnimatedTransitioning {
    
        func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
            return 0.5
        }
    
        func animateTransition(using context: UIViewControllerContextTransitioning) {
            let toView = context.viewController(forKey: .to)!.view!
    
            context.containerView.addSubview(toView)
    
            toView.alpha = 0.0
    
            UIView.animate(withDuration: transitionDuration(using: context), animations: {
                toView.alpha = 1.0
            }, completion: { finished in
                context.completeTransition(!context.transitionWasCancelled)
            })
        }
    
    }
    
    class BackAnimator : NSObject, UIViewControllerAnimatedTransitioning {
    
        func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
            return 0.5
        }
    
        func animateTransition(using context: UIViewControllerContextTransitioning) {
            let toView   = context.viewController(forKey: .to)!.view!
            let fromView = context.viewController(forKey: .from)!.view!
    
            context.containerView.insertSubview(toView, belowSubview: fromView)
    
            UIView.animate(withDuration: transitionDuration(using: context), animations: {
                fromView.alpha = 0.0
            }, completion: { finished in
                context.completeTransition(!context.transitionWasCancelled)
            })
        }
    }
    

    所以,我可以将故事板中导航控制器的基类更改为这个自定义子类,现在根场景可以只呈现导航控制器(没有特殊的prepare(for:)),一切正常。

    【讨论】:

    • 好的,非常感谢您的帮助,错误见谅(比如“强”,我还是不明白强弱的区别……),我还在开始在编程中。你已经在另一篇文章中帮助了我,我说“问题是我并不真正理解使用交互式过渡时我在做什么”。这就是为什么我的代码有点乱,而且不是很好......你的解决方案似乎很干净,我只是有一个问题:我的 NavigationController 中有 5 个 ViewController,我还需要它们之间的交互过渡(使用推送 segues) ,它与您的代码兼容吗?
    • 实际上,经过一周的努力、错误和问题,我有我的 TransitionManager 类来管理转换:模态或推送,交互与否,呈现或解雇。我只有最后一个问题(著名的“最后一个”问题):NavigationController 的第一个 VC 和 Root 之间的转换,这是一个消除模态转换,不是交互式的。我有我在另一篇文章中解释的问题:interactive 属性在 interactionControllerForDismissal 方法中为 false。
    • 我有点害怕改变我所有的代码会给我带来我无法解决的错误并取消我所有的工作......
    • @user3780788 如果您想尝试修补您当前的解决方案以使其正常工作,那当然是您的选择(尽管我不会和您一起冒险)。不过,恕我直言,我只是觉得设计有缺陷,这就是我提出上述方法的原因。请随意查看我的 github 演示 (github.com/robertmryan/Interactive-Custom-Transitions-in-Swift),其中我对上述内容进行了非常适度的更改,不仅负责从根到导航控制器的转换,还负责在 A、B 之间推送和弹出时的交互式转换, 和 C.
    • 我不想打扰你,我在 Github 上看到了你的项目,这正是我这周想要的……我认为它会对人们有所帮助。感谢您的宝贵帮助!
    猜你喜欢
    • 1970-01-01
    • 2011-08-12
    • 2020-01-26
    • 1970-01-01
    • 2013-07-02
    • 1970-01-01
    相关资源
    最近更新 更多