The magic of Animatable values in SwiftUI

栏目: IT技术 · 发布时间: 4年前

内容简介:Let’s start with the basics. Usually, we attach the animation modifier to a view and change some state variables.To learn more about basics of animation in

WWDC20 is already around the corner, and we are waiting for massive changes and additions to the SwiftUI framework. It is a perfect week to wrap up the season with a post about one of the strongest sides of the SwiftUI framework, which is animation . Today we will learn how to build complex animations by using VectorArithmetic protocol.

Basics

Let’s start with the basics. Usually, we attach the animation modifier to a view and change some state variables. SwiftUI docs say that animation modifier applies the given animation to all animatable values within this view. Here is a small example that animates the button on every tap.

struct RootView: View {
    @State private var scale: CGFloat = 1

    var body: some View {
        Button("Press me") {
            self.scale += 1
        }
        .padding()
        .foregroundColor(.white)
        .background(Color.blue)
        .cornerRadius(8)
        .scaleEffect(scale)
        .animation(.default)
    }
}

To learn more about basics of animation in SwiftUI , take a look at my “Animations in SwiftUI” post .

But how SwiftUI understand which values are animatable ? SwiftUI introduces a protocol called Animatable . This protocol has the only requirement, animatableData property, that describes the changes during an animation. So during the state changes, SwiftUI traverses the view hierarchy and finds all the values that conform to Animatable protocol and animate its changes by understanding animatableData of a particular item. Let’s take a look at another example.

struct Line1: Shape {
    let coordinate: CGFloat

    func path(in rect: CGRect) -> Path {
        Path { path in
            path.move(to: .zero)
            path.addLine(to: CGPoint(x: coordinate, y: coordinate))
        }
    }
}

struct RootView: View {
    @State private var coordinate: CGFloat = 0

    var body: some View {
        Line1(coordinate: coordinate)
            .stroke(Color.red)
            .animation(Animation.linear(duration: 1).repeatForever())
            .onAppear { self.coordinate = 100 }
    }
}

Here we have a Line struct that conforms to the Shape protocol. All shapes in SwiftUI conform to Animatable protocol, but as you can see, there is no animation while running the example. SwiftUI doesn’t animate our line because the framework doesn’t know how to animate it.

To learn more about Shape API in SwiftUI , take a look at my “Building BarChart with Shape API in SwiftUI” post .

By default, shape returns the instance of EmptyAnimatableData struct as its animatableData . SwiftUI allows us to use EmptyAnimatableData whenever we don’t know how to animate the value. Let’s solve the problem by replacing EmptyAnimatableData with some value.

struct Line1: Shape {
    var coordinate: CGFloat

    var animatableData: CGFloat {
        get { coordinate }
        set { coordinate = newValue }
    }

    func path(in rect: CGRect) -> Path {
        Path { path in
            path.move(to: .zero)
            path.addLine(to: CGPoint(x: coordinate, y: coordinate))
        }
    }
}

In the example above, we made our Line struct animatable by implementing animatableData property. But how we can animate multiple properties of the same shape? There is another type called AnimatablePair that helps us to animate the paired values. Let’s make our Line struct animatable in both vertical and horizontal directions.

struct Line2: Shape {
    var x: CGFloat
    var y: CGFloat

    var animatableData: AnimatablePair<CGFloat, CGFloat> {
        get { AnimatablePair(x, y) }
        set {
            x = newValue.first
            y = newValue.second
        }
    }

    func path(in rect: CGRect) -> Path {
        Path { path in
            path.move(to: .zero)
            path.addLine(to: CGPoint(x: x, y: y))
        }
    }
}

OK, nice. Now we can animate two values of the same shape. But what about the line chart? Assume that you are working on the charting library, and you want to build an animatable line chart? There might be hundreds of values that we want to animate. How could we deal with this challenge?

Introducing VectorArithmetic protocol

As you can see, we have already used CGFloat and AnimatablePair types as animatable data. But it doesn’t mean that you can use any type here. Animatable protocol has a constraint on animatableData property. Any type that conforms to VectorArithmetic protocol can be used as animtableData . SwiftUI provides us a few types that conform to VectorArithmetic protocol out of the box. For example, Float , Double , CGFloat , and AnimatablePair .

