SwiftUI快速入门指南-Modifier篇

背景

本文帮助有Swift基础的同学,快速入门SwiftUI,基于cursour整理

主要分为四个部分:

1. 什么是 Modifier?

Modifier 是用于修改视图外观和行为的方法。每个 modifier 都会返回一个新的视图。

swift 复制代码
Text("Hello")
    .font(.title)           // 修改字体
    .foregroundColor(.blue) // 修改颜色
    .padding()              // 添加内边距
    .background(.yellow)    // 添加背景

核心概念:

  • ✅ Modifier 不修改原视图,而是创建新视图
  • ✅ 支持链式调用
  • ✅ 顺序很重要!

2. Modifier 分类

A. 文本 Modifier
swift 复制代码
Text("SwiftUI Modifier")
    // 字体
    .font(.title)
    .font(.system(size: 24, weight: .bold, design: .rounded))
    .fontWeight(.semibold)
    
    // 颜色
    .foregroundColor(.blue)
    .foregroundStyle(.red)
    
    // 样式
    .italic()
    .bold()
    .underline()
    .strikethrough()
    .kerning(2)              // 字间距
    .tracking(3)             // 字符间距
    .baselineOffset(5)       // 基线偏移
    
    // 多行
    .lineLimit(3)
    .lineSpacing(8)
    .multilineTextAlignment(.center)
    .truncationMode(.tail)
B. 布局 Modifier
swift 复制代码
VStack {
    Text("布局示例")
}
// 内边距
.padding()
.padding(.horizontal, 20)
.padding(.top, 10)
.padding(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20))

// 尺寸
.frame(width: 200, height: 100)
.frame(minWidth: 100, maxWidth: .infinity)
.frame(maxHeight: 300)

// 对齐
.frame(width: 300, height: 200, alignment: .topLeading)

// 偏移
.offset(x: 10, y: 20)

// 位置
.position(x: 100, y: 100)
C. 背景和边框 Modifier
swift 复制代码
Text("样式示例")
    // 背景
    .background(.blue)
    .background(Color.blue.opacity(0.3))
    .background(
        LinearGradient(
            colors: [.blue, .purple],
            startPoint: .leading,
            endPoint: .trailing
        )
    )
    
    // 边框
    .border(.red, width: 2)
    
    // 圆角边框
    .cornerRadius(10)
    .clipShape(RoundedRectangle(cornerRadius: 15))
    .clipShape(Circle())
    
    // 描边
    .overlay(
        RoundedRectangle(cornerRadius: 10)
            .stroke(.red, lineWidth: 2)
    )
D. 阴影和效果 Modifier
swift 复制代码
Text("效果示例")
    // 阴影
    .shadow(radius: 5)
    .shadow(color: .gray, radius: 10, x: 5, y: 5)
    
    // 模糊
    .blur(radius: 3)
    
    // 透明度
    .opacity(0.8)
    
    // 旋转
    .rotationEffect(.degrees(45))
    .rotation3DEffect(.degrees(45), axis: (x: 1, y: 0, z: 0))
    
    // 缩放
    .scaleEffect(1.5)
    .scaleEffect(x: 1.2, y: 0.8)
E. 交互 Modifier
swift 复制代码
Text("点击我")
    // 点击
    .onTapGesture {
        print("被点击了")
    }
    .onTapGesture(count: 2) {
        print("双击")
    }
    
    // 长按
    .onLongPressGesture {
        print("长按")
    }
    
    // 拖拽
    .gesture(
        DragGesture()
            .onChanged { value in
                print("拖拽中")
            }
    )
    
    // 禁用
    .disabled(true)
F. 生命周期 Modifier
swift 复制代码
Text("生命周期")
    // 出现
    .onAppear {
        print("视图出现")
    }
    
    // 消失
    .onDisappear {
        print("视图消失")
    }
    
    // 值变化
    .onChange(of: someValue) { oldValue, newValue in
        print("值改变了")
    }
    
    // 任务
    .task {
        await loadData()
    }

3. Modifier 顺序的重要性 ⚠️

这是最容易出错的地方!Modifier 的顺序会产生完全不同的结果。

swift 复制代码
// 示例 1: 先 padding 后 background
Text("Hello")
    .padding(20)        // 先添加内边距
    .background(.blue)  // 背景覆盖整个区域(包括 padding)

// 结果:蓝色背景包含文字和内边距

// 示例 2: 先 background 后 padding
Text("Hello")
    .background(.blue)  // 背景只覆盖文字
    .padding(20)        // 在背景外添加内边距

// 结果:蓝色背景只包含文字,外面有空白

// 边框和圆角
Text("示例")
    .padding()
    .background(.blue)
    .cornerRadius(10)    // ✅ 正确:圆角应用到背景
    .border(.red, width: 2)  // 边框在圆角外

Text("示例")
    .padding()
    .cornerRadius(10)    // ❌ 错误:圆角应用到文字(没效果)
    .background(.blue)
    .border(.red, width: 2)
    
// Frame 和 Background
Text("示例")
    .frame(width: 200, height: 100)
    .background(.blue)   // ✅ 蓝色填满整个 frame

