在iOS中,如何向下拖动以关闭模态? [英] In iOS, how to drag down to dismiss a modal?

查看:1596
本文介绍了在iOS中,如何向下拖动以关闭模态?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

解雇模态的常用方法是向下滑动 - 我们如何允许用户向下拖动模态,如果它足够远,模态被解除,否则它会动画回到原始位置?



例如,我们可以在Twitter应用的照片视图或Snapchat的发现模式中找到这个。



类似的主题点当用户向下滑动时,我们可以使用UISwipeGestureRecognizer和[self dismissViewControllerAnimated ...]来关闭模态VC。但是这只会处理一次滑动,而不是让用户拖动模态。

解决方案

我刚创建了一个交互式拖动教程下一个模态来解雇它。





如果你只是想自己运行代码,那就是回购:



https://github.com/ThornTechPublic/InteractiveModal



这是我使用的方法:



查看控制器



您可以使用自定义动画覆盖解雇动画。如果用户正在拖动模态,交互器将启动。

  import UIKit 

class ViewController:UIViewController {
let interactor = Interactor()
override func prepareForSegue(segue:UIStoryboardSegue,sender:AnyObject?){
if let destinationViewController = segue.destinationViewController为? ModalViewController {
destinationViewController.transitioningDelegate = self
destinationViewController.interactor = interactor
}
}
}

扩展名ViewController:UIViewControllerTransitioningDelegate {
func animationController(forDismissed dismissed:UIViewController) - > UIViewControllerAnimatedTransitioning? {
return DismissAnimator()
}
func interactionControllerForDismissal(animator:UIViewControllerAnimatedTransitioning) - > UIViewControllerInteractiveTransitioning? {
返回interactor.hasStarted? interctor:nil
}
}



Dismiss Animator



您可以创建自定义动画师。这是一个自定义动画,您在 UIViewControllerAnimatedTransitioning 协议中打包。

  import UIKit 

class DismissAnimator:NSObject {
}

扩展名DismissAnimator:UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext:UIViewControllerContextTransitioning?) - > NSTimeInterval {
返回0.6
}

func animateTransition(transitionContext:UIViewControllerContextTransitioning){
guard
let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey),
let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey),
let containerView = transitionContext.containerView()
else {
return
}
containerView.insertSubview(toVC。 view,belowSubview:fromVC.view)
let screenBounds = UIScreen.mainScreen()。bounds
let bottomLeftCorner = CGPoint(x:0,y:screenBounds.height)
let finalFrame = CGRect( origin:bottomLeftCorner,size:screenBounds.size)

UIView.animateWithDuration(
transitionDuration(transitionContext),
animations:{
fromVC.view.frame = finalFrame
},
完成:{_ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
}

}
}



交互者



你的子类 UIPercentDrivenInteractiveTransition 以便它可以充当您的状态机。由于两个VC都可以访问交互对象,因此可以使用它来跟踪平移进度。

  import UIKit 

class Interactor:UIPercentDrivenInteractiveTransition {
var hasStarted = false
var shouldFinish = false
}



模态视图控制器



这会将平移手势状态映射到交互方法调用。 translationInView() y 值确定用户是否超过阈值。当平移手势 .Ended 时,交互者将完成或取消。

  import UIKit 

class ModalViewController:UIViewController {

var interactor:Interactor? = nil

@IBAction func close(发送者:UIButton){
dismissViewControllerAnimated(true,completion:nil)
}

@IBAction func handleGesture(发件人:UIPanGestureRecognizer){
let percentThreshold:CGFloat = 0.3

//将y-position转换为向下拉进度(百分比)
let translation = sender.translationInView(view)
let verticalMovement = translation.y / view.bounds.height
let downwardMovement = fmaxf(Float(verticalMovement),0.0)
let downwardMovementPercent = fminf(downwardMovement,1.0)
let progress = CGFloat(downwardMovementPercent)
guard let interactor = interactor else {return}

switch sender.state {
case .Began:
interactor.hasStarted = true
dismissViewControllerAnimated(true,completion:nil)
case .Changed:
interactor.shouldFinish =进展> percentThreshold
interactor.updateInteractiveTransition(progress)
case .Cancelled:
interactor.hasStarted = false
interactor.cancelInteractiveTransition()
case .Ended:
interaction .hasStarted = false
interactor.shouldFinish
? interactor.finishInteractiveTransition()
:interactor.cancelInteractiveTransition()
默认值:
break
}
}

}


