🔰Configuring Views
Last updated
Last updated
在設計 View 的時候,可以利用不同的方法,來達到同一個目的,如:「利用自製元件、View extension、ViewModifier、ViewBuilder」等。
SwiftUI ⟩ View and Controls ⟩ View ⟩ Styling
SwiftUI ⟩
下面我們展示如何用不同的方式來寫「在 TextField 前加一個圖示」這個動作:
直接用 HStack
寫成一個「元件」:IconTextField
用 View extension:view.prefix(icon:)
用 ViewModifier:PrefixIcon
用 ViewBuilder:IconTextFieldView
import SwiftUI
import PlaygroundSupport
// 表單
struct SignUpForm: View {
@State private var username = ""
@State private var email = ""
@State private var phone = ""
@State private var tel = ""
@State private var calendar = ""
var body: some View {
Form {
// 表頭
Text("表單").font(.headline)
// ⭐️ 1. HStack
HStack {
Image(systemName: "person.circle.fill")
TextField("姓名", text: $username)
.validate(username) { $0.count > 2 } // ⭐️ 使用 Validation
}
// ⭐️ 2. 使用元件 `IconTextField`
IconTextField(
icon : "envelope.circle.fill",
title: "信箱",
text : $email
)
// ⭐️ 3. 使用 `View` extension
TextField("0800-000-123", text: $phone)
.textFieldStyle(RoundedBorderTextFieldStyle()) // ⭐️ 使用 TextFieldStyle
.validate(phone) { $0.count > 2 }
.prefix(icon: "phone.circle.fill") // ⭐️ View extension
// ⭐️ 4. 使用 `ViewModifier`
// ⚠️ 注意:
// 此處先用 PrefixIcon 再用 Validation,
// 所以 Validation 的紅色外框會連「圖示」都框起來!
TextField("(03) 000-1234", text: $tel)
.modifier(PrefixIcon(name: "phone.circle.fill"))// ⭐️ ViewModifier
.validate(tel) { $0.count > 1 }
// ⭐️ 5. ViewBuilder
IconTextFieldView(iconName: "calendar.circle.fill") {
TextField("2020/09/14", text: $calendar)
}
Button(
action: { },
label : { Text("確定") }
)
}.accentColor(.purple)
}
}
// live view
struct ContentView: View {
var body: some View {
VStack {
SignUpForm()
}
}
}
PlaygroundPage.current.setLiveView(ContentView())
// 2. 元件
struct IconTextField: View {
// local properties
var icon : String // icon name for SF Symbols
var title: String // placeholder text for TextField
// property from parent view
@Binding var text: String // text in TextField
// body of this component
var body: some View {
HStack {
Image(systemName: icon)
TextField(title, text: $text)
.textFieldStyle(RoundedBorderTextFieldStyle()) // ⭐️ TextFieldStyle
}
}
}
// 3. `View` extension
extension View {
// view.prefix(icon: "phone.circle.fill")
func prefix(icon name: String) -> some View {
HStack {
Image(systemName: name)
self
}
}
}
// 4. ViewModifier
struct PrefixIcon: ViewModifier {
var name: String // icon name for SF Symbols
// ⭐️ ViewModifier's only requirement
func body(content: Content) -> some View {
HStack {
Image(systemName: name)
content
}
}
}
struct IconTextFieldView<Content: View>: View {
// properties
let iconName: String
let content: Content
// IconTextFieldView(iconName: "...") { ... }
init(
iconName : String,
@ViewBuilder content: () -> Content // ⭐️ ViewBuilder
) {
self.iconName = iconName
self.content = content()
}
// conforms to `View` protocol
var body: some View {
// ⭐️ 在 `content` 前面放一個圖示。
HStack {
Image(systemName: iconName)
content
}
}
}
// ⭐️ Validation (ViewModifier)
struct Validation<Value>: ViewModifier {
var value : Value // 要被檢查的值
var validate: (Value) -> Bool // 負責檢查的函數
func body(content: Content) -> some View {
// 外框的設定
let color: Color = validate(value) ? .clear : .pink
let rounded = RoundedRectangle(cornerRadius: 6) // 1. 外型:圓角
.stroke(color) // 2. 顏色:透明(通過檢查)
// // 紅色(沒通過)
// ⭐️ 「負的 padding」可讓 child view 長得比 parent view 還大
.padding(-4) // 3. 位置:在外面
// draw outline
return content
.overlay(rounded) // ⭐️ 設定圓角外框
.animation(.default) // ⭐️ 設定動畫
}
}
// Validation helper
extension View {
// view.validate(value) { ... }
func validate<Value>(
_ value: Value,
validate: @escaping (Value) -> Bool
) -> some View {
self.modifier(Validation(value: value, validate: validate))
}
}
Configuring SwiftUI Views - Swift by Sundell
View Modifier - 🍎
View Builder - 🍎