设置平移手势的界限

Posted

技术标签:

【中文标题】设置平移手势的界限【英文标题】:Setting boundaries on pan gestures 【发布时间】:2018-02-27 12:23:56 【问题描述】:

我创建了 5 个 UIView,每个都有 UIPanGestureRecognizer,然后我将它们添加到另一个 UIView。所有这些都有效,我可以拖动它们,但我不希望它们被拖动到其父 UIView 的边界之外。我已经读到我可以使用 gestureRecognizerShouldBegin 函数实现 UIGestureRecognizerDelegate 来实现这一点,但是我找不到足够的文档来准确地了解如何实现我所需要的。像往常一样,Apple 对此的文档非常模糊。我的代码是:

    let foreheadTopViewWidth = foreheadTopView.frame.width
            let foreheadTopViewHeight = foreheadTopView.frame.height
            let touchPointOffset = touchPointSize / 2
            let touchPointYPos = (foreheadTopViewHeight / 2) - touchPointOffset

            let touchPointForehead1 = TouchPoint(touchPointSize: touchPointSize, xPosition: 0, yPosition: touchPointYPos, treatmentArea: .upperForehead)
            let touchPointForehead2 = TouchPoint(touchPointSize: touchPointSize, xPosition: ((foreheadTopViewWidth * 0.50) - touchPointOffset) / 2, yPosition: touchPointYPos, treatmentArea: .upperForehead)
            let touchPointForehead3 = TouchPoint(touchPointSize: touchPointSize, xPosition: (foreheadTopViewWidth * 0.50) - touchPointOffset, yPosition: touchPointYPos, treatmentArea: .upperForehead)
            let touchPointForehead4 = TouchPoint(touchPointSize: touchPointSize, xPosition: (foreheadTopViewWidth * 0.75) - (touchPointOffset / 0.75), yPosition: touchPointYPos, treatmentArea: .upperForehead)
            let touchPointForehead5 = TouchPoint(touchPointSize: touchPointSize, xPosition: foreheadTopViewWidth - touchPointSize, yPosition: touchPointYPos, treatmentArea: .upperForehead)

            foreheadTopView.addSubview(touchPointForehead1)
            foreheadTopView.addSubview(touchPointForehead2)
            foreheadTopView.addSubview(touchPointForehead3)
            foreheadTopView.addSubview(touchPointForehead4)
            foreheadTopView.addSubview(touchPointForehead5)

            let foreheadGesture1 = UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:)))
            let foreheadGesture2 = UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:)))
            let foreheadGesture3 = UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:)))
            let foreheadGesture4 = UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:)))
            let foreheadGesture5 = UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:)))
            foreheadGesture1.delegate = self
            foreheadGesture2.delegate = self
            foreheadGesture3.delegate = self
            foreheadGesture4.delegate = self
            foreheadGesture5.delegate = self

            touchPointForehead1.addGestureRecognizer(foreheadGesture1)
            touchPointForehead2.addGestureRecognizer(foreheadGesture2)
            touchPointForehead3.addGestureRecognizer(foreheadGesture3)
            touchPointForehead4.addGestureRecognizer(foreheadGesture4)
            touchPointForehead5.addGestureRecognizer(foreheadGesture5)

            foreheadTopView.layer.addSublayer(touchPointForehead1.lineTo(touchpoint: touchPointForehead2))
            foreheadTopView.layer.addSublayer(touchPointForehead2.lineTo(touchpoint: touchPointForehead3))
            foreheadTopView.layer.addSublayer(touchPointForehead3.lineTo(touchpoint: touchPointForehead4))
            foreheadTopView.layer.addSublayer(touchPointForehead4.lineTo(touchpoint: touchPointForehead5))

