自定义交互过渡动画

时间:2017-02-12 19:24:48

标签: ios swift swift3 uipangesturerecognizer

我想在两个视图控制器之间实现交互式转换。我希望它是一个模态或现在的过渡。

  • 我希望应用程序从第一个视图控制器开始,并允许用户向下滑动以引入第二个视图控制器
  • 第二个视图控制器应该进入并覆盖当前(第一个视图控制器)而不是将其移开

我知道我需要使用以下内容。

  

transitioningDelegate

     

animationController(forPresented:呈现:来源:)

     

interactionControllerForPresentation(使用:)

     

UIPercentDrivenInteractiveTransition

我无法弄清楚如何实现这一切。我似乎无法在swift 3中找到任何有用的或任何有用的示例。现在我创建了一个简单的单视图应用程序,其中包含两个视图控制器VC1(蓝色背景)和VC2(黄色背景),以便轻松测试任何可能的解决方案。

1 个答案:

答案 0 :(得分:40)

有关转换委托,动画控制器和交互控制器的讨论,请参阅WWDC 2013视频Custom Transitions Using View Controllers。有关演示控制器(您还应该使用)的介绍,请参阅WWDC 2014视频View Controller Advancements in iOS 8A Look Inside Presentation Controllers

基本思想是创建一个转换委托对象,用于标识将用于自定义转换的动画控制器,交互控制器和表示控制器:

class TransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {

    /// Interaction controller
    ///
    /// If gesture triggers transition, it will set will manage its own
    /// `UIPercentDrivenInteractiveTransition`, but it must set this
    /// reference to that interaction controller here, so that this
    /// knows whether it's interactive or not.

    weak var interactionController: UIPercentDrivenInteractiveTransition?

    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return PullDownAnimationController(transitionType: .presenting)
    }

    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return PullDownAnimationController(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
    }

}

然后,您只需指定正在使用自定义转换以及应使用哪种转换委托。您可以在实例化目标视图控制器时执行此操作,也可以将其指定为目标视图控制器的init的一部分,例如:

class SecondViewController: UIViewController {

    let customTransitionDelegate = TransitioningDelegate()

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        modalPresentationStyle = .custom
        transitioningDelegate = customTransitionDelegate
    }

    ...
}

动画控制器指定动画的详细信息(如何设置动画,用于非交互式过渡的持续时间等):

class PullDownAnimationController: 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.y = -frame.size.height
            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.y = -frame.size.height
                fromView.frame = frame
            }, completion: { finished in
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            })
        }
    }

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.5
    }
}

上面的动画控制器处理呈现和解除,但如果感觉太复杂,理论上你可以把它分成两个类,一个用于呈现,另一个用于解散。但是我不想让两个不同的类紧密耦合,所以我要承担animateTransition的轻微复杂性的代价,以确保它们都很好地封装在一节课。

无论如何,我们想要的下一个对象是表示控制器。在这种情况下,表示控制器告诉我们从视图层次结构中删除原始视图控制器的视图。 (在这种情况下,我们这样做,因为您转换到的场景恰好占据整个屏幕,因此无需在视图层次结构中保留旧视图。)如果您要添加任何视图其他额外的镀铬(例如添加调光/模糊视图等),它们属于演示控制器。

无论如何,在这种情况下,演示控制器非常简单:

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

最后,你可能想要一个手势识别器:

  • 实例化UIPercentDrivenInteractiveTransition;
  • 启动过渡本身;
  • 随着手势的进展更新UIPercentDrivenInteractiveTransition;
  • 在手势完成时取消或完成交互式转换;和
  • 会在UIPercentDrivenInteractiveTransition完成后删除class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let panDown = UIPanGestureRecognizer(target: self, action: #selector(handleGesture(_:))) view.addGestureRecognizer(panDown) } var interactionController: UIPercentDrivenInteractiveTransition? // pan down transitions to next view controller func handleGesture(_ gesture: UIPanGestureRecognizer) { let translate = gesture.translation(in: gesture.view) let percent = translate.y / gesture.view!.bounds.size.height if gesture.state == .began { let controller = storyboard!.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController interactionController = UIPercentDrivenInteractiveTransition() controller.customTransitionDelegate.interactionController = interactionController show(controller, sender: self) } else if gesture.state == .changed { interactionController?.update(percent) } else if gesture.state == .ended || gesture.state == .cancelled { let velocity = gesture.velocity(in: gesture.view) if (percent > 0.5 && velocity.y == 0) || velocity.y > 0 { interactionController?.finish() } else { interactionController?.cancel() } interactionController = nil } } } (以确保它不会延迟,因此它不会干扰您以后可能要做的任何非交互式转换。 ..这是一个容易忽视的微妙的小点。

所以"呈现"视图控制器可能有一个手势识别器,可能会执行以下操作:

class SecondViewController: UIViewController {

    let customTransitionDelegate = TransitioningDelegate()

    required init?(coder aDecoder: NSCoder) {
        // as shown above
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let panUp = UIPanGestureRecognizer(target: self, action: #selector(handleGesture(_:)))
        view.addGestureRecognizer(panUp)
    }

    // pan up transitions back to the presenting view controller

    var interactionController: UIPercentDrivenInteractiveTransition?

    func handleGesture(_ gesture: UIPanGestureRecognizer) {
        let translate = gesture.translation(in: gesture.view)
        let percent   = -translate.y / gesture.view!.bounds.size.height

        if gesture.state == .began {
            interactionController = UIPercentDrivenInteractiveTransition()
            customTransitionDelegate.interactionController = interactionController

            dismiss(animated: true)
        } else if gesture.state == .changed {
            interactionController?.update(percent)
        } else if gesture.state == .ended {
            let velocity = gesture.velocity(in: gesture.view)
            if (percent > 0.5 && velocity.y == 0) || velocity.y < 0 {
                interactionController?.finish()
            } else {
                interactionController?.cancel()
            }
            interactionController = nil
        }

    }

    @IBAction func didTapButton(_ sender: UIButton) {
        dismiss(animated: true)
    }

}

你可能也想改变它,所以它只能识别向下的手势(而不是任何旧的平底锅),但希望这说明了这个想法。

你可能想要&#34;呈现&#34;视图控制器有一个手势识别器来解雇场景:

class MSDSpider(CrawlSpider):
name = "msdtest"
allowed_domains = ['workandincome.govt.nz']
start_urls = ['https://www.workandincome.govt.nz/map/definitions/alternative-housing.html',]

rules = (
    Rule(LinkExtractor(allow=('.html')), callback='parse_item', follow=False),
)

def parse_item(self, response):

    hxs = Selector(response)


    page = response.url.split("/")[-1]
    filename = '%s' % page

        with open(filename, 'wb') as f:
            f.write(response.body)
            self.log('Saved file %s' % filename)

有关上述代码的演示,请参阅https://github.com/robertmryan/SwiftCustomTransitions

看起来像:

interactive gesture demo

但是,底线,自定义过渡有点复杂,所以我再次推荐你那些原创视频。在发布任何进一步的问题之前,请务必仔细观察它们。您的大多数问题都可能会在这些视频中得到解答。