- Published on
如何自定义一个ViewModifier
- Authors
- Name
在使用 SwiftUI 的过程中,对比 UIKit 组织 UI 的方式,最明显的区别感受是,SwiftUI中对 ViewModifier 的链式调用。
我们可以通过在符合 View 协议的视图上使用一连串的 ViewModifier 来改变其外观和进行一些操作。
关于其使用方法,在 Apple 的 SwiftUI 官方文档中已经说得很清楚了
(SwiftUI/ViewModifier)[https://developer.apple.com/documentation/swiftui/viewmodifier]
如何自定义一个 ViewModifier
1.创建一个结构体,实现 ViewModifier 协议
Adopt the ViewModifier protocol when you want to create a reusable modifier that you can apply to any view. The example below combines several modifiers to create a new modifier that you can use to create blue caption text surrounded by a rounded rectangle:
中文翻译:
当您想创建一个可重复使用的修改器,并将其应用于任何视图时,请采用 ViewModifier 协议。下面的示例结合了多个修改器,创建了一个新的修改器,您可以用它来创建由圆角矩形包围的蓝色标题文本:
struct BorderedCaption: ViewModifier {
func body(content: Content) -> some View {
content
.font(.caption2)
.padding(10)
.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(lineWidth: 1)
)
.foregroundColor(Color.blue)
}
}
2.(可选)为 View 添加扩展方法,方便调用,这样可以像调用内置修饰器一样使用你的自定义修饰器。
You can apply modifier(:) directly to a view, but a more common and idiomatic approach uses modifier(:) to define an extension to View itself that incorporates the view modifier:
中文翻译:
您可以直接对视图应用 modifier(:),但更常见、更习惯的方法是使用 modifier(:) 来定义一个包含视图修改器的 View 扩展:
extension View {
func borderedCaption() -> some View {
modifier(BorderedCaption())
}
}
3.像使用内置调用器一样使用自定义修饰器
You can then apply the bordered caption to any view, similar to this:
Image(systemName: "bus")
.resizable()
.frame(width:50, height:50)
Text("Downtown Bus")
.borderedCaption()

代码解释
我在自定义的过程中,有一些比较疑惑的点
我们先看下 ViewModifier 协议的定义
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@MainActor @preconcurrency public protocol ViewModifier {
/// The type of view representing the body.
associatedtype Body : View
/// Gets the current body of the caller.
///
/// `content` is a proxy for the view that will have the modifier
/// represented by `Self` applied to it.
@ViewBuilder @MainActor @preconcurrency func body(content: Self.Content) -> Self.Body
/// The content view type passed to `body()`.
typealias Content
}
@MainActor 表明该协议和方法必须在主线程(Main Actor 环境)中执行
@preconcurrency Swift 5.5引入的并发相关注解,用于在引入严格并发检查之前,允许该协议在非严格并发环境中使用,以向后兼容旧代码。
associatedtype 定义了一个关联类型
Body
,其必须符合View
类型协议,作为方法func body(content: Self.Content) -> Self.Body
的返回类型,Body
表示修饰器应用后返回的视图类型,也就是最终的视图内容。typealias 定义了一个类型别名
Content
,表示传递给body
方法的输入视图类型。Content
是修饰器将要修改的原始视图的类型。@ViewBuilder @MainActor @preconcurrency func body(content: Self.Content) -> Self.Body
遵循ViewModifier
协议的类型(即我们自定义的 ViewModifier) 必须要实现的方法。
知识点
@ViewBuilder 是否真正了解和掌控使用
Anwser:@ViewBuilder
:SwiftUI的DSL(领域特定语言)特性,允许在方法体内以声明式语法构建视图,自动将多个视图组合成单一视图。为什么只需要实现其定义的方法就可以了,对于 Body 和 Content 都不需要定义呢?
Anwser:不论是 Body 还是 Content 都会进行自动类型推断。因此我们只需要实现body
方法即可。就像我们定义一个字符串一样:
let name = "duke"
会进行自动类型推断
- 为什么在
func body(content: Self.Content) -> Self.Body
中 入参中的 content 在类型是 Self.Content ,其采用typealias
定义。 而返回结果 Self.Body 采用associatedtype
定义。
typealias
用于为现有类型创建别名,固定绑定,编译时确定。 适合简化类型名称或提高可读性,无需动态类型选择。 在ViewModifier中,typealias Content表示输入视图,灵活且由上下文推断。
associatedtype
用于协议中定义抽象类型,动态绑定,需实现者指定类型。 适合协议中需要灵活类型关系并施加约束的场景。 在ViewModifier中,associatedtype Body : View
表示返回视图需符合View约束。
本质区别typealias
是静态的类型重命名,associatedtype
是动态的类型占位符。 typealias
无约束能力,associatedtype
支持协议约束和动态类型绑定。