280 lines
9.1 KiB
Markdown
280 lines
9.1 KiB
Markdown
|
|
---
|
||
|
|
name: liquid-glass-design
|
||
|
|
description: iOS 26 Liquid Glass 設計系統 — 針對 SwiftUI、UIKit 與 WidgetKit 提供的具備模糊、反射及互動式形變效果的動態玻璃材質。
|
||
|
|
---
|
||
|
|
|
||
|
|
# Liquid Glass 設計系統 (iOS 26)
|
||
|
|
|
||
|
|
實作 Apple Liquid Glass 的模式 — 這是一種動態材質,它能模糊背景內容、反射周圍內容的顏色與光線,並對觸控和指標交互做出反應。內容涵蓋 SwiftUI、UIKit 與 WidgetKit 的整合。
|
||
|
|
|
||
|
|
## 何時啟用
|
||
|
|
|
||
|
|
- 為 iOS 26+ 建構或更新採用新設計語言的 App。
|
||
|
|
- 實作玻璃風格的按鈕、卡片、工具列或容器。
|
||
|
|
- 在玻璃元素之間建立形變 (Morphing) 轉換。
|
||
|
|
- 將 Liquid Glass 效果應用於小工具 (Widgets)。
|
||
|
|
- 將現有的模糊 (Blur) / 材質效果遷移至新的 Liquid Glass API。
|
||
|
|
|
||
|
|
## 核心模式 — SwiftUI
|
||
|
|
|
||
|
|
### 基礎玻璃效果 (Basic Glass Effect)
|
||
|
|
|
||
|
|
在任何視圖中添加 Liquid Glass 最簡單的方法:
|
||
|
|
|
||
|
|
```swift
|
||
|
|
Text("Hello, World!")
|
||
|
|
.font(.title)
|
||
|
|
.padding()
|
||
|
|
.glassEffect() // 預設值:普通變體,膠囊形狀
|
||
|
|
```
|
||
|
|
|
||
|
|
### 自定義形狀與色調 (Shape and Tint)
|
||
|
|
|
||
|
|
```swift
|
||
|
|
Text("Hello, World!")
|
||
|
|
.font(.title)
|
||
|
|
.padding()
|
||
|
|
.glassEffect(.regular.tint(.orange).interactive(), in: .rect(cornerRadius: 16.0))
|
||
|
|
```
|
||
|
|
|
||
|
|
關鍵自定義選項:
|
||
|
|
- `.regular` — 標準玻璃效果。
|
||
|
|
- `.tint(Color)` — 加上色調以增加視覺突顯。
|
||
|
|
- `.interactive()` — 讓玻璃對觸控和指標交互做出反應。
|
||
|
|
- 形狀:`.capsule` (預設)、`.rect(cornerRadius:)`、`.circle`。
|
||
|
|
|
||
|
|
### 玻璃按鈕樣式
|
||
|
|
|
||
|
|
```swift
|
||
|
|
Button("點擊我") { /* 執行動作 */ }
|
||
|
|
.buttonStyle(.glass)
|
||
|
|
|
||
|
|
Button("重要操作") { /* 執行動作 */ }
|
||
|
|
.buttonStyle(.glassProminent)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 多元素的 GlassEffectContainer
|
||
|
|
|
||
|
|
針對效能考量與形變效果,務必將多個玻璃視圖包裹在一個容器中:
|
||
|
|
|
||
|
|
```swift
|
||
|
|
GlassEffectContainer(spacing: 40.0) {
|
||
|
|
HStack(spacing: 40.0) {
|
||
|
|
Image(systemName: "scribble.variable")
|
||
|
|
.frame(width: 80.0, height: 80.0)
|
||
|
|
.font(.system(size: 36))
|
||
|
|
.glassEffect()
|
||
|
|
|
||
|
|
Image(systemName: "eraser.fill")
|
||
|
|
.frame(width: 80.0, height: 80.0)
|
||
|
|
.font(.system(size: 36))
|
||
|
|
.glassEffect()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
`spacing` 參數控制合併距離 (Merge distance) — 距離越近的元素,其玻璃形狀會融合在一起。
|
||
|
|
|
||
|
|
### 聯合玻璃效果 (Uniting Glass Effects)
|
||
|
|
|
||
|
|
使用 `glassEffectUnion` 將多個視圖結合成單一玻璃形狀:
|
||
|
|
|
||
|
|
```swift
|
||
|
|
@Namespace private var namespace
|
||
|
|
|
||
|
|
GlassEffectContainer(spacing: 20.0) {
|
||
|
|
HStack(spacing: 20.0) {
|
||
|
|
ForEach(symbolSet.indices, id: \.self) { item in
|
||
|
|
Image(systemName: symbolSet[item])
|
||
|
|
.frame(width: 80.0, height: 80.0)
|
||
|
|
.glassEffect()
|
||
|
|
.glassEffectUnion(id: item < 2 ? "第一組" : "第二組", namespace: namespace)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 形變轉換 (Morphing Transitions)
|
||
|
|
|
||
|
|
在玻璃元素出現或消失時建立平滑的形變效果:
|
||
|
|
|
||
|
|
```swift
|
||
|
|
@State private var isExpanded = false
|
||
|
|
@Namespace private var namespace
|
||
|
|
|
||
|
|
GlassEffectContainer(spacing: 40.0) {
|
||
|
|
HStack(spacing: 40.0) {
|
||
|
|
Image(systemName: "scribble.variable")
|
||
|
|
.frame(width: 80.0, height: 80.0)
|
||
|
|
.glassEffect()
|
||
|
|
.glassEffectID("pencil", in: namespace)
|
||
|
|
|
||
|
|
if isExpanded {
|
||
|
|
Image(systemName: "eraser.fill")
|
||
|
|
.frame(width: 80.0, height: 80.0)
|
||
|
|
.glassEffect()
|
||
|
|
.glassEffectID("eraser", in: namespace)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Button("切換狀態") {
|
||
|
|
withAnimation { isExpanded.toggle() }
|
||
|
|
}
|
||
|
|
.buttonStyle(.glass)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 使用側邊欄時擴展水平滾動範圍
|
||
|
|
|
||
|
|
為了讓水平滾動內容能延伸至側邊欄 (Sidebar) 或檢查器 (Inspector) 下方,請確保 `ScrollView` 的內容觸及容器的兩側邊緣。當佈局延伸至邊緣時,系統會自動處理側邊欄下方的滾動行為 — 無需額外的修正符 (Modifier)。
|
||
|
|
|
||
|
|
## 核心模式 — UIKit
|
||
|
|
|
||
|
|
### 基礎 UIGlassEffect
|
||
|
|
|
||
|
|
```swift
|
||
|
|
let glassEffect = UIGlassEffect()
|
||
|
|
glassEffect.tintColor = UIColor.systemBlue.withAlphaComponent(0.3)
|
||
|
|
glassEffect.isInteractive = true
|
||
|
|
|
||
|
|
let visualEffectView = UIVisualEffectView(effect: glassEffect)
|
||
|
|
visualEffectView.translatesAutoresizingMaskIntoConstraints = false
|
||
|
|
visualEffectView.layer.cornerRadius = 20
|
||
|
|
visualEffectView.clipsToBounds = true
|
||
|
|
|
||
|
|
view.addSubview(visualEffectView)
|
||
|
|
NSLayoutConstraint.activate([
|
||
|
|
visualEffectView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||
|
|
visualEffectView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
|
||
|
|
visualEffectView.widthAnchor.constraint(equalToConstant: 200),
|
||
|
|
visualEffectView.heightAnchor.constraint(equalToConstant: 120)
|
||
|
|
])
|
||
|
|
|
||
|
|
// 將內容加入至 contentView
|
||
|
|
let label = UILabel()
|
||
|
|
label.text = "Liquid Glass"
|
||
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
||
|
|
visualEffectView.contentView.addSubview(label)
|
||
|
|
NSLayoutConstraint.activate([
|
||
|
|
label.centerXAnchor.constraint(equalTo: visualEffectView.contentView.centerXAnchor),
|
||
|
|
label.centerYAnchor.constraint(equalTo: visualEffectView.contentView.centerYAnchor)
|
||
|
|
])
|
||
|
|
```
|
||
|
|
|
||
|
|
### 多元素的 UIGlassContainerEffect
|
||
|
|
|
||
|
|
```swift
|
||
|
|
let containerEffect = UIGlassContainerEffect()
|
||
|
|
containerEffect.spacing = 40.0
|
||
|
|
|
||
|
|
let containerView = UIVisualEffectView(effect: containerEffect)
|
||
|
|
|
||
|
|
let firstGlass = UIVisualEffectView(effect: UIGlassEffect())
|
||
|
|
let secondGlass = UIVisualEffectView(effect: UIGlassEffect())
|
||
|
|
|
||
|
|
containerView.contentView.addSubview(firstGlass)
|
||
|
|
containerView.contentView.addSubview(secondGlass)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 滾動邊緣效果 (Scroll Edge Effects)
|
||
|
|
|
||
|
|
```swift
|
||
|
|
scrollView.topEdgeEffect.style = .automatic
|
||
|
|
scrollView.bottomEdgeEffect.style = .hard
|
||
|
|
scrollView.leftEdgeEffect.isHidden = true
|
||
|
|
```
|
||
|
|
|
||
|
|
### 工具列玻璃整合
|
||
|
|
|
||
|
|
```swift
|
||
|
|
let favoriteButton = UIBarButtonItem(image: UIImage(systemName: "heart"), style: .plain, target: self, action: #selector(favoriteAction))
|
||
|
|
favoriteButton.hidesSharedBackground = true // 退出共用的玻璃背景
|
||
|
|
```
|
||
|
|
|
||
|
|
## 核心模式 — WidgetKit
|
||
|
|
|
||
|
|
### 渲染模式偵測 (Rendering Mode Detection)
|
||
|
|
|
||
|
|
```swift
|
||
|
|
struct MyWidgetView: View {
|
||
|
|
@Environment(\.widgetRenderingMode) var renderingMode
|
||
|
|
|
||
|
|
var body: some View {
|
||
|
|
if renderingMode == .accented {
|
||
|
|
// 色調模式:採用帶有白色的、具備主題色彩的玻璃背景
|
||
|
|
} else {
|
||
|
|
// 全彩模式:採用標準外觀
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 視覺階層的強調組 (Accent Groups)
|
||
|
|
|
||
|
|
```swift
|
||
|
|
HStack {
|
||
|
|
VStack(alignment: .leading) {
|
||
|
|
Text("標題")
|
||
|
|
.widgetAccentable() // 強調組
|
||
|
|
Text("副標題")
|
||
|
|
// 主要組 (預設)
|
||
|
|
}
|
||
|
|
Image(systemName: "star.fill")
|
||
|
|
.widgetAccentable() // 強調組
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 強調模式下的圖像渲染
|
||
|
|
|
||
|
|
```swift
|
||
|
|
Image("myImage")
|
||
|
|
.widgetAccentedRenderingMode(.monochrome)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 容器背景
|
||
|
|
|
||
|
|
```swift
|
||
|
|
VStack { /* 內容 */ }
|
||
|
|
.containerBackground(for: .widget) {
|
||
|
|
Color.blue.opacity(0.2)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## 關鍵設計決策
|
||
|
|
|
||
|
|
| 決策點 | 理由說明 |
|
||
|
|
|----------|-----------|
|
||
|
|
| GlassEffectContainer 包裹 | 效能優化,並實現在玻璃元素之間進行形變 |
|
||
|
|
| `spacing` 參數 | 控制合併距離 — 精細調整元素需距離多近才會開始融合 |
|
||
|
|
| `@Namespace` + `glassEffectID` | 當視圖階層變動時,能達成平滑的形變轉換 |
|
||
|
|
| `interactive()` 修正符 | 由開發者明確開啟觸控/指標交互 — 並非所有玻璃元素都需要反應 |
|
||
|
|
| UIKit 中的 UIGlassContainerEffect | 維持與 SwiftUI 相同的容器模式以保持一致性 |
|
||
|
|
| 小工具中的強調渲染模式 | 當使用者選擇有色調的主畫面時,系統會套用有色調的玻璃效果 |
|
||
|
|
|
||
|
|
## 最佳實踐
|
||
|
|
|
||
|
|
- **凡是將玻璃效果套用至多個同級視圖時,務必使用 GlassEffectContainer** — 這能實現形變效果並提升渲染效能。
|
||
|
|
- **在其他外觀修正符 (Frame, Font, Padding) 之後**再套用 `.glassEffect()`。
|
||
|
|
- **僅在需要回應互動的元素上使用 `.interactive()`** (如按鈕、可切換項目)。
|
||
|
|
- **謹慎選擇容器間距 (Spacing)**,以控制玻璃效果何時開始合併。
|
||
|
|
- **變動視圖階層時使用 `withAnimation`**,以利產生平滑的形變轉換動畫。
|
||
|
|
- **在不同外觀下進行測試** — 包含淺色模式、深色模式以及強調/色調模式。
|
||
|
|
- **確保留存無障礙對比度** — 玻璃材質上的文字必須保持清晰可讀。
|
||
|
|
|
||
|
|
## 應避免的反模式
|
||
|
|
|
||
|
|
- 在沒有 GlassEffectContainer 的情況下使用多個獨立的 `.glassEffect()` 視圖。
|
||
|
|
- 嵌套過多的玻璃效果 — 這會降低效能並干擾視覺清晰度。
|
||
|
|
- 將玻璃效果套用至每一個視圖 — 應保留給互動元素、工具列與卡片。
|
||
|
|
- 在 UIKit 中使用圓角時忘記設定 `clipsToBounds = true`。
|
||
|
|
- 忽略小工具中的強調渲染模式 — 這會破壞帶有色調的主畫面整體感。
|
||
|
|
- 在玻璃後方使用不透明背景 — 這會抵消半透明的視覺效果。
|
||
|
|
|
||
|
|
## 適用情境
|
||
|
|
|
||
|
|
- 採用全新 iOS 26 設計的導覽列、工具列與分頁標籤列 (Tab bars)。
|
||
|
|
- 懸浮動作按鈕與卡片式容器。
|
||
|
|
- 需要視覺深度與觸控回饋的互動式控制項。
|
||
|
|
- 需與系統 Liquid Glass 外觀整合的小工具。
|
||
|
|
- 相關 UI 狀態之間的形變轉換動畫。
|