前一篇第 11 天是提到「SwiftUI 中的 Picker](https://ithelp.ithome.com.tw/articles/10317041)」,雖然本系列文章基本上沒有前後關聯,如果你是還沒讀過前一篇的讀者,也推薦你去讀讀。
在畫面狀態改變時,我們時常會需要透過小動畫來讓過場更加有趣。這時候我們就可以來用 transition
這個 modifier 來達到我們的目的
transition
ModifierBefore | After |
---|---|
像是透明度
.transition(.opacity)
可以透過合併多個轉場效果,達到更複雜的效果
因為需要合併多個動畫效果就無法省略 type 名稱,因此前面需要加上 AnyTransition
.transition(AnyTransition
.opacity
.combined(with: .offset(x: 0, y: -10))
.combined(with: .scale)
.combined(with: .move(edge: .bottom))
)
spring 是蘋果提供的動畫效果,效果會比 .easeInOut 更有趣,也可以加入
像是這樣:
.transition(AnyTransition
.opacity
.animation(Animation.spring(.bouncy))
先建立一個簡單的畫面
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
)
)
}
}
用 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))
今天的範例加入位移、縮放、移動和 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 |
---|---|
註:本範例以快速呈現為導向,在專案內使用時請適度的設計與封裝。
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 的大大小小就到這邊,以上,明天見!
本篇使用到的 UI 元件和 modifiers 基本上沒有受到版本更新影響
因此 Xcode 14 等環境下使用也是沒問題的。