SwiftUI 新特性:Animatable 宏的使用与原理解析

大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
- 前言
- 先从一个简单的例子说起
- 传统的做法:使用 Animatable 协议
- 全新方案:使用 Animatable 宏
- 完整可运行示例
- 忽略某些属性:使用 AnimatableIgnored
- 内部原理:宏背后做了什么?
- 应用场景分析
- 总结
前言
在 SwiftUI 中,实现平滑、自然的动画一直是它的强项。
不过在 WWDC 最新的更新里,Apple 又把动画的实现难度进一步降低——引入了一个全新的宏:@Animatable。
这篇文章我们就来聊聊它的使用方式、背后的逻辑,以及在开发中可以带来哪些实实在在的便利。
先从一个简单的例子说起
假设我们想要在界面上显示一个整数,并在点击按钮后从 0 动画增加到 100。
我们可能会先写出这样的代码:
struct IntegerView: View {var number: Intvar body: some View {Text(number.formatted())}
}struct ContentView: View {@State private var number = 0var body: some View {VStack {IntegerView(number: number).animation(.default.speed(0.5), value: number)Button("Animate") {number = 100}}}
}
这段代码看上去没问题,但运行后你会发现一个问题:
当我们点击“Animate”按钮时,数值直接从 0 跳到了 100,并没有平滑过渡。
这是因为 SwiftUI 并不知道该如何“逐步变化”一个整数。
它只知道两个状态:“0” 和 “100”,于是只能简单地替换文字。
传统的做法:使用 Animatable 协议
早期我们要解决这个问题,需要让自定义的视图(比如 IntegerView)遵守 Animatable 协议。
这个协议会告诉 SwiftUI:哪些属性是可以被插值(interpolated)的,也就是可以被动画过渡的。
例如,我们可以这样写:
struct IntegerView: View, Animatable {var number: Doublevar animatableData: Double {get { number }set { number = newValue }}var body: some View {Text(number.formatted(.number.precision(.fractionLength(0))))}
}
这样,SwiftUI 就知道如何让 number 从 0 平滑地过渡到 100。
虽然能用,但这种写法比较“手动”,尤其当视图属性多的时候,需要逐个实现 animatableData。
全新方案:使用 Animatable 宏
在新的 SwiftUI 版本中,Apple 提供了更轻量的写法。
我们只需要用一个宏 @Animatable,就能让视图自动获得动画能力。
@Animatable
struct IntegerView: View {var number: Floatvar body: some View {Text(number.formatted(.number.precision(.fractionLength(0))))}
}
可以看到,我们做了两点调整:
- 在结构体上添加了
@Animatable; - 把
number的类型从Int改成了Float。
这是因为 @Animatable 只适用于遵循 VectorArithmetic 协议 的类型,而 Float 就是其中之一。
完整可运行示例
现在我们来看看完整的可运行代码:
import SwiftUI@Animatable
struct IntegerView: View {var number: Floatvar body: some View {Text(number.formatted(.number.precision(.fractionLength(0)))).font(.system(size: 48, weight: .bold)).foregroundColor(.blue).padding()}
}struct ContentView: View {@State private var number: Float = 0var body: some View {VStack(spacing: 40) {IntegerView(number: number).animation(.easeInOut(duration: 2), value: number)Button("Animate") {number = 100}.font(.title2).buttonStyle(.borderedProminent)}.padding()}
}
运行后你会发现,当点击按钮时,数字会从 0 平滑过渡到 100。
整个动画自然流畅,而且代码量比原来少了很多。
SwiftUI 在内部帮我们自动完成了 Animatable 协议的实现逻辑。
忽略某些属性:使用 AnimatableIgnored
有时候我们可能不希望让某个属性参与动画,比如一些常量配置或调试参数。
这个时候就可以使用 @AnimatableIgnored 宏。
@Animatable
struct IntegerView: View {var number: Float@AnimatableIgnoredvar ignoredValue: Float = 10.0var body: some View {Text(number.formatted(.number.precision(.fractionLength(0)))).font(.system(size: 40)).foregroundColor(.purple)}
}
加上 @AnimatableIgnored 后,ignoredValue 将不会被动画系统考虑。
这非常有用,尤其在一些复杂自定义 View 中,能让你精准控制动画范围。
内部原理:宏背后做了什么?
@Animatable 的作用,其实就是帮我们自动生成了符合 Animatable 协议的代码。
如果你手动实现,它大致相当于:
struct IntegerView: View, Animatable {var number: Floatvar animatableData: Float {get { number }set { number = newValue }}var body: some View { ... }
}
当我们有多个可以插值的属性时,SwiftUI 会自动把它们组合成一个“向量”来计算变化。
因此,只要属性类型支持 VectorArithmetic,动画就能自然地工作。
应用场景分析
-
数字变化类 UI
比如积分、温度、速度、进度值等动态数字展示,使用@Animatable能让数字从旧值平滑过渡到新值。 -
图表或进度条
当数据更新时,直接用动画展示变化过程,比突兀的数值跳变更自然。 -
自定义形状动画
在 Shape 或 Path 中定义参数(例如半径、角度、长度),使用@Animatable也能自动实现形状的平滑过渡。
总结
新的 @Animatable 宏让 SwiftUI 动画变得更轻松了:
- 不再需要手动实现
Animatable协议; - 支持自动识别并插值所有符合
VectorArithmetic的属性; - 搭配
@AnimatableIgnored可以灵活排除不需要动画的字段。
一句话总结就是:
“过去我们需要告诉 SwiftUI 哪些属性能动;现在只需要告诉它哪些不能动。”
这无疑让动画开发更加直观、优雅,也更符合 SwiftUI “声明式开发”的理念。
