因爲 API 變更,此文章部份內容已失效,最新完整中文教程及代碼請查看 github.com/WillieWangW…git
SwiftUI
表明將來構建 App 的方向,歡迎加羣一塊兒交流技術,解決問題。github
使用
SwiftUI
時,不管用做何處,咱們均可以單獨爲 view 添加動畫,或者對 view 的狀態進行動畫處理。SwiftUI
爲咱們處理全部動畫的組合、重疊和中斷的複雜性。spring在本文中,咱們會給包含圖表的 view 設置動畫,跟蹤用戶在使用
Landmarks
app 時行爲。咱們會看到經過使用animation(_:)
方法爲 view 設置動畫是多麼簡單。canvas下載項目文件並按照如下步驟操做,也能夠打開已完成的項目自行瀏覽代碼。swift
- 預計完成時間:20 分鐘
- 項目文件:下載
當咱們在一個 view 上使用 animation(_:)
方法時, SwiftUI
會動態的修改這個 view 的可動畫屬性。一個 view 的顏色、透明度、旋轉、大小以及其餘屬性都是可動畫的。bash
1.1 在 HikeView.swift
中,打開實時預覽來測試顯示和隱藏圖表。微信
確保在本文中過程當中都打開了實時預覽,這樣就能夠測試到每一步的結果。app
1.2 添加 animation(.basic())
方法來打開按鈕的旋轉動畫。ide
HikeView.swift函數
import SwiftUI
struct HikeView: View {
var hike: Hike
@State private var showDetail = false
var body: some View {
VStack {
HStack {
HikeGraph(data: hike.observations, path: \.elevation)
.frame(width: 50, height: 30)
VStack(alignment: .leading) {
Text(hike.name)
.font(.headline)
Text(hike.distanceText)
}
Spacer()
Button(action: {
self.showDetail.toggle()
}) {
Image(systemName: "chevron.right.circle")
.imageScale(.large)
.rotationEffect(.degrees(showDetail ? 90 : 0))
.padding()
.animation(.basic())
}
}
if showDetail {
HikeDetail(hike: hike)
}
}
}
}
複製代碼
1.3 添加一個在圖表顯示時讓按鈕變大的動畫。
animation(_:)
會做用於 view 所包裝的全部可動畫的修改。
HikeView.swift
import SwiftUI
struct HikeView: View {
var hike: Hike
@State private var showDetail = false
var body: some View {
VStack {
HStack {
HikeGraph(data: hike.observations, path: \.elevation)
.frame(width: 50, height: 30)
VStack(alignment: .leading) {
Text(hike.name)
.font(.headline)
Text(hike.distanceText)
}
Spacer()
Button(action: {
self.showDetail.toggle()
}) {
Image(systemName: "chevron.right.circle")
.imageScale(.large)
.rotationEffect(.degrees(showDetail ? 90 : 0))
.scaleEffect(showDetail ? 1.5 : 1)
.padding()
.animation(.basic())
}
}
if showDetail {
HikeDetail(hike: hike)
}
}
}
}
複製代碼
1.4 把動畫類型從 .basic()
改爲 .spring()
。
SwiftUI
包含帶有預設或自定義緩動的基本動畫,以及彈性和流體動畫。咱們能夠調整動畫的速度、在動畫開始以前設置延遲,或指定動畫的重複。
HikeView.swift
import SwiftUI
struct HikeView: View {
var hike: Hike
@State private var showDetail = false
var body: some View {
VStack {
HStack {
HikeGraph(data: hike.observations, path: \.elevation)
.frame(width: 50, height: 30)
VStack(alignment: .leading) {
Text(hike.name)
.font(.headline)
Text(hike.distanceText)
}
Spacer()
Button(action: {
self.showDetail.toggle()
}) {
Image(systemName: "chevron.right.circle")
.imageScale(.large)
.rotationEffect(.degrees(showDetail ? 90 : 0))
.scaleEffect(showDetail ? 1.5 : 1)
.padding()
.animation(.spring())
}
}
if showDetail {
HikeDetail(hike: hike)
}
}
}
}
複製代碼
嘗試在 scaleEffect
方法上方添加另外一個動畫方法來關閉旋轉動畫。
圍繞 SwiftUI
嘗試結合不一樣的動畫效果,看看都有哪些效果。
HikeView.swift
import SwiftUI
struct HikeView: View {
var hike: Hike
@State private var showDetail = false
var body: some View {
VStack {
HStack {
HikeGraph(data: hike.observations, path: \.elevation)
.frame(width: 50, height: 30)
VStack(alignment: .leading) {
Text(hike.name)
.font(.headline)
Text(hike.distanceText)
}
Spacer()
Button(action: {
self.showDetail.toggle()
}) {
Image(systemName: "chevron.right.circle")
.imageScale(.large)
.rotationEffect(.degrees(showDetail ? 90 : 0))
.animation(nil)
.scaleEffect(showDetail ? 1.5 : 1)
.padding()
.animation(.spring())
}
}
if showDetail {
HikeDetail(hike: hike)
}
}
}
}
複製代碼
1.6 在繼續下一節前,刪除兩個 animation(_:)
方法。
HikeView.swift
import SwiftUI
struct HikeView: View {
var hike: Hike
@State private var showDetail = false
var body: some View {
VStack {
HStack {
HikeGraph(data: hike.observations, path: \.elevation)
.frame(width: 50, height: 30)
VStack(alignment: .leading) {
Text(hike.name)
.font(.headline)
Text(hike.distanceText)
}
Spacer()
Button(action: {
self.showDetail.toggle()
}) {
Image(systemName: "chevron.right.circle")
.imageScale(.large)
.rotationEffect(.degrees(showDetail ? 90 : 0))
.scaleEffect(showDetail ? 1.5 : 1)
.padding()
}
}
if showDetail {
HikeDetail(hike: hike)
}
}
}
}
複製代碼
如今咱們已經學會若是給單個 view 添加動畫,是時候給狀態的值的改變添加動畫了。
這一節,咱們會給用戶點擊按鈕並切換 showDetail
狀態屬性時發生的全部更改添加動畫。
2.1 將 showDetail.toggle()
的調用包裝到 withAnimation
函數中。
受 showDetail
屬性影響的公開按鈕和 HikeDetail
view 如今就都有了動畫過渡。
HikeView.swift
import SwiftUI
struct HikeView: View {
var hike: Hike
@State private var showDetail = false
var body: some View {
VStack {
HStack {
HikeGraph(data: hike.observations, path: \.elevation)
.frame(width: 50, height: 30)
VStack(alignment: .leading) {
Text(hike.name)
.font(.headline)
Text(hike.distanceText)
}
Spacer()
Button(action: {
withAnimation {
self.showDetail.toggle()
}
}) {
Image(systemName: "chevron.right.circle")
.imageScale(.large)
.rotationEffect(.degrees(showDetail ? 90 : 0))
.scaleEffect(showDetail ? 1.5 : 1)
.padding()
}
}
if showDetail {
HikeDetail(hike: hike)
}
}
}
}
複製代碼
減緩動畫,看看 SwiftUI
動畫是如何能夠中斷的。
2.2 給 withAnimation
方法傳遞一個 4 秒的基礎動畫。
咱們能夠傳遞相同類型的動畫給 animation(_:)
的 withAnimation
函數。
HikeView.swift
import SwiftUI
struct HikeView: View {
var hike: Hike
@State private var showDetail = false
var body: some View {
VStack {
HStack {
HikeGraph(data: hike.observations, path: \.elevation)
.frame(width: 50, height: 30)
VStack(alignment: .leading) {
Text(hike.name)
.font(.headline)
Text(hike.distanceText)
}
Spacer()
Button(action: {
withAnimation(.basic(duration: 4)) {
self.showDetail.toggle()
}
}) {
Image(systemName: "chevron.right.circle")
.imageScale(.large)
.rotationEffect(.degrees(showDetail ? 90 : 0))
.scaleEffect(showDetail ? 1.5 : 1)
.padding()
}
}
if showDetail {
HikeDetail(hike: hike)
}
}
}
}
複製代碼
2.3 嘗試在動畫期間打開和關閉圖表 view 。
2.4 在進入下一節前,從 withAnimation
函數中移除緩慢動畫。
HikeView.swift
import SwiftUI
struct HikeView: View {
var hike: Hike
@State private var showDetail = false
var body: some View {
VStack {
HStack {
HikeGraph(data: hike.observations, path: \.elevation)
.frame(width: 50, height: 30)
VStack(alignment: .leading) {
Text(hike.name)
.font(.headline)
Text(hike.distanceText)
}
Spacer()
Button(action: {
withAnimation {
self.showDetail.toggle()
}
}) {
Image(systemName: "chevron.right.circle")
.imageScale(.large)
.rotationEffect(.degrees(showDetail ? 90 : 0))
.scaleEffect(showDetail ? 1.5 : 1)
.padding()
}
}
if showDetail {
HikeDetail(hike: hike)
}
}
}
}
複製代碼
默認狀況下,view 經過淡入和淡出過渡到屏幕上和屏幕外。咱們能夠使用 transition(_:)
方法來自定義轉場。
3.1 給知足條件時顯示的 HikeView
添加一個 transition(_:)
方法。
如今圖標會滑動顯示和消失。
HikeView.swift
import SwiftUI
struct HikeView: View {
var hike: Hike
@State private var showDetail = false
var body: some View {
VStack {
HStack {
HikeGraph(data: hike.observations, path: \.elevation)
.frame(width: 50, height: 30)
VStack(alignment: .leading) {
Text(hike.name)
.font(.headline)
Text(hike.distanceText)
}
Spacer()
Button(action: {
withAnimation {
self.showDetail.toggle()
}
}) {
Image(systemName: "chevron.right.circle")
.imageScale(.large)
.rotationEffect(.degrees(showDetail ? 90 : 0))
.scaleEffect(showDetail ? 1.5 : 1)
.padding()
}
}
if showDetail {
HikeDetail(hike: hike)
.transition(.slide)
}
}
}
}
複製代碼
3.2 將轉場提取爲 AnyTransition
的靜態屬性。
這能夠在您展開自定義轉場時保持代碼清晰。對於自定義轉場,咱們能夠使用與 SwiftUI
所用相同的 .
符號。
HikeView.swift
import SwiftUI
extension AnyTransition {
static var moveAndFade: AnyTransition {
AnyTransition.slide
}
}
struct HikeView: View {
var hike: Hike
@State private var showDetail = false
var body: some View {
VStack {
HStack {
HikeGraph(data: hike.observations, path: \.elevation)
.frame(width: 50, height: 30)
VStack(alignment: .leading) {
Text(hike.name)
.font(.headline)
Text(hike.distanceText)
}
Spacer()
Button(action: {
withAnimation {
self.showDetail.toggle()
}
}) {
Image(systemName: "chevron.right.circle")
.imageScale(.large)
.rotationEffect(.degrees(showDetail ? 90 : 0))
.scaleEffect(showDetail ? 1.5 : 1)
.padding()
}
}
if showDetail {
HikeDetail(hike: hike)
.transition(.moveAndFade)
}
}
}
}
複製代碼
3.3 換成使用 move(edge:)
轉場,這樣圖表會從同一邊滑入和滑出。
HikeView.swift
import SwiftUI
extension AnyTransition {
static var moveAndFade: AnyTransition {
AnyTransition.move(edge: .trailing)
}
}
struct HikeView: View {
var hike: Hike
@State private var showDetail = false
var body: some View {
VStack {
HStack {
HikeGraph(data: hike.observations, path: \.elevation)
.frame(width: 50, height: 30)
VStack(alignment: .leading) {
Text(hike.name)
.font(.headline)
Text(hike.distanceText)
}
Spacer()
Button(action: {
withAnimation {
self.showDetail.toggle()
}
}) {
Image(systemName: "chevron.right.circle")
.imageScale(.large)
.rotationEffect(.degrees(showDetail ? 90 : 0))
.scaleEffect(showDetail ? 1.5 : 1)
.padding()
}
}
if showDetail {
HikeDetail(hike: hike)
.transition(.moveAndFade)
}
}
}
}
複製代碼
3.4 使用 asymmetric(insertion:removal:)
方法來給 view 顯示和消失時提供不一樣的轉場。
HikeView.swift
import SwiftUI
extension AnyTransition {
static var moveAndFade: AnyTransition {
let insertion = AnyTransition.move(edge: .trailing)
.combined(with: .opacity)
let removal = AnyTransition.scale()
.combined(with: .opacity)
return .asymmetric(insertion: insertion, removal: removal)
}
}
struct HikeView: View {
var hike: Hike
@State private var showDetail = false
var body: some View {
VStack {
HStack {
HikeGraph(data: hike.observations, path: \.elevation)
.frame(width: 50, height: 30)
VStack(alignment: .leading) {
Text(hike.name)
.font(.headline)
Text(hike.distanceText)
}
Spacer()
Button(action: {
withAnimation {
self.showDetail.toggle()
}
}) {
Image(systemName: "chevron.right.circle")
.imageScale(.large)
.rotationEffect(.degrees(showDetail ? 90 : 0))
.scaleEffect(showDetail ? 1.5 : 1)
.padding()
}
}
if showDetail {
HikeDetail(hike: hike)
.transition(.moveAndFade)
}
}
}
}
複製代碼
單擊條形下方的按鈕時,圖形會在三組不一樣的數據之間切換。在本節中,咱們將使用組合動畫爲構成圖形的 Capsule
提供動態、波動的轉場。
4.1 把 showDetail
的默認值改爲 true
,並把 HikeView
的預覽固定在 canvas
中,
這讓咱們在其餘文件中製做動畫時依然能在上下文中看到圖表。
4.2 在 GraphCapsule.swift
中,添加一個新的計算動畫屬性,並將其應用於 Capsule
的 shape
。
GraphCapsule.swift
import SwiftUI
struct GraphCapsule: View {
var index: Int
var height: Length
var range: Range<Double>
var overallRange: Range<Double>
var heightRatio: Length {
max(Length(magnitude(of: range) / magnitude(of: overallRange)), 0.15)
}
var offsetRatio: Length {
Length((range.lowerBound - overallRange.lowerBound) / magnitude(of: overallRange))
}
var animation: Animation {
Animation.default
}
var body: some View {
Capsule()
.fill(Color.gray)
.frame(height: height * heightRatio, alignment: .bottom)
.offset(x: 0, y: height * -offsetRatio)
.animation(animation)
)
}
}
複製代碼
4.3 將動畫改成彈性動畫,使用初始速度讓條形圖跳躍。
GraphCapsule.swift
import SwiftUI
struct GraphCapsule: View {
var index: Int
var height: Length
var range: Range<Double>
var overallRange: Range<Double>
var heightRatio: Length {
max(Length(magnitude(of: range) / magnitude(of: overallRange)), 0.15)
}
var offsetRatio: Length {
Length((range.lowerBound - overallRange.lowerBound) / magnitude(of: overallRange))
}
var animation: Animation {
Animation.spring(initialVelocity: 5)
}
var body: some View {
Capsule()
.fill(Color.gray)
.frame(height: height * heightRatio, alignment: .bottom)
.offset(x: 0, y: height * -offsetRatio)
.animation(animation)
)
}
}
複製代碼
4.4 加快動畫速度,縮短每一個小節移動到新位置所需的時間。
GraphCapsule.swift
import SwiftUI
struct GraphCapsule: View {
var index: Int
var height: Length
var range: Range<Double>
var overallRange: Range<Double>
var heightRatio: Length {
max(Length(magnitude(of: range) / magnitude(of: overallRange)), 0.15)
}
var offsetRatio: Length {
Length((range.lowerBound - overallRange.lowerBound) / magnitude(of: overallRange))
}
var animation: Animation {
Animation.spring(initialVelocity: 5)
.speed(2)
}
var body: some View {
Capsule()
.fill(Color.gray)
.frame(height: height * heightRatio, alignment: .bottom)
.offset(x: 0, y: height * -offsetRatio)
.animation(animation)
)
}
}
複製代碼
4.5 根據 Capsule
在圖表上的位置爲每一個動畫添加延遲。
GraphCapsule.swift
import SwiftUI
struct GraphCapsule: View {
var index: Int
var height: Length
var range: Range<Double>
var overallRange: Range<Double>
var heightRatio: Length {
max(Length(magnitude(of: range) / magnitude(of: overallRange)), 0.15)
}
var offsetRatio: Length {
Length((range.lowerBound - overallRange.lowerBound) / magnitude(of: overallRange))
}
var animation: Animation {
Animation.spring(initialVelocity: 5)
.speed(2)
.delay(0.03 * Double(index))
}
var body: some View {
Capsule()
.fill(Color.gray)
.frame(height: height * heightRatio, alignment: .bottom)
.offset(x: 0, y: height * -offsetRatio)
.animation(animation)
)
}
}
複製代碼
4.6 觀察自定義動畫在圖表之間轉場時是如何營造波紋效果的。