Text("示例")
    .background(.blue)   
    .frame(width: 200, height: 100)  // ❌ 蓝色只在文字周围

4. 自定义 Modifier

方法一:使用 ViewModifier 协议
swift 复制代码
// 定义自定义 modifier
struct CardModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(.white)
            .cornerRadius(10)
            .shadow(color: .gray.opacity(0.4), radius: 5, x: 0, y: 2)
    }
}

// 扩展 View 以便于使用
extension View {
    func cardStyle() -> some View {
        self.modifier(CardModifier())
    }
}

// 使用
Text("卡片样式")
    .cardStyle()
方法二:直接扩展 View
swift 复制代码
extension View {
    func primaryButton() -> some View {
        self
            .font(.headline)
            .foregroundColor(.white)
            .padding()
            .frame(maxWidth: .infinity)
            .background(.blue)
            .cornerRadius(10)
    }
}

// 使用
Text("登录")
    .primaryButton()
带参数的自定义 Modifier
swift 复制代码
struct BorderModifier: ViewModifier {
    var color: Color
    var width: CGFloat
    var cornerRadius: CGFloat
    
    func body(content: Content) -> some View {
        content
            .padding()
            .overlay(
                RoundedRectangle(cornerRadius: cornerRadius)
                    .stroke(color, lineWidth: width)
            )
    }
}

extension View {
    func customBorder(
        color: Color = .blue,
        width: CGFloat = 2,
        cornerRadius: CGFloat = 8
    ) -> some View {
        self.modifier(BorderModifier(
            color: color,
            width: width,
            cornerRadius: cornerRadius
        ))
    }
}

// 使用
Text("自定义边框")
    .customBorder(color: .red, width: 3, cornerRadius: 15)

5. 条件 Modifier

swift 复制代码
// 方法一:使用 @ViewBuilder
extension View {
    @ViewBuilder
    func `if`<Transform: View>(
        _ condition: Bool,
        transform: (Self) -> Transform
    ) -> some View {
        if condition {
            transform(self)
        } else {
            self
        }
    }
}

// 使用
Text("条件样式")
    .if(isHighlighted) { view in
        view
            .font(.largeTitle)
            .foregroundColor(.red)
    }
    
// 方法二:三元运算符
Text("示例")
    .foregroundColor(isActive ? .blue : .gray)
    .font(isLarge ? .title : .body)
    
// 方法三:使用 modifier
struct ConditionalModifier: ViewModifier {
    var condition: Bool
    
    func body(content: Content) -> some View {
        if condition {
            content
                .background(.yellow)
                .cornerRadius(10)
        } else {
            content
                .background(.gray)
        }
    }
}

// 使用
Text("条件")
    .modifier(ConditionalModifier(condition: isSpecial))

6. 组合 Modifier 实战示例

swift 复制代码
struct ProfileCard: View {
    @State private var isLiked = false
    
    var body: some View {
        VStack(spacing: 12) {
            // 头像
            Image(systemName: "person.circle.fill")
                .resizable()
                .scaledToFit()
                .frame(width: 80, height: 80)
                .foregroundColor(.blue)
                .clipShape(Circle())
                .overlay(
                    Circle()
                        .stroke(.gray, lineWidth: 2)
                )
                .shadow(radius: 5)
            
            // 名字
            Text("张三")
                .font(.title2)
                .fontWeight(.bold)
            
            // 描述
            Text("iOS 开发工程师")
                .font(.subheadline)
                .foregroundColor(.secondary)
            
            // 按钮
            Button(action: { isLiked.toggle() }) {
                HStack {
                    Image(systemName: isLiked ? "heart.fill" : "heart")
                    Text(isLiked ? "已关注" : "关注")
                }
                .font(.headline)
                .foregroundColor(isLiked ? .red : .white)
                .padding(.horizontal, 20)
                .padding(.vertical, 10)
                .background(isLiked ? .white : .blue)
                .cornerRadius(20)
                .overlay(
                    RoundedRectangle(cornerRadius: 20)
                        .stroke(isLiked ? .red : .blue, lineWidth: 2)
                )
            }
        }
        .padding(20)
        .background(.white)
        .cornerRadius(15)
        .shadow(color: .black.opacity(0.1), radius: 10, x: 0, y: 5)
        .padding()
    }
}

7. 常用 Modifier 组合模板

swift 复制代码
extension View {
    // 卡片样式
    func card() -> some View {
        self
            .padding()
            .background(.white)
            .cornerRadius(12)
            .shadow(color: .gray.opacity(0.3), radius: 8, x: 0, y: 4)
    }
    
    // 主按钮样式
    func primaryButtonStyle() -> some View {
        self
            .font(.headline)
            .foregroundColor(.white)
            .padding()
            .frame(maxWidth: .infinity)
            .background(
                LinearGradient(
                    colors: [.blue, .purple],
                    startPoint: .leading,
                    endPoint: .trailing
                )
            )
            .cornerRadius(10)
            .shadow(radius: 5)
    }
    