@objc func didPan(gesture: UIPanGestureRecognizer) 

        guard let touchpoint = gesture.view as? TouchPoint else 
            return
        
        if (gesture.state == .began) 
            touchpoint.center = gesture.location(in: foreheadTopView)
        

        let newCenter: CGPoint = gesture.location(in: foreheadTopView)
        let dX = newCenter.x - touchpoint.center.x
        let dY = newCenter.y - touchpoint.center.y
        touchpoint.center = CGPoint(x: touchpoint.center.x + dX, y: touchpoint.center.y + dY)

        if let outGoingTouchPoint = touchpoint.outGoingTouchPoint, let line = touchpoint.outGoingLine, let path = touchpoint.outGoingLine?.path 
            let newPath = UIBezierPath(cgPath: path)
            newPath.removeAllPoints()
            newPath.move(to: touchpoint.center)
            newPath.addLine(to: outGoingTouchPoint.center)
            line.path = newPath.cgPath
        

        if let inComingTouchPoint = touchpoint.inComingTouchPoint, let line = touchpoint.inComingLine, let path = touchpoint.inComingLine?.path 
            let newPath = UIBezierPath(cgPath: path)
            newPath.removeAllPoints()
            newPath.move(to: inComingTouchPoint.center)
            newPath.addLine(to: touchpoint.center)
            line.path = newPath.cgPath
        
    

就我需要它在平移/拖动方面做的事情而言,上面的代码工作得很好。我还扩展了我的 ViewController 以符合 UIGestureRecognizerDelegate

extension ViewController: UIGestureRecognizerDelegate 
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool 

    

但我不确定如何将可拖动视图限制在其父视图的边界内。有人可以帮忙吗?

【问题讨论】:

Restrict the movement of subview inside superview bounds的可能重复 【参考方案1】:

你必须做一些计算,希望这段代码对你有帮助。

@objc func handlepan(_ gesture:UIPanGestureRecognizer)

    let translation = gesture.translation(in: self.imagelayer)
    if let view1 = gesture.view 

        if (view1.frame.origin.x + translation.x >= 0) && (view1.frame.origin.y + translation.y >= 0) && (view1.frame.origin.x + translation.x <= view1.frame.width ) && (view1.frame.origin.y + translation.y <= view1.frame.height)
        
        view1.center = CGPoint(x:view1.center.x + translation.x,
                              y:view1.center.y + translation.y)
        
    
    gesture.setTranslation(CGPoint.zero, in: self.imagelayer)

【讨论】:

【参考方案2】:

不,委托不适合这件事。我相信你想保持手势,只是将你的观点的位置限制在他们的父母之内。为此,您需要限制平移手势更改事件。

最简单的方法是检查两个相反的点是否在父矩形内:

let topLeft = CGPoint(x: touchpoint.frame.minX + dX, y: touchpoint.frame.minY + dY)
let bottomRight = CGPoint(x: touchpoint.frame.maxX + dX, y: touchpoint.frame.maxY + dY)
if let containerView = touchpoint.superview, containerView.bounds.contains(topLeft), containerView.bounds.contains(bottomRight) 
    touchpoint.center = CGPoint(x: touchpoint.center.x + dX, y: touchpoint.center.y + dY)

所以基本上我们正在计算新的左上角和右下角点。如果这些都在它们的超级视图范围内,那么视图可能会移动到该点。

这很好,但问题是一旦发生这种情况,您的视图可能不会完全位于边界处。例如,如果用户非常快地将它向左拖动,那么在某些时候topLeft 可能是(15, 0),在下一个事件中是(-10, 0),这意味着它将忽略第二个并保持在(15, 0)

所以你需要实现对移动的限制并将其限制在它上面......

var targetFrame = CGRect(x: touchpoint.frame.origin.x + dX, y: touchpoint.frame.origin.y + dY, width: touchpoint.frame.width, height: touchpoint.frame.height)

if let containerView = touchpoint.superview 
    targetFrame.origin.x = max(0.0, targetFrame.origin.x)
    targetFrame.origin.y = max(0.0, targetFrame.origin.y)
    targetFrame.size.width = min(targetFrame.size.width, containerView.bounds.width-(targetFrame.origin.x+targetFrame.width))
    targetFrame.size.height = min(targetFrame.size.height, containerView.bounds.height-(targetFrame.origin.y+targetFrame.height))


touchpoint.frame = targetFrame

所以这应该让你的视图停留在超级视图中......

【讨论】:

谢谢两位。两者都有效,但@Matic Oblak 给出的答案更详细,所以我将其标记为正确。

以上是关于设置平移手势的界限的主要内容,如果未能解决你的问题,请参考以下文章

旋转后平移手势行为不正确

如何设置平移手势的 y 轴范围?

斯威夫特:如何设置最大/最小平移手势?

使用平移手势选择多个表格视图单元格

翻译 UX“平移”手势以设置没有上限的线性值的策略

为啥我的平移手势只能在第二次输入后识别事件?