SwiftUI 通过组合小视图构建复杂 UI,自定义视图组件是提升代码复用性和可维护性的关键。本章将带你学习如何创建自定义组件,掌握参数传递、样式抽象和数据绑定。
一、为什么要自定义视图组件?
自定义组件就像“UI 积木”,可以将重复的界面元素封装为可复用的模块,带来以下好处:
- 复用性:在多个界面中使用相同的组件,减少重复代码。
- 可读性:分离视图逻辑,让主界面更清晰。
- 易维护:小组件易于调试和更新,适合团队协作。
二、什么是自定义视图组件?
SwiftUI 的视图是遵循 View
协议的结构体,自定义组件就是将一组 UI 元素和逻辑封装成一个 View
,通过参数和绑定实现灵活性和交互性。
三、创建基本自定义组件
一句话总结:封装简单的 UI 元素,通过参数控制外观。
以下是一个用户头像组件,支持图片和大小参数:
struct AvatarView: View {
let imageName: String
let size: CGFloat
var body: some View {
Image(imageName)
.resizable()
.scaledToFill()
.frame(width: size, height: size)
.clipShape(Circle())
.overlay(Circle().stroke(Color.blue, lineWidth: 2))
.shadow(radius: 3)
}
}
使用方式:
AvatarView(imageName: "user01", size: 60)
说明:通过 imageName
和 size
参数,组件支持不同的图片和尺寸,适合复用。
四、带样式参数的自定义组件
一句话总结:通过样式参数让组件更灵活,支持多种外观。
以下是一个可配置样式的按钮组件:
struct ButtonStyleConfig {
let backgroundColor: Color
let foregroundColor: Color
let font: Font
let cornerRadius: CGFloat
static let primary = ButtonStyleConfig(
backgroundColor: .blue,
foregroundColor: .white,
font: .headline,
cornerRadius: 10
)
static let secondary = ButtonStyleConfig(
backgroundColor: .gray,
foregroundColor: .black,
font: .subheadline,
cornerRadius: 8
)
}
struct CustomButton: View {
let title: String
let style: ButtonStyleConfig
let action: () -> Void
var body: some View {
Button(action: action) {
Text(title)
.font(style.font)
.foregroundColor(style.foregroundColor)
.padding()
.frame(maxWidth: .infinity)
.background(style.backgroundColor)
.cornerRadius(style.cornerRadius)
}
}
}
使用方式:
CustomButton(title: "提交", style: .primary) {
print("提交按钮点击")
}
CustomButton(title: "取消", style: .secondary) {
print("取消按钮点击")
}
说明:通过 ButtonStyleConfig
封装样式,组件支持多种外观配置,预定义样式(如 .primary
)简化使用。
五、支持绑定数据的自定义组件
一句话总结:用 @Binding
实现子视图与父视图的数据同步。
以下是一个开关行组件,允许父视图通过 @Binding
控制开关状态:
struct ToggleRow: View {
let title: String
@Binding var isOn: Bool
var body: some View {
HStack {
Text(title)
Spacer()
Toggle("", isOn: $isOn)
.labelsHidden()
}
.padding()
}
}
使用方式:
struct ParentView: View {
@State private var wifiEnabled = true
var body: some View {
ToggleRow(title: "Wi-Fi", isOn: $wifiEnabled)
}
}
说明:@Binding
通过 $
传递父视图的状态引用,子视图可以直接修改 wifiEnabled
。
六、可组合的复杂组件
一句话总结:通过嵌套组件构建复杂 UI,保持复用性和清晰度。
以下是一个用户资料卡组件,组合了 AvatarView
和 CustomButton
,并添加动态交互:
struct ProfileCard: View {
let name: String
let image: String
@State private var isFollowing = false
var body: some View {
VStack(spacing: 10) {
AvatarView(imageName: image, size: 80)
Text(name)
.font(.title3)
.fontWeight(.medium)
CustomButton(title: isFollowing ? "取消关注" : "关注", style: isFollowing ? .secondary : .primary) {
isFollowing.toggle()
print("关注状态:\(isFollowing)")
}
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(12)
.shadow(radius: 3)
}
}
使用方式:
ProfileCard(name: "Alice", image: "user01")
说明:通过复用 AvatarView
和 CustomButton
,并用 @State
管理关注状态,组件实现了动态交互。
七、动态列表组件
一句话总结:用自定义组件处理动态数据,适合列表等场景。
以下是一个任务列表项组件,结合 @Binding
管理任务状态:
struct TaskItem: View {
let task: String
@Binding var isCompleted: Bool
var body: some View {
HStack {
Text(task)
.strikethrough(isCompleted)
Spacer()
Toggle("", isOn: $isCompleted)
.labelsHidden()
}
.padding()
.background(isCompleted ? Color(.systemGray5) : Color.white)
.cornerRadius(8)
}
}
struct TaskListView: View {
@State private var tasks = [
(name: "学习 SwiftUI", isCompleted: false),
(name: "完成作业", isCompleted: true)
]
var body: some View {
List {
ForEach($tasks.indices, id: \.self) { index in
TaskItem(task: tasks[index].name, isCompleted: $tasks[index].isCompleted)
}
}
}
}
说明:TaskItem
作为列表项组件,结合 @Binding
实现任务完成状态的动态更新。
八、预览多个样式
一句话总结:用 PreviewProvider
测试组件在不同配置下的效果。
以下是 CustomButton
的多样式预览:
struct CustomButton_Previews: PreviewProvider {
static var previews: some View {
Group {
CustomButton(title: "确认", style: .primary, action: {})
.previewLayout(.sizeThatFits)
.padding()
.previewDisplayName("Primary Button")
CustomButton(title: "取消", style: .secondary, action: {})
.previewLayout(.sizeThatFits)
.padding()
.previewDisplayName("Secondary Button")
CustomButton(title: "暗黑模式", style: .primary, action: {})
.previewLayout(.sizeThatFits)
.padding()
.preferredColorScheme(.dark)
.previewDisplayName("Dark Mode")
}
}
}
说明:
-
.previewLayout(.sizeThatFits)
让预览紧贴组件大小。 -
.previewDisplayName
为预览命名,便于区分。 -
.preferredColorScheme(.dark)
测试暗黑模式效果。
九、小结
自定义视图组件是 SwiftUI 开发的核心技巧,以下是关键点:
-
封装逻辑:用
View
协议封装可复用的 UI 模块。 - 灵活传参:通过参数控制样式和行为,支持静态和动态数据。
-
双向绑定:用
@Binding
实现子视图与父视图的数据同步。 -
多样式预览:用
PreviewProvider
测试不同配置,提升开发效率。 -
设计原则:
- 单一职责:每个组件专注一个功能(如按钮、头像)。
- 高内聚低耦合:组件逻辑独立,减少外部依赖。
- 样式抽象:用结构体或枚举封装样式,增强复用性。
-
动态交互:结合
@State
或@Binding
实现动态 UI。
通过自定义组件,你可以像搭积木一样构建复杂 UI,同时保持代码清晰和可维护。