I have a SwiftUI app that allows to drag pins on a map.
A pin is first long pressed. On ended, the pin is moved upwards so that its base can be seen. It can then be dragged.
I want to write a UI test that checks correct operation.
However my test fails since the screen points obtained are wrong.
I thus wrote a minimal project that demonstrates the issue.
Here is the ContentView
:
import SwiftUI
struct ContentView: View {
@GestureState private var dragAmount = CGSize.zero
@State private var pinOffset = CGSize.zero
var body: some View {
let longPressGesture = LongPressGesture(minimumDuration: 0.2)
.onEnded { _ in
withAnimation(.easeInOut(duration: 0.3)) {
pinOffset = CGSize(width: 0, height: -60)
}
}
let dragGesture = DragGesture(coordinateSpace: .global)
.updating($dragAmount) { dragGestureValue, dragAmount, transaction in
dragAmount = dragGestureValue.translation
}
.onEnded { dragGestureValue in
pinOffset = CGSize(width: dragGestureValue.translation.width,
height: dragGestureValue.translation.height - 60)
}
let combinedGesture = longPressGesture.sequenced(before: dragGesture)
Image(systemName: "pin")
.offset(CGSize(width: dragAmount.width + pinOffset.width,
height: dragAmount.height + pinOffset.height))
.simultaneousGesture(combinedGesture)
}
}
And here is my UI test:
func test_dragging() throws {
let app = XCUIApplication()
app.launch()
// setup
let dragAmountX: CGFloat = 100
let dragAmountY: CGFloat = 100
let allowedError: CGFloat = 2
var actualError: CGFloat
let pinDraggingLiftOff: CGFloat = 60
let pinLongPressDuration = 1.0
// Long press the pin. On ended, the pin is lifted off. Then drag it to another location.
// given
let expectedDistance = sqrt(dragAmountX * dragAmountX + (dragAmountY - pinDraggingLiftOff) * (dragAmountY - pinDraggingLiftOff))
let pinBeforeDrag = app.images.firstMatch
let pinBaseBeforeDrag = pinBeforeDrag.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.99)).screenPoint
let pinCenterBeforeDrag = pinBeforeDrag.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
let pinCenterAfterDrag = pinCenterBeforeDrag.withOffset(CGVector(dx: dragAmountX, dy: dragAmountY))
// when
pinCenterBeforeDrag.press(forDuration: pinLongPressDuration, thenDragTo: pinCenterAfterDrag)
// then
let pinAfterDrag = app.images.firstMatch
let pinBaseAfterDrag = pinAfterDrag.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.99)).screenPoint
let actualDistance = sqrt(pow(pinBaseBeforeDrag.x - pinBaseAfterDrag.x, 2) + pow(pinBaseBeforeDrag.y - pinBaseAfterDrag.y, 2))
actualError = abs(actualDistance - expectedDistance)
XCTAssert(actualError < allowedError, "Found distance \(actualDistance); expected: \(expectedDistance)")
}
A few values obtained:
dragAmountX dragAmountY expectedDistance actualDistance
0 0 60 60
0 100 40 33
100 0 116.6 110.7
100 100 107.7 101.7
I do not understand why all distances are wrong as soon as the pin is dragged.
PS: I have read this question, but it is unrelated.