Mar*_*ner 5 drag-and-drop coordinates swift swiftui geometryreader
我正在尝试使用 SwiftUI 在我的应用程序中实现拖放功能。
我创建了两个圆圈,它们位于两个不同的HStacks. 它们不共享相同的坐标空间。
带有描边的圆圈是目标,绿色实心圆圈是要拖动的对象。
我能够使用GeometryReaderinside an来获得他们的绝对位置.overlay。我用它来检测当对象圆被拖动到目标圆上时它们是否重叠。这有效。
当它们不重叠时,对象圆将移回其原始位置。当它们重叠时,对象圆应该卡入目标圆的位置。这是我似乎有问题的地方。
我试图通过以下方式设置对象圆的新 X 和 Y 位置:对象圆本地位置 - 对象圆全局位置 + 目标圆全局位置。
objectPosition = CGPoint(
x: objectPosition.x - objectFrame.midX + targetFrame.midX,
y: objectPosition.y - objectFrame.midY + targetFrame.midY
)
Run Code Online (Sandbox Code Playgroud)
我假设这会将我带到目标圆的坐标空间。但不知怎的,它不起作用。
到目前为止,我一直无法找到一种可靠且简单的方法来转换 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()
}
}
Run Code Online (Sandbox Code Playgroud)
经过几次尝试后,我找到了一个有效的解决方案,无论创建了多少视图。使用 aGeometryReader作为每个对象的父级Circle,我得到了要拖动的对象的位置.global和位置。.local我从其本地位置中减去全局位置,然后添加目标的全局位置。这给了我对象在其局部坐标空间中的新绝对位置,即其目的地。我的代码还实现了拖放功能,ViewModifier以Circle方便和将来使用。我正在使用 的两个底层CGRects来Circles测试相交。值得注意的是,圆的初始位置也是使用GeometryReader
如果这可以简化,请发表您的评论或答案。
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 targetGlobalPosition: 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() {
targetGlobalPosition = 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
newObjectPosition = CGPoint(
x: objectPosition.x - objectGlobalPosition.x + targetGlobalPosition.x,
y: objectPosition.y - objectGlobalPosition.y + targetGlobalPosition.y
)
}
.onEnded { drag in
isDragging = false
let targetFrame = CGRect(origin: targetGlobalPosition, 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 rectangles, not the circles. See above insetBy adjustment to get a good middle ground.
atTarget = true
} else {
atTarget = false
}
}
)
}
}
}
}
}
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()
}
}
Run Code Online (Sandbox Code Playgroud)
基于上述解决方案,我创建了一个使用内部.offset来对齐视图的修改器。也许有人觉得这很有帮助:
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public extension View {
/// Positions the center of this view at the specified point in the specified
/// coordinate space using offset.
///
/// Use the `openRelativeOffset(_ position:in:)` modifier to place the center of a view at a
/// specific coordinate in the specified coordinate space using a
/// CGPoint to specify the `x`
/// and `y` position of the target CoordinateSpace defined by the Enum `coordinateSpace`
/// This is not changing the position of the view by internally using an offset, other views using auto layout should not be affected.
///
/// Text("Position by passing a CGPoint() and CoordinateSpace")
/// .openRelativeOffset(CGPoint(x: 175, y: 100), in: .global)
/// .border(Color.gray)
///
/// - Parameters
/// - position: The point in the target CoordinateSpace at which to place the center of this. Uses auto layout if nil.
/// view.
/// - in coordinateSpace: The target CoordinateSpace at which to place the center of this view.
///
/// - Returns: A view that fixes the center of this view at `position` in `coordinateSpace` .
func openRelativeOffset(_ position: CGPoint?, in coordinateSpace: CoordinateSpace) -> some View {
modifier(OpenRelativeOffset(position: position, coordinateSpace: coordinateSpace))
}
}
private struct OpenRelativeOffset: ViewModifier {
var position: CGPoint?
@State private var newPosition: CGPoint = .zero
@State private var newOffset: CGSize = .zero
let coordinateSpace: CoordinateSpace
@State var localPosition: CGPoint = .zero
@State var targetPosition: CGPoint = .zero
func body(content: Content) -> some View {
if let position = position {
return AnyView(
content
.offset(newOffset)
.background(
GeometryReader { geometry in
Color.clear
.onAppear {
let localFrame = geometry.frame(in: .local)
let otherFrame = geometry.frame(in: coordinateSpace)
localPosition = CGPoint(x: localFrame.midX, y: localFrame.midY)
targetPosition = CGPoint(x: otherFrame.midX, y: otherFrame.midY)
newPosition.x = localPosition.x - targetPosition.x + position.x
newPosition.y = localPosition.y - targetPosition.y + position.y
newOffset = CGSize(width: newPosition.x - abs(localPosition.x), height: newPosition.y - abs(localPosition.y))
}
}
)
)
} else {
return AnyView(
content
)
}
}
}
Run Code Online (Sandbox Code Playgroud)
该存储库中还提供了源代码以及内部使用的另一个版本.position。
https://github.com/marcoboerner/OpenSwiftUIViews
| 归档时间: |
|
| 查看次数: |
2061 次 |
| 最近记录: |