ViewModifier
是什么
把一组样式或 UI 结构打包成可复用的东西,用 .modifier() 链式调用贴到任意 View 上。
类比 UIKit
UIKit 里你会封装一个函数来复用样式:
swift
func styleToolButton(_ button: UIButton) {
button.titleLabel?.font = .systemFont(ofSize: 25)
button.setTitleColor(.white, for: .normal)
button.frame.size = CGSize(width: 30, height: 30)
}
ViewModifier 干的是同一件事,但它不只能改属性,还能在原有 View 外面包一层新的 View 结构,这是普通函数做不到的:
swift
struct BadgeModifier: ViewModifier {
func body(content: Content) -> some View {
ZStack(alignment: .topTrailing) {
content // 原来的 View 原封不动
Text("99")
.background(Color.red)
.clipShape(Circle())
.offset(x: 10, y: -10)
}
}
}
Image(systemName: "bell").modifier(BadgeModifier())
Image(systemName: "message").modifier(BadgeModifier())
本质
本质就是一个语法糖,功能上等价于自定义一个 View 然后把其他 View 塞进去,但它能融入 SwiftUI 的链式调用语法,用起来跟
.font().foregroundColor()一模一样。
圆角 + 渐变色 + 描边
是什么
SwiftUI 没有 UIKit 那样直接设置 layer.borderWidth 的属性,填充和描边需要两个图层叠加来实现。
类比 UIKit
UIKit 两行搞定:
swift
view.layer.borderWidth = 4
view.layer.borderColor = UIColor.green.cgColor
SwiftUI 必须用 ZStack 叠两个 RoundedRectangle:
swift
.background(
ZStack {
RoundedRectangle(cornerRadius: 20)
.stroke(model.color, style: StrokeStyle(lineWidth: 4))
RoundedRectangle(cornerRadius: 20)
.fill(gradientStyle)
}
)
为什么先 stroke 再 fill
stroke(描边)默认居中描边,线宽一半在内一半在外。fill 只填充内部区域,所以 fill 会覆盖 stroke 内侧的那一半。先画 stroke 再盖 fill,能让 stroke 外侧的一半露出来,边框视觉上更完整。反过来的话内侧边框线被盖住,边框显得细一半。
本质
这块 UIKit 确实更直观,SwiftUI 的声明式思路在这个场景下反而绕了一圈
本质
SwiftUI 没有 layer,只有 Shape + 绘制规则
fill 和 stroke 的区别
| 操作 | 本质 |
|---|---|
| fill | 填充 Shape 内部 |
| stroke | 沿路径画边 |
stroke 的问题
less
.stroke(lineWidth: 4)
👉 描边在路径两侧(内 + 外)
推荐方案(更精准)
less
.strokeBorder(lineWidth: 4)
👉 描边完全在内部
推荐结构
scss
.fill(...)
.overlay(stroke)
👉 语义清晰:先填充,再叠加边框
ViewModifier
本质是
(View) -> View,不是修改 View,而是生成新 View
Modifier 顺序
顺序不是语法问题,而是 View 树结构
描边本质
边框不是属性,而是绘制结果(Shape + stroke)