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

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

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

enter image description here

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

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

  • Start из Line - CGPoint

  • End из Line - CGPoint

  • Position из Circle View - CGPoint

Для простоты примера я добавил несколько простых моментов, а именно Top & Bottom, но на самом деле то, чего я пытаюсь достичь, это установил sh периметр на слое карты, выбирая координаты широты и долготы, преобразуя его в пространство экранных координат и нанося Path (линию) для этих двух точек, а затем предоставляя возможность пользователю маркировать периметр и предоставляя свободу перетаскивать его вдоль линии периметра в соответствии с потребностями пользователя. (Один ярлык на строку, без осложнений).

При этом Slider не был вариантом.

Что выглядит примерно так:
enter image description here

Пример кода:

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 ]

0 голосов
/ 13 апреля 2020

Работает

Обновлен вспомогательный метод для получения ближайшей точки пересечения места перетаскивания и точки на линии.

Присваивается self.position, при котором вид остается на линии.

Жест:

DragGesture(minimumDistance: 0, coordinateSpace: .global)
                        .onChanged { drag in
                            self.position = self.pointFrom(drag.location,
                                                           toLineSegment: self.top, self.bottom)
                        }

Вспомогательный метод для получения Point on Line:

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)
    }

Остальное все одинаково.

Результат примерно такой :

enter image description here

0 голосов
/ 12 апреля 2020

Вы можете попробовать использовать положение вдоль линии, то есть угол наклона линии.

у вас есть вид перетаскивания, чтобы получить текущую позицию:

var drag: some Gesture {
    DragGesture().onChanged { value in self.pos = CGPoint(x: value.location.x, y: value.location.y)}
}

Вы знаете начальное и конечное положение линии, поэтому знаете угол «atan (dx, dy)». Так что, начиная со стартовой позиции, вы можете получить следующую позицию для круга, что-то вроде x = d cos (угол) + x0, y = d sin (угол) + y0 ...

...