iT邦幫忙

2025 iThome 鐵人賽

DAY 16
0
Mobile Development

從零開始學習 iOS系列 第 16

從零開始學習 iOS Day15 - 動畫

  • 分享至 

  • xImage
  •  

SwiftUI 提供簡單直覺的方式來實現動畫,主要有三大核心:

  1. 隱式動畫 (Implicit Animation) → 用animation來觸發
  2. 顯式動畫 (Explicit Animation) → 用 withAnimation 來觸發
  3. 過渡動畫 (Transition) → 視圖新增/移除的動畫效果

隱式動畫 (Implicit Animation)

狀態改變 時,透過 .animation() 自動套用動畫。

struct ImplicitAnimationExample: View {
    @State private var isBig = false

    var body: some View {
        VStack {
            Circle()
                .fill(Color.blue)
                .frame(width: isBig ? 200 : 100, height: isBig ? 200 : 100)
                .animation(.easeInOut, value: isBig)

            Button("切換大小") {
                isBig.toggle()
            }
        }
    }
}

https://github.com/jian-fu-hung/ithelp-2025/blob/main/image/Day15/%E8%9E%A2%E5%B9%95%E9%8C%84%E5%BD%B1%202025-09-30%20%E6%99%9A%E4%B8%8A11.24.08.gif?raw=true

  • 使用 .animation(..., value:) 第一個參數是動畫曲線(速度控制),第二個參數是監聽的狀態值。
  • 每當 value 改變時,自動執行動畫

顯式動畫 (Explicit Animation)

使用 withAnimation 包住狀態改變,手動觸發動畫。

struct ExplicitAnimationExample: View {
    @State private var isRotated = false

    var body: some View {
        VStack {
            Rectangle()
                .fill(Color.green)
                .frame(width: 100, height: 100)
                .rotationEffect(.degrees(isRotated ? 180 : 0))

            Button("旋轉") {
                withAnimation(.spring()) {
                    isRotated.toggle()
                }
            }
        }
    }
}

https://github.com/jian-fu-hung/ithelp-2025/blob/main/image/Day15/%E8%9E%A2%E5%B9%95%E9%8C%84%E5%BD%B1%202025-09-30%20%E6%99%9A%E4%B8%8A11.24.48.gif?raw=true

  • 只在 withAnimation 區塊內的狀態改變會有動畫效果
  • 適合用在「單次互動」

常見的Animation

SwiftUI 提供多種動畫速度:

  • .linear:沒有加速度,從頭到尾速度都一樣。
  • .easeIn:動畫一開始慢,後面加速。
  • .easeOut:動畫一開始快,結束時慢下來。
  • .easeInOut:常見於平滑過渡,進出效果自然。
  • .spring:有彈性與回彈的感覺,可以調整 阻尼(damping)速度(speed)

範例:比較不同曲線的差異

struct Implicit2AnimationExample: View {
    @State private var moveRight = false

        var body: some View {
            VStack {
                Circle()
                    .fill(Color.blue)
                    .frame(width: 100, height: 100)
                    .offset(x: moveRight ? 150 : -150)
                    .animation(.linear, value: moveRight)
                Circle()
                    .fill(Color.blue)
                    .frame(width: 100, height: 100)
                    .offset(x: moveRight ? 150 : -150)
                    .animation(.easeInOut, value: moveRight)
                Circle()
                    .fill(Color.blue)
                    .frame(width: 100, height: 100)
                    .offset(x: moveRight ? 150 : -150)
                    .animation(.easeOut, value: moveRight)
                Circle()
                    .fill(Color.blue)
                    .frame(width: 100, height: 100)
                    .offset(x: moveRight ? 150 : -150)
                    .animation(.easeIn, value: moveRight)
                Circle()
                    .fill(Color.blue)
                    .frame(width: 100, height: 100)
                    .offset(x: moveRight ? 150 : -150)
                    .animation(.spring, value: moveRight)

                Button("切換位置") {
                    moveRight.toggle()
                }
                .padding(.top, 50)
            }
        }
}

https://github.com/jian-fu-hung/ithelp-2025/blob/main/image/Day15/%E8%9E%A2%E5%B9%95%E9%8C%84%E5%BD%B1%202025-09-30%20%E6%99%9A%E4%B8%8A11.24.24.gif?raw=true


過渡動畫 (Transition)

當 View 被 新增或移除 時,可以用 .transition() 套用動畫。

struct TransitionExample: View {
    @State private var showBox = false

    var body: some View {
        VStack {
            if showBox {
                Rectangle()
                    .fill(Color.red)
                    .frame(width: 200, height: 200)
                    .transition(.slide)
            }

            Button("切換顯示") {
                withAnimation(.easeInOut) {
                    showBox.toggle()
                }
            }
        }
    }
}

https://github.com/jian-fu-hung/ithelp-2025/blob/main/image/Day15/%E8%9E%A2%E5%B9%95%E9%8C%84%E5%BD%B1%202025-09-30%20%E6%99%9A%E4%B8%8A11.25.01.gif?raw=true

常用的 Transition:

  • .opacity → 淡入淡出
  • .slide → 從邊緣滑入/滑出
  • .scale → 放大縮小出現
  • .asymmetric(insertion: .slide, removal: .opacity) → 插入和移除使用不同動畫

其他常用動畫

重複動畫(repeatForever)

struct LoopAnimationExample : View {
    @State private var isRotated = false
    var body: some View {
        VStack {
            Rectangle()
                .fill(Color.green)
                .frame(width: 100, height: 100)
                .rotationEffect(.degrees(isRotated ? 360 : 0))
                .animation(.linear(duration: 2).repeatForever(autoreverses: false), value: isRotated)
            
        }
        .onAppear {
            isRotated = true
        }
    }
}

https://github.com/jian-fu-hung/ithelp-2025/blob/main/image/Day15/%E8%9E%A2%E5%B9%95%E9%8C%84%E5%BD%B1%202025-09-30%20%E6%99%9A%E4%B8%8A11.25.12.gif?raw=true

延遲動畫(delay)

struct DelayAnimationExample : View {
    @State private var isRotated = true
    var body: some View {
        VStack {
            Rectangle()
                .fill(Color.green)
                .frame(width: 100, height: 100)
                .rotationEffect(.degrees(isRotated ? 360 : 0))
                .animation(.easeInOut.delay(1), value: isRotated)
            Button("旋轉") {
                withAnimation(.spring()) {
                    isRotated.toggle()
                }
            }
        }
    }
}

https://github.com/jian-fu-hung/ithelp-2025/blob/main/image/Day15/%E8%9E%A2%E5%B9%95%E9%8C%84%E5%BD%B1%202025-09-30%20%E6%99%9A%E4%B8%8A11.25.26.gif?raw=true


今日小結

  • 隱式動畫.animation(..., value:) 綁定狀態變化
  • 顯式動畫withAnimation { ... } 包狀態改變
  • 過渡動畫.transition(...) 控制新增/移除的效果
  • 其他常用動畫.repeatForever.delay

明天我們將介紹 Canvas,用來在 SwiftUI 中實現更靈活的繪圖與動畫效果。


上一篇
從零開始學習 iOS Day14 - 導航與分頁
下一篇
從零開始學習 iOS Day16 - Canvas
系列文
從零開始學習 iOS24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言