如何在SwiftUI中自定义角度变化的动画

时间:2020-01-30 15:01:26

标签: swiftui swiftui-animation

我有一个应用程序,可以显示一群人,每个人都有其原点和角度。

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

SwiftUI神奇地自动将许多动画从位置A移到位置B

withAnimation {
    person.location = newLocation
}

但是-对于“角度(朝向)”属性,我希望动画以最短的路线进行(请记住,在现实世界中,角度会环绕)。

例如当角度改变5-> 10(度)

时,Swift UI会正确设置动画
5,6,7,8,9,10

但是从2到358,需要很长的路要走

SwiftUI执行2,3,4,5,6,7 .......,357,358

我想在哪里做

2,1,0,359,358

我该怎么办?

谢谢

更新:我希望有一个解决方案,允许我使用动画系统,也许使用新的MyAngle结构直接提供动画步骤,或者使用某种动画修改器。 .easeInOut修改了步骤-是否有等效的方法可以创建.goTheRightWay动画?

3 个答案:

答案 0 :(得分:0)

如何将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
}

答案 1 :(得分:0)

好-发布我自己的答案。 它的工作方式类似于@Ben的答案-但将“阴影角度”管理移至旋转效果。

您要做的就是将rotationEffect(angle:Angle)的{​​{1}}切换为

看起来像

shortRotationEffect(angle:Angle,id:UUID)

ShortRotationEffect使用提供的ID维护先前角度的字典。当您设置一个新角度时,它将计算出一个等效的角度,该角度可提供较短的旋转并将其应用于法线 Image(systemName: "person.fill").resizable() .frame(width: 50, height: 50) .shortRotationEffect(self.person.angle,id:person.id) .animation(.easeInOut)

这里是:

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 change:Double = (newAngle.degrees - lastAngle.degrees) %% 360.double
          
        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)
    }
}

答案 2 :(得分:0)

在尝试了其他两个选项之后,我们仍然遇到了视觉故障(不常见,但仍然存在!)。

我们的解决方案:将UIKit用于动画

我们创建了一个SPM软件包,其中添加了一个简单的修饰符.uiRotationEffect()。此修饰符将View包裹在UIView中,并使用UIView的{​​{1}}函数来获取正确的行为。

您可以安装软件包here,也可以复制并粘贴代码here,时间不长。

工作解决方案的GIF:

enter image description here