在iOS中,如何向下拖动以关闭模态窗口?

107

一种常见的关闭模态框的方式是向下滑动-如何允许用户拖动模态框向下,如果足够远,模态框将关闭,否则它会以原始位置动画返回?

例如,在Twitter应用程序的照片视图或Snapchat的“发现”模式中可以找到这个使用。

类似的帖子指出,我们可以使用UISwipeGestureRecognizer和[self dismissViewControllerAnimated ...]来在用户向下滑动时关闭模态VC。但这只处理单个滑动,不允许用户拖动模态框。


1
看一下自定义交互式转换。这是你可以实现它的方式。https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIViewControllerInteractiveTransitioning_protocol/index.html - croX
1
参考了Robert Chen的https://github.com/ThornTechPublic/InteractiveModal仓库,并编写了一个包装器/处理类来处理所有内容。不再需要样板代码,支持四种基本过渡(从上到下,从下到上,从左到右和从右到左)以及解除手势 https://github.com/chamira/ProjSetup/blob/master/AppProject/_BasicSetup/UIViewControllerBasicTransitioningHandler.swift - Chamira Fernando
@ChamiraFernando,我看了你的代码,它帮了我很多。有没有办法让它包含多个方向而不是一个? - Jevon Cowell
我会做的。时间这些天是一个巨大的限制 :( - Chamira Fernando
https://github.com/satishVekariya/DraggableViewController - SPatel
它对我起作用了:https://github.com/ModernProgrammer/DragDismissDemo - Ravi
16个回答

4

如果您真的想深入了解自定义UIViewController转场,我推荐阅读来自raywenderlich.com的这篇出色的教程

原始的示例项目存在缺陷。因此,我进行了修复并将其上传到Github仓库。该项目使用Swift 5编写,因此您可以轻松运行和使用它。

这是一个预览:

它还具有交互性!

祝您愉快地编程!


4
在Objective C中: 以下是代码
viewDidLoad函数中
UISwipeGestureRecognizer *swipeRecognizer = [[UISwipeGestureRecognizer alloc]
                                             initWithTarget:self action:@selector(swipeDown:)];
swipeRecognizer.direction = UISwipeGestureRecognizerDirectionDown;
[self.view addGestureRecognizer:swipeRecognizer];

//Swipe Down Method

- (void)swipeDown:(UIGestureRecognizer *)sender{
[self dismissViewControllerAnimated:YES completion:nil];
}

如何控制下滑操作的时间,以免其被取消? - TonyTony

3
这是一个关于从轴上拖动视图控制器的简单类。只需从DraggableViewController继承您的类即可。
MyCustomClass: DraggableViewController

仅为当前 ViewController 工作。

// MARK: - DraggableViewController

public class DraggableViewController: UIViewController {

    public let percentThresholdDismiss: CGFloat = 0.3
    public var velocityDismiss: CGFloat = 300
    public var axis: NSLayoutConstraint.Axis = .horizontal
    public var backgroundDismissColor: UIColor = .black {
        didSet {
            navigationController?.view.backgroundColor = backgroundDismissColor
        }
    }

    // MARK: LifeCycle

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(onDrag(_:))))
    }

    // MARK: Private methods

    @objc fileprivate func onDrag(_ sender: UIPanGestureRecognizer) {

        let translation = sender.translation(in: view)

        // Movement indication index
        let movementOnAxis: CGFloat

        // Move view to new position
        switch axis {
        case .vertical:
            let newY = min(max(view.frame.minY + translation.y, 0), view.frame.maxY)
            movementOnAxis = newY / view.bounds.height
            view.frame.origin.y = newY

        case .horizontal:
            let newX = min(max(view.frame.minX + translation.x, 0), view.frame.maxX)
            movementOnAxis = newX / view.bounds.width
            view.frame.origin.x = newX
        }

        let positiveMovementOnAxis = fmaxf(Float(movementOnAxis), 0.0)
        let positiveMovementOnAxisPercent = fminf(positiveMovementOnAxis, 1.0)
        let progress = CGFloat(positiveMovementOnAxisPercent)
        navigationController?.view.backgroundColor = UIColor.black.withAlphaComponent(1 - progress)

        switch sender.state {
        case .ended where sender.velocity(in: view).y >= velocityDismiss || progress > percentThresholdDismiss:
            // After animate, user made the conditions to leave
            UIView.animate(withDuration: 0.2, animations: {
                switch self.axis {
                case .vertical:
                    self.view.frame.origin.y = self.view.bounds.height

                case .horizontal:
                    self.view.frame.origin.x = self.view.bounds.width
                }
                self.navigationController?.view.backgroundColor = UIColor.black.withAlphaComponent(0)

            }, completion: { finish in
                self.dismiss(animated: true) //Perform dismiss
            })
        case .ended:
            // Revert animation
            UIView.animate(withDuration: 0.2, animations: {
                switch self.axis {
                case .vertical:
                    self.view.frame.origin.y = 0

                case .horizontal:
                    self.view.frame.origin.x = 0
                }
            })
        default:
            break
        }
        sender.setTranslation(.zero, in: view)
    }
}