A common way to dismiss a modal is to swipe down - How do we allows the user to drag the modal down, if it's far enough, the modal's dismissed, otherwise it animates back to the original position?

For example, we can find this used on the Twitter app's photo views, or Snapchat's "discover" mode.

Similar threads point out that we can use a UISwipeGestureRecognizer and [self dismissViewControllerAnimated...] to dismiss a modal VC when a user swipes down. But this only handles a single swipe, not letting the user drag the modal around.

解决方案

I just created a tutorial for interactively dragging down a modal to dismiss it.

http://www.thorntech.com/2016/02/ios-tutorial-close-modal-dragging/

I found this topic to be confusing at first, so the tutorial builds this out step-by-step.

If you just want to run the code yourself, this is the repo:

https://github.com/ThornTechPublic/InteractiveModal

This is the approach I used:

View Controller

You override the dismiss animation with a custom one. If the user is dragging the modal, the interactor kicks in.

import UIKit

class ViewController: UIViewController {
    let interactor = Interactor()
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if let destinationViewController = segue.destinationViewController as? ModalViewController {
            destinationViewController.transitioningDelegate = self
            destinationViewController.interactor = interactor
        }
    }
}

extension ViewController: UIViewControllerTransitioningDelegate {
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return DismissAnimator()
    }
    func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return interactor.hasStarted ? interactor : nil
    }
}

Dismiss Animator

You create a custom animator. This is a custom animation that you package inside a UIViewControllerAnimatedTransitioning protocol.

import UIKit

class DismissAnimator : NSObject {
}

extension DismissAnimator : UIViewControllerAnimatedTransitioning {
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
        return 0.6
    }

    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        guard
            let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey),
            let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey),
            let containerView = transitionContext.containerView()
            else {
                return
        }
        containerView.insertSubview(toVC.view, belowSubview: fromVC.view)
        let screenBounds = UIScreen.mainScreen().bounds
        let bottomLeftCorner = CGPoint(x: 0, y: screenBounds.height)
        let finalFrame = CGRect(origin: bottomLeftCorner, size: screenBounds.size)

        UIView.animateWithDuration(
            transitionDuration(transitionContext),
            animations: {
                fromVC.view.frame = finalFrame
            },
            completion: { _ in
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
            }
        )
    }
}

Interactor

You subclass UIPercentDrivenInteractiveTransition so that it can act as your state machine. Since the interactor object is accessed by both VCs, use it to keep track of the panning progress.

import UIKit

class Interactor: UIPercentDrivenInteractiveTransition {
    var hasStarted = false
    var shouldFinish = false
}

Modal View Controller

This maps the pan gesture state to interactor method calls. The translationInView() y value determines whether the user crossed a threshold. When the pan gesture is .Ended, the interactor either finishes or cancels.

import UIKit

class ModalViewController: UIViewController {

    var interactor:Interactor? = nil

    @IBAction func close(sender: UIButton) {
        dismissViewControllerAnimated(true, completion: nil)
    }

    @IBAction func handleGesture(sender: UIPanGestureRecognizer) {
        let percentThreshold:CGFloat = 0.3

        // convert y-position to downward pull progress (percentage)
        let translation = sender.translationInView(view)
        let verticalMovement = translation.y / view.bounds.height
        let downwardMovement = fmaxf(Float(verticalMovement), 0.0)
        let downwardMovementPercent = fminf(downwardMovement, 1.0)
        let progress = CGFloat(downwardMovementPercent)
        guard let interactor = interactor else { return }

        switch sender.state {
        case .Began:
            interactor.hasStarted = true
            dismissViewControllerAnimated(true, completion: nil)
        case .Changed:
            interactor.shouldFinish = progress > percentThreshold
            interactor.updateInteractiveTransition(progress)
        case .Cancelled:
            interactor.hasStarted = false
            interactor.cancelInteractiveTransition()
        case .Ended:
            interactor.hasStarted = false
            interactor.shouldFinish
                ? interactor.finishInteractiveTransition()
                : interactor.cancelInteractiveTransition()
        default:
            break
        }
    }

}

这篇关于在iOS中,如何向下拖动以关闭模态?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