Как ограничить перетаскивание / панорамирование вида между двумя точками CGPoints (как слайдер)

У меня есть 2 независимых набора данных, один используется для рисования линии (Path), а другой используется для размещения небольшого изображения где-нибудь на линии(Circle).

Вы можете представить это как слайдер (на самом деле UX должен быть примерно таким)

Я хотел добавить жест перетаскивания в представление красного круга, чтобы пользователь мог перемещать его в любом месте между конечными точками линии. Но способ, которым я реализовал или думал, что это вообще не работает или очень плох.

Известный набор данных:

  • Start из Line - CGPoint

  • End из Line - CGPoint

  • Position из 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 ...

Другие вопросы по тегам