在功能性 UIScrollView 中使用 UIPanGestureRecognizer 平移视图

Pan view using UIPanGestureRecognizer within a functional UIScrollView

问题

我有一个包含 UIViewUIScrollView,我希望允许用户使用 UIPanGestureRecognizer.

进行平移

为了使其按预期工作,用户应该能够用一根手指平移视图,但能够用另一根手指平移滚动视图 - 做同时(每人用一根手指)。

但是,当用户平移其中包含的视图时,滚动视图将停止工作。在视图的平移手势结束之前无法平移。

已尝试解决方法

我试图通过重写以下 UIGestureRecognizerDelegate 方法同时滚动平移视图和包含它的 UIScrollView 来解决此问题:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

然而,这使得平移视图 移动滚动视图。每个元素的平移手势应该相互独立,而不是链接。

演示项目

我已经创建了一个简单的演示项目来演示这一点,在这里:

https://github.com/jeffc-dev/ScrollViewPannerTest

此项目包含一个带有方形视图的滚动视图,应该能够独立于其包含的滚动视图进行平移,但不能。

我为什么要这样做

这样做的目的是让用户 easier/quicker 找到要平移视图的目的地。这有点类似于在 Springboard 中重新排列图标:您可以用一根手指平移应用程序图标,同时同时用另一根手指在页面之间快速平移找到一个地方放下它。我没有使用分页滚动视图 - 只是一个普通的滚动视图 - 我希望它是一个无缝的平移手势(我不需要 need/want 用户必须输入 'wiggle mode')但是基本原理是一样的。


更新:DonMag 提出了使用 UILongPressGestureRecognizer 将视图移出滚动视图以进行平移的想法,这看起来很有希望。但是,如果我走那条路,我认为我需要在这样做之后无缝过渡到使用 UIPanGestureRecognizer(因为我确实使用了一些特定于平移手势识别器的功能)。

我敢肯定有不同的方法可以做到这一点,但这是一种方法...

我没有使用 UIPanGesture,而是使用了 UILongPressGesture

当手势开始时,我们将视图从 scrollView 移动 到它的父视图。当我们继续按下视图并四处拖动它时,它现在独立于 scrollView。当我们结束手势(抬起手指)时,我们将视图添加回 scrollView。

拖动的时候,我们可以用第二根手指滚动滚动视图的内容。

代码的主要部分如下所示:

@objc func handleLongPress(_ g: UILongPressGestureRecognizer) -> Void {
    
    switch g.state {
    
    case .began:
        
        // get our superview and its superview
        guard let sv = superview as? UIScrollView,
              let ssv = sv.superview
        else {
            return
        }
        theScrollView = sv
        theRootView = ssv
        
        // convert center coords
        let cvtCenter = theScrollView.convert(self.center, to: theRootView)
        self.center = cvtCenter
        curCenter = self.center
        
        // add self to ssv (removes self from sv)
        ssv.addSubview(self)
        
        // start wiggling anim
        startAnim()
        
        // inform the controller
        startCallback?(self)
        
    case .changed:
        
        guard let thisView = g.view else {
            return
        }
        
        // get the gesture point
        let point = g.location(in: thisView.superview)
        
        // Calculate new center position
        var newCenter = thisView.center;
        newCenter.x += point.x - curCenter.x;
        newCenter.y += point.y - curCenter.y;
        
        // Update view center
        thisView.center = newCenter
        curCenter = newCenter
        
        // inform the controller
        movedCallback?(self)
        
    default:
        
        // stop wiggle anim
        stopAnim()
        
        // convert center to scroll view (original superview) coords
        let cvtCenter = theRootView.convert(curCenter, to: theScrollView)
        
        // update center
        self.center = cvtCenter
        
        // add self back to scroll view
        theScrollView.addSubview(self)
        
        // inform the controller
        endedCallback?(self)
        
    }
    
}

我分叉了你的 GitHub 仓库并添加了一个新的控制器来演示:https://github.com/DonMag/ScrollViewPannerTest

您会发现它只是此方法的起点。被拖拽的view(其实在这个demo中,可以用两根手指同时拖拽两个view)使用闭包通知controller拖拽...

目前,“drag/drop”不影响滚动视图中的任何其他子视图。唯一做任何事情的闭包是“结束”闭包,此时控制器重新计算 scrollView 的 contentSize。 “移动”闭包可用于重新定位视图——但那是另一项任务。