【问题标题】:In SwiftUI, how can you make a gesture inactive outside a container view?在 SwiftUI 中,如何使手势在容器视图之外处于非活动状态?
【发布时间】: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)
        }
        
    }
}

关于如何在任何容器外保持禁用拖动的任何想法,如已经实现的那样,但也允许在每个容器的完整范围内拖动,而不管其他容器发生什么?

【问题讨论】:

    标签: ios swiftui gesture clipping


    【解决方案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)
    } }
    

    【讨论】:

    • 你的答案不清楚,重新格式化你的帖子,你的代码中使用了很多不必要的空格和未闭合的括号
    • 请重新格式化您的代码以获得完整的工作版本。您可以阅读guidelineshow 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)
        }
    }
    

    注意事项:

    1. 就像现在一样,手势可以在每个容器中的任何位置(即绿色和黄色区域)工作,即使您没有在红色或蓝色方块内拖动也是如此。
    2. 如果您将整个手势代码放入视图中并将其包装在 GeometryReader 中,这样您就可以在“.onChanged”内的上下文中引用本地边界,这可能会更加通用和/或提供更多控制。李>

    【讨论】:

    • 它工作得很好,但它很丑,因为你不能隔离每个组件内部的行为。我需要这些组件的网格,我不想在一个超级视图中管理它们。他们每个人都应该包含自己的行为。
    猜你喜欢
    • 1970-01-01
    • 2018-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-19
    • 1970-01-01
    • 1970-01-01
    • 2011-03-06
    相关资源
    最近更新 更多