SwiftUI 10-构建自定义视图组件

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)

说明:通过 imageNamesize 参数,组件支持不同的图片和尺寸,适合复用。

四、带样式参数的自定义组件

一句话总结:通过样式参数让组件更灵活,支持多种外观。

以下是一个可配置样式的按钮组件:

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,保持复用性和清晰度。

以下是一个用户资料卡组件,组合了 AvatarViewCustomButton,并添加动态交互:

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")

说明:通过复用 AvatarViewCustomButton,并用 @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,同时保持代码清晰和可维护。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容