Как ограничить перетаскивание / панорамирование вида между двумя точками CGPoints (как слайдер)
У меня есть 2 независимых набора данных, один используется для рисования линии (Path
), а другой используется для размещения небольшого изображения где-нибудь на линии(Circle)
.
Вы можете представить это как слайдер (на самом деле UX должен быть примерно таким)
Я хотел добавить жест перетаскивания в представление красного круга, чтобы пользователь мог перемещать его в любом месте между конечными точками линии. Но способ, которым я реализовал или думал, что это вообще не работает или очень плох.
Известный набор данных:
Start
изLine
- CGPointEnd
изLine
- CGPointPosition
изCircle View
- CGPoint
Для простоты примера я добавил несколько простых моментов, а именно: Top
& Bottom
, но на самом деле я пытаюсь установить периметр на слое карты, получая координаты широты и долготы, преобразовывая их в пространство экранных координат и нанося Path
(линия) для этих 2 точек, а затем дает пользователю возможность пометить периметр и дает свободу перетаскивать его по линии периметра в соответствии с потребностями пользователя. (По одному ярлыку на строку, никаких сложностей).
При этом Slider не подходил.
Образец кода:
struct TestView: View {
@State var position = CGPoint(x: 60, y: 60)
let top = CGPoint(x: 50, y: 50)
let bottom = CGPoint(x: 300, y: 300)
var body: some View {
ZStack {
Path { path in
path.move(to: self.top)
path.addLine(to: self.bottom)
}
.stroke(Color.red, lineWidth: 5)
Circle()
.foregroundColor(.red)
.frame(width: 20)
.position(self.position)
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .global)
.onChanged { drag in
print(drag.location)
if
self.top.x <= drag.location.x,
self.bottom.x >= drag.location.x,
self.top.y <= drag.location.y,
self.bottom.y >= drag.location.y,
self.pointOnLine(point: drag.location)
{
self.position = drag.location
}
}
)
}
}
}
Вспомогательный метод, чтобы проверить, находится ли точка на линии:
func pointOnLine(point: CGPoint) -> Bool {
let dxc = point.x - top.x
let dyc = point.y - top.y
let dxl = bottom.x - top.x
let dyl = bottom.y - top.y
let cross = dxc * dyl - dyc * dxl
return cross == 0
}
Любая помощь приветствуется. Заранее спасибо.
2 ответа
Получил это работает
Обновлен вспомогательный метод, чтобы получить ближайшую точку пересечения местоположения перетаскивания и точки на линии.
Назначено на self.position
что удерживает View придерживаясь линии.
Жест:
DragGesture(minimumDistance: 0, coordinateSpace: .global)
.onChanged { drag in
self.position = self.pointFrom(drag.location,
toLineSegment: self.top, self.bottom)
}
Вспомогательный метод для получения балла онлайн:
private func pointFrom(_ point: CGPoint, toLineSegment start: CGPoint, _ end: CGPoint) -> CGPoint {
let pointAndStartXDiff = point.x - start.x
let pointAndStartYDiff = point.y - start.y
let startAndEndXDiff = end.x - start.x
let startAndEndYDiff = end.y - start.y
let dotProduct = pointAndStartXDiff * startAndEndXDiff + pointAndStartYDiff * startAndEndYDiff
let lengthSquare = startAndEndXDiff * startAndEndXDiff + startAndEndYDiff * startAndEndYDiff
let param = dotProduct / lengthSquare
// intersection of normal to start, end that goes through point
var xIntersection, yIntersection: CGFloat
if param < 0 || (start.x == end.x && start.y == end.y) {
xIntersection = start.x
yIntersection = start.y
} else if param > 1 {
xIntersection = end.x
yIntersection = end.y
} else {
xIntersection = start.x + param * startAndEndXDiff
yIntersection = start.y + param * startAndEndYDiff
}
return CGPoint(x: xIntersection, y: yIntersection)
}
В остальном все так же.
Результат примерно такой:
Вы можете попробовать использовать положение вдоль линии, то есть использовать угол линии.
у вас есть какое-то представление перетаскивания, чтобы получить текущую позицию:
var drag: some Gesture {
DragGesture().onChanged { value in self.pos = CGPoint(x: value.location.x, y: value.location.y)}
}
вы знаете начальную и конечную позиции линии, поэтому вы знаете угол "атан (dx,dy)". Итак, глядя из начальной позиции, вы можете получить следующую позицию для круга, что-то вроде x = dcos (угол)+x0, y=dsin(угол)+y0 ...