在功能性 UIScrollView 中使用 UIPanGestureRecognizer 平移视图
Pan view using UIPanGestureRecognizer within a functional UIScrollView
问题
我有一个包含 UIView
的 UIScrollView
,我希望允许用户使用 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。 “移动”闭包可用于重新定位视图——但那是另一项任务。
问题
我有一个包含 UIView
的 UIScrollView
,我希望允许用户使用 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。 “移动”闭包可用于重新定位视图——但那是另一项任务。