Let’s back to our line chart idea. I want to make a line chart shape that animates all the values. There are probably could be hundreds of points, and it looks like an excellent candidate for a custom type that conforms to VectorArithmetic protocol. VectorArithmetic has a few requirements like scaling, adding, subtracting, etc. You should describe how SwiftUI must handle these operations on your type. Here is a drop-in implementation for an array of values.

import SwiftUI
import enum Accelerate.vDSP

struct AnimatableVector: VectorArithmetic {
    static var zero = AnimatableVector(values: [0.0])

    static func + (lhs: AnimatableVector, rhs: AnimatableVector) -> AnimatableVector {
        let count = min(lhs.values.count, rhs.values.count)
        return AnimatableVector(values: vDSP.add(lhs.values[0..<count], rhs.values[0..<count]))
    }

    static func += (lhs: inout AnimatableVector, rhs: AnimatableVector) {
        let count = min(lhs.values.count, rhs.values.count)
        vDSP.add(lhs.values[0..<count], rhs.values[0..<count], result: &lhs.values[0..<count])
    }

    static func - (lhs: AnimatableVector, rhs: AnimatableVector) -> AnimatableVector {
        let count = min(lhs.values.count, rhs.values.count)
        return AnimatableVector(values: vDSP.subtract(lhs.values[0..<count], rhs.values[0..<count]))
    }

    static func -= (lhs: inout AnimatableVector, rhs: AnimatableVector) {
        let count = min(lhs.values.count, rhs.values.count)
        vDSP.subtract(lhs.values[0..<count], rhs.values[0..<count], result: &lhs.values[0..<count])
    }

    var values: [Double]

    mutating func scale(by rhs: Double) {
        values = vDSP.multiply(rhs, values)
    }

    var magnitudeSquared: Double {
        vDSP.sum(vDSP.multiply(values, values))
    }
}

As you can see, I use the Accelerate framework that provides us high-performance vector-based arithmetic. By using AnimatableVector struct, we can animate as many values as we want, and it will work super fast because it uses the Accelerate framework. Now we have all we need to implement an animatable line chart shape.

import SwiftUI

struct LineChart: Shape {
    var vector: AnimatableVector

    var animatableData: AnimatableVector {
        get { vector }
        set { vector = newValue }
    }

    func path(in rect: CGRect) -> Path {
        Path { path in
            let xStep = rect.width / CGFloat(vector.values.count)
            var currentX: CGFloat = xStep
            path.move(to: .zero)

            vector.values.forEach {
                path.addLine(to: CGPoint(x: currentX, y: CGFloat($0)))
                currentX += xStep
            }
        }
    }
}

You can download the source code of AnimatableVector through Github gist .

struct RootView: View {
    @State private var vector: AnimatableVector = .zero

    var body: some View {
        LineChart(vector: vector)
            .stroke(Color.red)
            .animation(Animation.default.repeatForever())
            .onAppear { 
                self.vector = AnimatableVector(values: [50, 100, 75, 100]) 
            }
    }
}

The magic of Animatable values in SwiftUI

Conclusion

I hope you enjoy the post. Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading, and see you next week!


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

编码的奥秘

编码的奥秘

Charles Petzold / 伍卫国、王宣政、孙燕妮 / 机械工业出版社 / 2000-9-1 / 24.00

渴望交流是大多数人的天性。在本书中,“编码”通常指一种在人和机器之间进行信息转换的系统。换句话说、编码即是交流。有时我们将编码看得很神秘,其实大多数编码并非都是这样。大多数的编码都需要被很好地理解,因为它们是人类交流的基础。――《编码的奥秘》 手电筒、英国人入侵、黑色的猫和跷跷板与计算机有什么必然联系?本书向我们展示了使用语言的一些直观方法并创造新的方法来进行相互之间的交流。此书使我们明白了......一起来看看 《编码的奥秘》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器