在 SwiftUI 中,如何使手势在容器视图之外处于非活动状态?
Posted
技术标签:
【中文标题】在 SwiftUI 中,如何使手势在容器视图之外处于非活动状态?【英文标题】:In SwiftUI, how can you make a gesture inactive outside a container view? 【发布时间】:2020-11-12 21:27:30 【问题描述】:我正在尝试使视图仅在其剪辑容器视图中可拖动和/或可缩放(否则它可能会遇到并与其他视图的手势冲突),但到目前为止我没有尝试过阻止手势扩展在容器的可见边界之外。
这是我不想要的行为的简化演示......
当红色 Rectangle 部分超出绿色 VStack 区域(被剪裁)时,它会响应超出绿色区域的拖动手势:
import SwiftUI
import PlaygroundSupport
struct ContentView: View
@State var position: CGPoint = CGPoint(x: 100, y: 150)
@State var lastPosition: CGPoint = CGPoint(x: 100, y: 150)
var body: some View
let drag = DragGesture()
.onChanged
self.position = CGPoint(x: $0.translation.width + self.lastPosition.x, y: $0.translation.height + self.lastPosition.y)
.onEnded _ in
self.lastPosition = self.position
return VStack
Rectangle().foregroundColor(.red)
.frame(width: 150, height: 150)
.position(self.position)
.gesture(drag)
.clipped()
.background(Color.green)
.frame(width: 200, height: 300)
PlaygroundPage.current.setLiveView(ContentView())
你会如何限制这个手势只在容器内工作(上例中的绿色区域)?
更新: @Asperi 对上述问题的解决方案效果很好,但是当我在上面的容器旁边添加第二个可拖动容器时,我在第一个容器中得到一个“死区”,我无法在其中拖动(它似乎是第二个正方形的位置如果它没有被剪裁,将覆盖第一个)。问题只发生在原始/左侧,而不是新的。我认为这与它具有更高的优先级有关,因为它被排在第二位。
这是新问题的插图:
这是更新后的代码:
struct ContentView: View
@State var position1: CGPoint = CGPoint(x: 100, y: 150)
@State var lastPosition1: CGPoint = CGPoint(x: 100, y: 150)
let dragArea1: CGRect = CGRect(x: 0, y: 0, width: 200, height: 300)
@State var position2: CGPoint = CGPoint(x: 100, y: 150)
@State var lastPosition2: CGPoint = CGPoint(x: 100, y: 150)
let dragArea2: CGRect = CGRect(x: 0, y: 0, width: 200, height: 300)
var body: some View
let drag1 = DragGesture(coordinateSpace: .named("dragArea1"))
.onChanged
guard self.dragArea1.contains($0.startLocation) else return
self.position1 = CGPoint(x: $0.translation.width + self.lastPosition1.x, y: $0.translation.height + self.lastPosition1.y)
.onEnded _ in
self.lastPosition1 = self.position1
let drag2 = DragGesture(coordinateSpace: .named("dragArea2"))
.onChanged
guard self.dragArea2.contains($0.startLocation) else return
self.position2 = CGPoint(x: $0.translation.width + self.lastPosition2.x, y: $0.translation.height + self.lastPosition2.y)
.onEnded _ in
self.lastPosition2 = self.position2
return HStack
VStack
Rectangle().foregroundColor(.red)
.frame(width: 150, height: 150)
.position(self.position1)
.gesture(drag1)
.clipped()
.background(Color.green)
.frame(width: dragArea1.width, height: dragArea1.height)
VStack
Rectangle().foregroundColor(.blue)
.frame(width: 150, height: 150)
.position(self.position2)
.gesture(drag2)
.clipped()
.background(Color.yellow)
.frame(width: dragArea2.width, height: dragArea2.height)
关于如何在任何容器外保持禁用拖动的任何想法,如已经实现的那样,但也允许在每个容器的完整范围内拖动,而不管其他容器发生什么?
【问题讨论】:
【参考方案1】:这是可能的解决方案。这个想法是在容器坐标空间中具有拖动坐标,如果起始位置超出该命名区域,则忽略拖动。
使用 Xcode 11.4 / ios 13.4 测试
struct ContentView: View
@State var position: CGPoint = CGPoint(x: 100, y: 150)
@State var lastPosition: CGPoint = CGPoint(x: 100, y: 150)
var body: some View
let area = CGRect(x: 0, y: 0, width: 200, height: 300)
let drag = DragGesture(coordinateSpace: .named("area"))
.onChanged
guard area.contains($0.startLocation) else return
self.position = CGPoint(x: $0.translation.width + self.lastPosition.x, y: $0.translation.height + self.lastPosition.y)
.onEnded _ in
self.lastPosition = self.position
return VStack
Rectangle().foregroundColor(.red)
.frame(width: 150, height: 150)
.position(self.position)
.gesture(drag)
.clipped()
.background(Color.green)
.frame(width: area.size.width, height: area.size.height)
.coordinateSpace(name: "area")
【讨论】:
非常感谢@Asperi!对于所发布的问题,这是一个非常优雅的解决方案,并且效果很好。我已经用一个后续问题更新了这个问题,该问题是由于将其中两个可拖动区域彼此相邻而产生的。如果您有解决此问题的好主意,请查看并告诉我。非常感谢! 好的,我想出了如何做这个的第二部分。我会将我的工作作为单独的答案发布。再次感谢@Asperi!【参考方案2】:这两天我一直在寻找类似问题的解决方案 @Asperi 的解决方案有帮助,但它不适用于 3 位或更多数字
我的解决方案:我添加
.contentShape(Rectangle())
之前
.gesture(DragGesture().onChanged
这篇文章帮助了我。 https://www.hackingwithswift.com/quick-start/swiftui/how-to-control-the-tappable-area-of-a-view-using-contentshape
我希望它对某人有用。
示例代码:
var body: some View
VStack
Image("my image")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(200, 200)
.clipShape(Rectangle())
.contentShape(Rectangle()) // <== this code helped me
.gesture(
DragGesture()
.onChanged
//
.onEnded _ in
//
)
对于上面的例子,代码可能是这样的:
struct ContentView: View
@State var position1: CGPoint = CGPoint(x: 100, y: 150)
@State var lastPosition1: CGPoint = CGPoint(x: 100, y: 150)
let dragArea1: CGSize = CGSize(width: 200, height: 300)
@State var position2: CGPoint = CGPoint(x: 100, y: 150)
@State var lastPosition2: CGPoint = CGPoint(x: 100, y: 150)
let dragArea2: CGSize = CGSize(width: 200, height: 300)
var body: some View
let drag = DragGesture()
.onChanged
if $0.startLocation.x <= self.dragArea1.width
self.position1 = CGPoint(x: $0.translation.width + self.lastPosition1.x, y: $0.translation.height + self.lastPosition1.y)
else
self.position2 = CGPoint(x: $0.translation.width + self.lastPosition2.x, y: $0.translation.height + self.lastPosition2.y)
.onEnded _ in
self.lastPosition1 = self.position1
self.lastPosition2 = self.position2
return HStack
VStack
Rectangle().foregroundColor(.red)
.frame(width: 150, height: 150)
.position(self.position1)
.clipped()
.background(Color.green)
.frame(width: dragArea1.width, height: dragArea1.height)
VStack
Rectangle().foregroundColor(.blue)
.frame(width: 150, height: 150)
.position(self.position2)
.clipped()
.background(Color.yellow)
.frame(width: dragArea2.width, height: dragArea2.height)
.clipShape(Rectangle()) //<=== This
.contentShape(Rectangle()) //<=== and this
.gesture(drag)
【讨论】:
你的答案不清楚,重新格式化你的帖子,你的代码中使用了很多不必要的空格和未闭合的括号 请重新格式化您的代码以获得完整的工作版本。您可以阅读guidelines 和how an answer 应该是什么样子。 非常感谢!这为我解决了问题。【参考方案3】:对于第二部分(即更新到原始问题),这就是我最终得到的结果。基本上,我将两个单独的拖动手势组合成一个覆盖整个 HStack 的手势,然后根据它在 HStack 中的起始位置将手势定向到适当的 @State 变量。
结果演示:
代码:
struct ContentView: View
@State var position1: CGPoint = CGPoint(x: 100, y: 150)
@State var lastPosition1: CGPoint = CGPoint(x: 100, y: 150)
let dragArea1: CGSize = CGSize(width: 200, height: 300)
@State var position2: CGPoint = CGPoint(x: 100, y: 150)
@State var lastPosition2: CGPoint = CGPoint(x: 100, y: 150)
let dragArea2: CGSize = CGSize(width: 200, height: 300)
var body: some View
let drag = DragGesture()
.onChanged
guard $0.startLocation.y >= 0 && $0.startLocation.y <= self.dragArea1.height else return
if $0.startLocation.x <= self.dragArea1.width
self.position1 = CGPoint(x: $0.translation.width + self.lastPosition1.x, y: $0.translation.height + self.lastPosition1.y)
else
self.position2 = CGPoint(x: $0.translation.width + self.lastPosition2.x, y: $0.translation.height + self.lastPosition2.y)
.onEnded _ in
self.lastPosition1 = self.position1
self.lastPosition2 = self.position2
return HStack
VStack
Rectangle().foregroundColor(.red)
.frame(width: 150, height: 150)
.position(self.position1)
.clipped()
.background(Color.green)
.frame(width: dragArea1.width, height: dragArea1.height)
VStack
Rectangle().foregroundColor(.blue)
.frame(width: 150, height: 150)
.position(self.position2)
.clipped()
.background(Color.yellow)
.frame(width: dragArea2.width, height: dragArea2.height)
.gesture(drag)
注意事项:
-
就像现在一样,手势可以在每个容器中的任何位置(即绿色和黄色区域)工作,即使您没有在红色或蓝色方块内拖动也是如此。
如果您将整个手势代码放入视图中并将其包装在 GeometryReader 中,这样您就可以在“.onChanged”内的上下文中引用本地边界,这可能会更加通用和/或提供更多控制。李>
【讨论】:
它工作得很好,但它很丑,因为你不能隔离每个组件内部的行为。我需要这些组件的网格,我不想在一个超级视图中管理它们。他们每个人都应该包含自己的行为。以上是关于在 SwiftUI 中,如何使手势在容器视图之外处于非活动状态?的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI手势(Gesture)进阶 : 实现任意视图的长按惯性加速行为
SwiftUI手势(Gesture)进阶 : 实现任意视图的长按惯性加速行为
如何使 SwiftUI 列表行背景颜色扩展整个宽度,包括安全区域之外