    // 输入框样式
    func textFieldStyle() -> some View {
        self
            .padding()
            .background(.gray.opacity(0.1))
            .cornerRadius(8)
            .overlay(
                RoundedRectangle(cornerRadius: 8)
                    .stroke(.gray.opacity(0.5), lineWidth: 1)
            )
    }
    
    // 标签样式
    func tag(color: Color = .blue) -> some View {
        self
            .font(.caption)
            .padding(.horizontal, 12)
            .padding(.vertical, 6)
            .background(color.opacity(0.2))
            .foregroundColor(color)
            .cornerRadius(12)
    }
}

// 使用示例
struct ContentView: View {
    @State private var email = ""
    
    var body: some View {
        VStack(spacing: 20) {
            // 卡片
            VStack {
                Text("用户信息")
                Text("详细内容")
            }
            .card()
            
            // 输入框
            TextField("邮箱", text: $email)
                .textFieldStyle()
            
            // 按钮
            Text("登录")
                .primaryButtonStyle()
            
            // 标签
            HStack {
                Text("热门").tag(color: .red)
                Text("新品").tag(color: .green)
                Text("推荐").tag(color: .blue)
            }
        }
        .padding()
    }
}

8. 高级 Modifier 技巧

A. 环境 Modifier

影响所有子视图:

swift 复制代码
VStack {
    Text("标题")
    Text("副标题")
    Text("内容")
}
.font(.title)          // 所有子视图都使用 title 字体
.foregroundColor(.blue) // 所有子视图都是蓝色
B. 几何读取器配合 Modifier
swift 复制代码
GeometryReader { geometry in
    Text("响应式")
        .frame(width: geometry.size.width * 0.8)
        .position(x: geometry.size.width / 2, y: geometry.size.height / 2)
}
C. 动画 Modifier
swift 复制代码
struct AnimatedView: View {
    @State private var isExpanded = false
    
    var body: some View {
        RoundedRectangle(cornerRadius: isExpanded ? 50 : 10)
            .fill(isExpanded ? .blue : .red)
            .frame(width: isExpanded ? 200 : 100, height: 100)
            .animation(.spring(response: 0.5, dampingFraction: 0.6), value: isExpanded)
            .onTapGesture {
                isExpanded.toggle()
            }
    }
}

9. Modifier 最佳实践

swift 复制代码
✅ 推荐做法
// 1. 提取重复的 modifier 为自定义 modifier
extension View {
    func standardCard() -> some View {
        self
            .padding()
            .background(.white)
            .cornerRadius(10)
            .shadow(radius: 5)
    }
}

// 2. 注意顺序
Text("示例")
    .padding()
    .background(.blue)
    .cornerRadius(10)  // 正确顺序

// 3. 使用语义化命名
extension View {
    func errorStyle() -> some View {
        self.foregroundColor(.red).bold()
    }
    
    func successStyle() -> some View {
        self.foregroundColor(.green).bold()
    }
}

❌ 避免做法
// 1. 避免过长的 modifier 链
Text("Bad")
    .font(.title).foregroundColor(.blue).padding().background(.yellow).cornerRadius(10).shadow(radius: 5).opacity(0.9)
    // 太长了!应该换行

// 2. 避免重复代码
Text("Button 1")
    .padding()
    .background(.blue)
    .cornerRadius(10)

Text("Button 2")
    .padding()
    .background(.blue)
    .cornerRadius(10)
// 应该提取为自定义 modifier

// 3. 避免错误的顺序
Text("Wrong")
    .cornerRadius(10)   // 错误:在 background 之前
    .background(.blue)

总结

Modifier 核心要点:

  • ✅ Modifier 创建新视图,不修改原视图
  • ✅ 顺序非常重要
  • ✅ 支持链式调用
  • ✅ 可以自定义和复用
  • ✅ 使用语义化命名
  • ✅ 注意性能(避免过度嵌套)
相关推荐
xiAo_Ju2 小时前
SwiftUI快速入门指南-关键字篇
swiftui
东坡肘子14 小时前
毕业 30 年同学群:一场 AI 引发的“真假难辨”危机 -- 肘子的 Swift 周报 #112
人工智能·swiftui·swift
非专业程序员5 天前
Swift 多线程读变量安全吗?
swiftui·swift
RickeyBoy5 天前
Swift6 @retroactive:Swift 的重复协议遵循陷阱
swiftui·swift
东坡肘子8 天前
Homebrew 5.0:并行加速、MCP 加持,与 Intel 的最后倒计时 -- 肘子的 Swift 周报 #0111
rust·swiftui·swift
RickeyBoy8 天前
Swift 6 迁移常见 crash: _dispatch_assert_queue_fail
swiftui·swift
非专业程序员9 天前
精读GitHub - swift-markdown-ui
ios·swiftui·swift
Daniel_Coder13 天前
iOS Widget 开发-9:可配置 Widget:使用 IntentConfiguration 实现参数选择
ios·swiftui·swift·widget·intents
YungFan14 天前
SwiftUI-WebView 全面指南
ios·swiftui