1

我正在尝试使用 SwiftUI 在我的应用程序中实现拖放功能。

我创建了两个圆圈,它们位于两个不同的HStacks. 它们不共享相同的坐标空间。

带有笔划的圆圈是目标,绿色实心圆圈是要拖动的对象。

我能够使用一个GeometryReaderinside来获得他们的绝对位置.overlay。我用它来检测当对象圆被拖动到目标圆上时它们是否重叠。这行得通。

当它们不重叠时,对象圆将移回其原始位置。当它们确实重叠时,对象圆应该在目标圆的位置卡入到位。这是我似乎有问题的地方。

我正在尝试通过以下方式设置对象圆的新 X 和 Y 位置:对象圆局部位置 - 对象圆全局位置 + 目标圆全局位置。

    objectPosition = CGPoint(
        x: objectPosition.x - objectFrame.midX + targetFrame.midX,
        y: objectPosition.y - objectFrame.midY + targetFrame.midY
   )

我假设这会将我带到目标圆的坐标空间。但不知何故,它不起作用。

到目前为止,我一直无法找到一种可靠且简单的方法来在 SwiftUI 中转换坐标空间。使用覆盖内部的解决方法GeometryReader至少可以为我提供正确的全局位置。但是我还没有找到一种方法来使用这些位置将视图也放置在.global坐标空间中。

如果有人知道为什么我对坐标空间的计算是错误的,或者甚至知道一种更容易地相互转换和定位视图的方法,我将不胜感激。

这是我的 SwiftUI 单视图 iOS 应用程序代码:

import SwiftUI

struct ContentView: View {
    
    @State private var isDragging: Bool = false
    
    @State private var objectDragOffset: CGSize = .zero
    @State private var objectPosition: CGPoint = .zero
    
    @State private var objectFrame: CGRect = .zero
    @State private var targetFrame: CGRect = .zero
    
    var body: some View {
        VStack {
            HStack {
                Circle()
                    .stroke(lineWidth: 3)
                    .fill(Color.blue)
                    .frame(width: 100.0, height: 100.0)
                    .overlay(
                        GeometryReader { targetGeometry in
                            Color(.clear)
                                .onAppear { targetFrame = targetGeometry.frame(in: .global) }
                        }
                    )
                    .position(CGPoint(x:180, y: 190))
            }.background(Color.yellow)
            HStack {
                Circle()
                    .foregroundColor(.green)
                    .frame(width: 100, height: 100, alignment: .center)
                    .overlay(
                        GeometryReader { objectGeometry in
                            Color(.clear)
                                .onAppear {
                                    objectFrame = objectGeometry.frame(in: .global) }
                        }
                    )
                    .position(objectPosition)
                    .offset( isDragging ? objectDragOffset : .zero)
                    .onAppear { objectPosition = CGPoint(x: 200, y: 250 ) }
                    .gesture(
                        DragGesture(coordinateSpace: .global)
                            .onChanged { drag in
                                isDragging = true
                                objectDragOffset = drag.translation
                            }
                            .onEnded { drag in
                                isDragging = false
                                
                                if targetFrame.contains(drag.location) {
                                    objectPosition = CGPoint(
                                        x: objectPosition.x - objectFrame.midX + targetFrame.midX,
                                        y: objectPosition.y - objectFrame.midY + targetFrame.midY
                                    )
                                } else {
                                    objectPosition = CGPoint(x: 200, y: 250 )
                                }
                            }
                    )
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
4

1 回答 1

2

After a few more trying around I found a solution that works, also no matter how many views are created. Using a GeometryReader as the parent of each Circle I'm getting the .global and .local position of the object I want to drag. I subtract the global position from its local position and to this I add the global position of the target. That gives me the new absolute position of the object, its destination, in its local coordinate space. My code also has the drag and drop implemented as well as a ViewModifier for the Circle for convenience and future use. I'm using the two underlying CGRects of the Circles to test for intersection. It's important to note that the initial positions of the circle are also set using the GeometryReader

If this can be simplified please post your comment or answer.

import SwiftUI

struct ContentView: View {
    
    @State private var isDragging: Bool = false
    @State private var atTarget: Bool = false
    
    @State private var objectDragOffset: CGSize = .zero
    @State private var objectPosition: CGPoint = .zero
    @State private var objectGlobalPosition: CGPoint = .zero
    
    @State private var targetGlobalPossition: CGPoint = .zero
    
    @State private var newObjectPosition: CGPoint = .zero
    
    var body: some View {
        
        VStack {
            HStack {
                GeometryReader { geometry in
                    Circle()
                        .stroke(lineWidth: 3)
                        .fill(Color.blue)
                        .modifier(CircleModifier())
                        .position(CGPoint(x:geometry.frame(in: .local).midX, y: geometry.frame(in: .local).midY))
                        .onAppear() {
                            targetGlobalPossition = CGPoint(x:geometry.frame(in: .global).midX, y: geometry.frame(in: .global).midY)
                        }
                }
            }.background(Color.yellow)
            
            HStack {
                GeometryReader { geometry in
                    Circle()
                        .foregroundColor(.red)
                        .position(atTarget ? newObjectPosition : CGPoint(x:geometry.frame(in: .local).midX, y: geometry.frame(in: .local).midY))
                        .modifier(CircleModifier())
                        .onAppear() {
                            objectPosition = CGPoint(x:geometry.frame(in: .local).midX, y: geometry.frame(in: .local).midY)
                            objectGlobalPosition = CGPoint(x:geometry.frame(in: .global).midX, y: geometry.frame(in: .global).midY)
                        }
                        .offset( isDragging ? objectDragOffset : .zero)
                        .gesture(
                            DragGesture(coordinateSpace: .global)
                                .onChanged { drag in
                                    isDragging = true
                                    objectDragOffset = drag.translation
                                }
                                .onEnded { drag in
                                    isDragging = false
                                    
                                    let targetFrame = CGRect(origin: targetGlobalPossition, size: CircleModifier.frame)
                                    let objectFrame = CGRect(origin: objectGlobalPosition, size: CircleModifier.frame)
                                        .offsetBy(dx: drag.translation.width, dy: drag.translation.height)
                                        .insetBy(dx: CircleModifier.frame.width * 0.1, dy: CircleModifier.frame.height * 0.1)
                                    
                                    if targetFrame.intersects(objectFrame) {
                                        // Will check for the intersection of the rectangeles, not the circles. See above insetBy adjustment to get a good middle ground.
                                        atTarget = true
                                    } else {
                                        atTarget = false
                                    }
                                }
                        )
                }
            }
        }.onAppear {
            newObjectPosition = CGPoint(
                x: objectPosition.x - objectGlobalPosition.x + targetGlobalPossition.x,
                y: objectPosition.y - objectGlobalPosition.y + targetGlobalPossition.y
            )
        }
    }
}

struct CircleModifier: ViewModifier {
    static let frame = CGSize(width: 100.0, height: 100.0)
    
    func body(content: Content) -> some View {
        content
            .frame(width: CircleModifier.frame.width, height: CircleModifier.frame.height, alignment: .center)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
于 2021-01-05T17:59:03.827 回答