Как я могу настроить анимацию изменения угла в SwiftUI - PullRequest
0 голосов
/ 30 января 2020

У меня есть приложение, которое показывает группу людей, каждый из которых имеет происхождение и угол.

struct Location {
    var centre:CGPoint
    var facing:Angle
}

SwiftUI волшебным образом и автоматически выполняет большую часть анимации при перемещении из местоположения A в местоположение B

withAnimation {
    person.location = newLocation
}

Однако - для свойства «Угол» (направление) я хочу, чтобы анимация была go на кратчайшем маршруте (учитывая, что в реальном мире - обтекание углов).

например, Swift UI корректно анимирует, когда угол изменяется на 5 -> 10 (градусов)

5,6,7,8,9,10

, но при переходе от 2 до 358, он проходит длинный путь вокруг

SwiftUI делает 2,3 , 4,5,6,7 ......., 357,358

где я хотел бы это сделать

2,1,0,359,358

как я могу go об этом?

спасибо

обновление: я надеюсь на решение, которое позволит мне работать с системой анимации, возможно, с использованием новой структуры MyAngle, которая обеспечивает этапы анимации напрямую, возможно, с использованием некоторой анимации модификатор. .easeInOut изменяет шаги - есть ли эквивалентный подход, в котором я могу создать анимацию .goTheRightWay?

Ответы [ 2 ]

0 голосов
/ 01 февраля 2020

Хорошо - выкладываю свой ответ. Это работает немного как ответ @ Бена, но перемещает управление «углом тени» к эффекту вращения.

Все, что вам нужно сделать, это переключить rotationEffect(angle:Angle) для shortRotationEffect(angle:Angle,id:UUID)

это выглядит подобно

        Image(systemName: "person.fill").resizable()
            .frame(width: 50, height: 50)
            .shortRotationEffect(self.person.angle,id:person.id)
            .animation(.easeInOut)

, ShortRotationEffect использует предоставленный идентификатор для ведения словаря предыдущих углов. Когда вы устанавливаете новый угол, он вычисляет эквивалентный угол, который обеспечивает короткое вращение, и применяет его с нормальным rotationEffect(...)

Вот оно:

extension View {

    /// Like RotationEffect - but when animated, the rotation moves in the shortest direction.
    /// - Parameters:
    ///   - angle: new angle
    ///   - anchor: anchor point
    ///   - id: unique id for the item being displayed. This is used as a key to maintain the rotation history and figure out the right direction to move
    func shortRotationEffect(_ angle: Angle,anchor: UnitPoint = .center, id:UUID) -> some View {
        modifier(ShortRotation(angle: angle,anchor:anchor, id:id))
    }
}


struct ShortRotation: ViewModifier {
    static var storage:[UUID:Angle] = [:]

    var angle:Angle
    var anchor:UnitPoint
    var id:UUID

    func getAngle() -> Angle {
        var newAngle = angle

        if let lastAngle = ShortRotation.storage[id] {
            let lastDegrees = lastAngle.degrees.truncatingRemainder(dividingBy: 360)
            let newDegrees = angle.degrees.truncatingRemainder(dividingBy: 360)

            let change = newDegrees - lastDegrees
            if change < 180 {
                newAngle = lastAngle + Angle.init(degrees: change)
            }
            else {
                newAngle = lastAngle + Angle.init(degrees: change - 360)
            }
        }

        ShortRotation.storage[id] = newAngle

        return newAngle
    }


    func body(content: Content) -> some View {
        content
            .rotationEffect(getAngle(),anchor: anchor)
    }
}
0 голосов
/ 30 января 2020

Как насчет корректировки значения newLocation, чтобы оно находилось в пределах 180˚ от начала? Вот функция, чтобы проверить, больше ли анимированное расстояние, чем половина, и предоставить новую конечную точку, которая удовлетворяет этому.

func adjustedEnd(from start: CGFloat, to target: CGFloat) -> CGFloat {

    // Shift end to be greater than start
    var end = target
    while end < start { end += 360 }

    // Mod the distance with 360, shifting by 180 to keep on the same side of a circle
    return (end - start + 180).truncatingRemainder(dividingBy: 360) - 180 + start
}

Некоторые примеры тестов:

let startValues: [CGFloat] = [2, -10, 345, 365, 700]
let endValues: [CGFloat] = [2, 10, 180, 185, 350, -10, 715, -700]
for start in startValues {
    print("From \(start):")
    for end in endValues {
        let adjusted = adjustedEnd(from: start, to: end)
        print("\t\(end) \tbecomes \(adjusted);\tdistance \(abs(adjusted - start))")
    }
}

выводит следующее :

From 2.0:
    2.0     becomes 2.0;    distance 0.0
    10.0    becomes 10.0;   distance 8.0
    180.0   becomes 180.0;  distance 178.0
    185.0   becomes -175.0; distance 177.0
    350.0   becomes -10.0;  distance 12.0
    -10.0   becomes -10.0;  distance 12.0
    715.0   becomes -5.0;   distance 7.0
    -700.0  becomes 20.0;   distance 18.0
From -10.0:
    2.0     becomes 2.0;    distance 12.0
    10.0    becomes 10.0;   distance 20.0
    180.0   becomes -180.0; distance 170.0
    185.0   becomes -175.0; distance 165.0
    350.0   becomes -10.0;  distance 0.0
    -10.0   becomes -10.0;  distance 0.0
    715.0   becomes -5.0;   distance 5.0
    -700.0  becomes 20.0;   distance 30.0
From 345.0:
    2.0     becomes 362.0;  distance 17.0
    10.0    becomes 370.0;  distance 25.0
    180.0   becomes 180.0;  distance 165.0
    185.0   becomes 185.0;  distance 160.0
    350.0   becomes 350.0;  distance 5.0
    -10.0   becomes 350.0;  distance 5.0
    715.0   becomes 355.0;  distance 10.0
    -700.0  becomes 380.0;  distance 35.0
From 365.0:
    2.0     becomes 362.0;  distance 3.0
    10.0    becomes 370.0;  distance 5.0
    180.0   becomes 540.0;  distance 175.0
    185.0   becomes 185.0;  distance 180.0
    350.0   becomes 350.0;  distance 15.0
    -10.0   becomes 350.0;  distance 15.0
    715.0   becomes 355.0;  distance 10.0
    -700.0  becomes 380.0;  distance 15.0
From 700.0:
    2.0     becomes 722.0;  distance 22.0
    10.0    becomes 730.0;  distance 30.0
    180.0   becomes 540.0;  distance 160.0
    185.0   becomes 545.0;  distance 155.0
    350.0   becomes 710.0;  distance 10.0
    -10.0   becomes 710.0;  distance 10.0
    715.0   becomes 715.0;  distance 15.0
    -700.0  becomes 740.0;  distance 40.0

(отредактировано для учета отрицательных конечных значений)

Редактировать: Из вашего комментария о сохранении второго значения, как насчет установки Location.facing в скорректированный угол, а затем добавление в местоположение что-то вроде

var prettyFacing: Angle {
    var facing = self.facing
    while facing.degrees < 0 { facing += Angle(degrees: 360) }
    while facing.degrees > 360 { facing -= Angle(degrees: 360) }
    return facing
}
...