2
这是一个我基于 @Wilson 的回答所制作的扩展程序。
// MARK: IMPORT STATEMENTS
import UIKit

// MARK: EXTENSION
extension UIViewController {

    // MARK: IS SWIPABLE - FUNCTION
    func isSwipable() {
        let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
        self.view.addGestureRecognizer(panGestureRecognizer)
    }

    // MARK: HANDLE PAN GESTURE - FUNCTION
    @objc func handlePanGesture(_ panGesture: UIPanGestureRecognizer) {
        let translation = panGesture.translation(in: view)
        let minX = view.frame.width * 0.135
        var originalPosition = CGPoint.zero

        if panGesture.state == .began {
            originalPosition = view.center
        } else if panGesture.state == .changed {
            view.frame.origin = CGPoint(x: translation.x, y: 0.0)

            if panGesture.location(in: view).x > minX {
                view.frame.origin = originalPosition
            }

            if view.frame.origin.x <= 0.0 {
                view.frame.origin.x = 0.0
            }
        } else if panGesture.state == .ended {
            if view.frame.origin.x >= view.frame.width * 0.5 {
                UIView.animate(withDuration: 0.2
                     , animations: {
                        self.view.frame.origin = CGPoint(
                            x: self.view.frame.size.width,
                            y: self.view.frame.origin.y
                        )
                }, completion: { (isCompleted) in
                    if isCompleted {
                        self.dismiss(animated: false, completion: nil)
                    }
                })
            } else {
                UIView.animate(withDuration: 0.2, animations: {
                    self.view.frame.origin = originalPosition
                })
            }
        }
    }

}

使用方法

在您的视图控制器中,您希望可以进行滑动操作:

override func viewDidLoad() {
    super.viewDidLoad()

    self.isSwipable()
}

并且它可以通过从视图控制器的极左侧滑动来关闭,就像导航控制器一样。


嗨,我已经使用了你的代码,对于向右滑动它完美地工作,但是我想在向下滑动时解除,我该怎么做?请帮忙! - Khush

2

对于Swift 4 + Swift 5,使用UIPanGestureRecognizer。基于@SPatel上面的答案

添加这两个辅助函数:

func progressAlongAxis(_ pointOnAxis: CGFloat, _ axisLength: CGFloat) -> CGFloat {
    let movementOnAxis = pointOnAxis / axisLength
    let positiveMovementOnAxis = fmaxf(Float(movementOnAxis), 0.0)
    let positiveMovementOnAxisPercent = fminf(positiveMovementOnAxis, 1.0)
    return CGFloat(positiveMovementOnAxisPercent)
}

func ensureRange<T>(value: T, minimum: T, maximum: T) -> T where T: Comparable {
    return min(max(value, minimum), maximum)
}

通过向下拖动来解除:


class SwipeDownViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        // dismiss dragging vertically:
        view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(onDragY(_:))))
    }

    @objc func onDragY(_ sender: UIPanGestureRecognizer) {
        let percentThreshold: CGFloat = 0.3
        let translation = sender.translation(in: view)

        let newY = ensureRange(value: view.frame.minY + translation.y, minimum: 0, maximum: view.frame.maxY)
        let progress = progressAlongAxis(newY, view.bounds.height)

        view.frame.origin.y = newY // Move view to new position

        if sender.state == .ended {
            let velocity = sender.velocity(in: view)
            if velocity.y >= 300 || progress > percentThreshold {
                dismiss(animated: true) // Perform dismiss
            } else {
                UIView.animate(withDuration: 0.2, animations: {
                    self.view.frame.origin.y = 0 // Revert animation
                })
            }
        }

        sender.setTranslation(.zero, in: view)
    }
}

向右拖动以解除:


class SwipeRightViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        // dismiss dragging horizontally:
        view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(onDragX(_:))))
    }

    @objc func onDragX(_ sender: UIPanGestureRecognizer) {
        let percentThreshold: CGFloat = 0.3
        let translation = sender.translation(in: view)

        let newX = ensureRange(value: view.frame.minX + translation.x, minimum: 0, maximum: view.frame.maxX)
        let progress = progressAlongAxis(newX, view.bounds.width)

        view.frame.origin.x = newX // Move view to new position

        if sender.state == .ended {
            let velocity = sender.velocity(in: view)
            if velocity.x >= 300 || progress > percentThreshold {
                dismiss(animated: true) // Perform dismiss
            } else {
                UIView.animate(withDuration: 0.2, animations: {
                    self.view.frame.origin.x = 0 // Revert animation
                })
            }
        }

        sender.setTranslation(.zero, in: view)
    }
}



0
你可以使用UIPanGestureRecognizer来检测用户的拖动并移动模态视图。如果结束位置足够向下,视图可以被解除,或以其他方式动画返回到其原始位置。
查看此答案以获取有关如何实现此类功能的更多信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接