iT邦幫忙

2023 iThome 鐵人賽

DAY 12
0
Mobile Development

SwiftUI 的大大小小系列 第 12

Day 12 - 在 SwiftUI 中使用 transition Modifier

  • 分享至 

  • xImage
  •  

hero

前一篇第 11 天是提到「SwiftUI 中的 Picker](https://ithelp.ithome.com.tw/articles/10317041)」,雖然本系列文章基本上沒有前後關聯,如果你是還沒讀過前一篇的讀者,也推薦你去讀讀。

前言

在畫面狀態改變時,我們時常會需要透過小動畫來讓過場更加有趣。這時候我們就可以來用 transition 這個 modifier 來達到我們的目的

transition Modifier

成果

Before After
1201 1202

transition 的用法

可以透過動畫的對象,執行轉場動畫:

像是透明度

.transition(.opacity)

合併多個轉場效果

可以透過合併多個轉場效果,達到更複雜的效果

因為需要合併多個動畫效果就無法省略 type 名稱,因此前面需要加上 AnyTransition

.transition(AnyTransition
    .opacity
    .combined(with: .offset(x: 0, y: -10))
    .combined(with: .scale)
    .combined(with: .move(edge: .bottom))
)

加上 spring Animation

spring 是蘋果提供的動畫效果,效果會比 .easeInOut 更有趣,也可以加入

像是這樣:

.transition(AnyTransition
    .opacity
    .animation(Animation.spring(.bouncy))

初始 UI

先建立一個簡單的畫面

  • 一個按鈕
  • 一個模仿 GitHub 的 6 * 5 的熱度 grid
  • 透過 random 的方式決定要選哪一個顏色
struct ContentView: View {
    /// Base colors
    static let baseColors = [
        Color(red: 238/255.0, green: 238/255.0, blue: 238/255.0),
        Color(red: 200/255.0, green: 230/255.0, blue: 201/255.0),
        Color(red: 129/255.0, green: 199/255.0, blue: 132/255.0),
        Color(red: 67/255.0, green: 160/255.0, blue: 71/255.0),
        Color(red: 27/255.0, green: 94/255.0, blue: 32/255.0),
    ]
    
    @State private var isShown = false
    private let colors: [Color]
    private let rows: [GridItem]
    
    init() {
        colors = (1...30)
            .map { _ in Int.random(in: 0..<5) }
            .map { ContentView.baseColors[$0] }
        rows = (0..<5)
            .map { _ in GridItem(.fixed(20)) }
    }
    
    var body: some View {
        VStack(spacing: 16) {
            Button {
                self.isShown.toggle()
            } label: {
                HStack {
                    Text("鐵人賽閱讀熱度")
                        .font(.system(size: 17, weight: .medium))
                    Image(systemName: "chevron.right.circle")
                        .rotationEffect(.degrees(isShown ? 90 : 0))
                }
            }
            .foregroundColor(.black)
            if isShown {
                LazyHGrid(rows: rows) {
                    ForEach(0..<30) { index in
                        RoundedRectangle(cornerRadius: 4)
                            .frame(width: 20, height: 20)
                            .foregroundColor(colors[index])
                    }
                }
                .fixedSize()
            }
        }
        .padding()
        .clipped()
        .background(
            RoundedRectangle(cornerRadius: 12)
                .stroke(
                    Color(red: 0.91, green: 0.91, blue: 0.94),
                    lineWidth: 1
                )
        )
    }
}

加入 transitions

1. 加入按鈕觸發時的動畫

用 withAnimation 將 self.isShown.toggle() 包起來,可以讓被 isShown 影響的 UI 元件都以動畫的方式呈現

Button {
    withAnimation(.spring(.bouncy)) {
        self.isShown.toggle()
    }
} label: {
    HStack {
        Text("鐵人賽閱讀熱度")
            .font(.system(size: 17, weight: .medium))
        Image(systemName: "chevron.right.circle")
            .rotationEffect(.degrees(isShown ? 90 : 0))
    }
}

像是這個 icon 在切換的時候就會旋轉:

Image(systemName: "chevron.right.circle")
    .rotationEffect(.degrees(isShown ? 90 : 0))

2. 加入 .transition

今天的範例加入位移、縮放、移動和 spring 動畫:

VStack(spacing: 16) { /* 省略 */ }
.padding()
.clipped()
.transition(AnyTransition
    .opacity
    .combined(with: .offset(x: 0, y: -10))
    .combined(with: .scale)
    .combined(with: .move(edge: .bottom))
    .animation(Animation.spring(.bouncy))
)

執行結果

Before After
1201 1202

完整程式碼

註:本範例以快速呈現為導向,在專案內使用時請適度的設計與封裝。

struct ContentView: View {
    static let baseColors = [
        Color(red: 238/255.0, green: 238/255.0, blue: 238/255.0),
        Color(red: 200/255.0, green: 230/255.0, blue: 201/255.0),
        Color(red: 129/255.0, green: 199/255.0, blue: 132/255.0),
        Color(red: 67/255.0, green: 160/255.0, blue: 71/255.0),
        Color(red: 27/255.0, green: 94/255.0, blue: 32/255.0),
    ]
    
    @State private var isShown = false
    private let colors: [Color]
    private let rows: [GridItem]
    
    init() {
        colors = (1...30)
            .map { _ in Int.random(in: 0..<5) }
            .map { ContentView.baseColors[$0] }
        rows = (0..<5)
            .map { _ in GridItem(.fixed(20)) }
    }
    
    var body: some View {
        VStack(spacing: 16) {
            Button {
                withAnimation(.spring(.bouncy)) {
                    self.isShown.toggle()
                }
            } label: {
                HStack {
                    Text("鐵人賽閱讀熱度")
                        .font(.system(size: 17, weight: .medium))
                    Image(systemName: "chevron.right.circle")
                        .rotationEffect(.degrees(isShown ? 90 : 0))
                }
            }
            .foregroundColor(.black)
            if isShown {
                LazyHGrid(rows: rows) {
                    ForEach(0..<30) { index in
                        RoundedRectangle(cornerRadius: 4)
                            .frame(width: 20, height: 20)
                            .foregroundColor(colors[index])
                    }
                }
                .fixedSize()
                .transition(AnyTransition
                    .opacity
                    .combined(with: .offset(x: 0, y: -10))
                    .combined(with: .scale)
                    .combined(with: .move(edge: .bottom))
                    .animation(Animation.spring(.bouncy))
                )
            }
        }
        .padding()
        .clipped()
        .background(
            RoundedRectangle(cornerRadius: 12)
                .stroke(
                    Color(red: 0.91, green: 0.91, blue: 0.94),
                    lineWidth: 1
                )
        )
    }
}

結語

到這裡就是在 SwiftUI 該如活用 .transition 來呈現轉場效果 。

那今天的 SwiftUI 的大大小小就到這邊,以上,明天見!

環境

  • Xcode 15 beta 8

本篇使用到的 UI 元件和 modifiers 基本上沒有受到版本更新影響

因此 Xcode 14 等環境下使用也是沒問題的。


上一篇
Day 11 - SwiftUI 中的 Picker 和 tags
下一篇
Day 13 - 在 SwiftUI 中利用 EnvironmentObject 進行 Dependency Injection
系列文
SwiftUI 的大大小小30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言