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!


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

查看所有标签

猜你喜欢:

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

敏捷软件开发

敏捷软件开发

马丁 / 邓辉、孙鸣 / 人民邮电出版社 / 2008-01-01 / 69.00元

《敏捷软件开发:原则模式和实践(C#版)》不仅是一部深入浅出、生动易懂的面向对象原则与设计模式著作。而且还是一部通俗的敏捷方法导引书和快速实用的LJML教程。通过《敏捷软件开发:原则模式和实践(C#版)》你会发现,许多以前看起来非常枯燥费解的概念,忽然间都豁然开朗。变得鲜活生动起来。 C#版与此前的Java版相比,主要的更新包括加强了UML的介绍章节。使其更加贴近实战;增加了对MVP模式的介......一起来看看 《敏捷软件开发》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具