ComposingView
概述
文章主要分享SwiftUI Modifier的学习过程,将使用案例的方式进行说明。内容浅显易懂,ComposingView部分调试,不过测试代码是齐全的。如果想要运行结果,可以移步Github下载code -> github案例链接
1、创建和组合自定义视图
SwiftUI的核心之一就是组合,这意味着可以创建许多小视图,然后将他们组合以创建更大、更复杂的视图。这可以大规模的重用视图,这意味着工作量减少了。更好的情况下,组合视图运行时几乎不会造成额外的开销,因此可以随意使用而不用在意性能。这里我复刻一个完整的个人信息的例子。
Employee结构体
人物信息结构体
Swift
struct Employee {
var name: String
var jobTitle: String
var emailAddress: String
var profilePicture: String
}
ProfilePicture头像视图
App中的员工个人资料有头像图片,可以创建一个圆形的视图
Swift
struct ProfilePicture: View {
var imageName: String
var body: some View {
Image(imageName)
.resizable()
.frame(width: 100, height: 100)
.clipShape(Circle())
}
}
EmailAddresss视图
Swift
struct EmailAddress: View {
var address: String
var body: some View {
HStack {
Image(systemName: "envelope")
Text(address)
}
}
}
EmployeeDetail详细信息视图
Swift
struct EmployeeDetail: View {
var employee: Employee
var body: some View {
VStack(alignment: .leading) {
Text(employee.name)
.font(.largeTitle)
.foregroundStyle(.primary)
Text(employee.jobTitle)
.foregroundStyle(.secondary)
EmailAddress(address: employee.emailAddress)
}
}
}
EmployeeView整合视图
创建一个更大的视图,将ProfilePicture与Employee组合,提供整体的员工信息
Swift
struct EmployeeView: View {
var employee: Employee
var body: some View {
HStack {
ProfilePicture(imageName: employee.profilePicture)
EmployeeDetail(employee: employee)
}
}
}
通过分离的结构,可以用很多种方式来展示员工的信息:
- 只显示头像
- 只显示电子邮件
- 只显示员工具体信息
- 显示所有信息
更重要的是,这意味着当涉及到使用这些struct时,主要的内容视图不必担心如何构建这些内容的布局,因为它只包含一个大的视图,所有的这些布局都被融入到较小的视图中。这就意味着我只要在body中创建一个EmployeeView就可以了。
自定义视图的使用
Swift
struct FFCustomView: View {
//构建数据
let employee = Employee(name: "Meta BBLv", jobTitle: "Keep Loving, Keep Living", emailAddress: "metabblv@163.com", profilePicture: "chrysanthemum-tea-thumb")
var body: some View {
EmployeeView(employee: employee)
}
}
调试结果

2、将文本视图组合在一起
SwiftUI文本视图重载了"+"运算符,可以将文本视图组合创建新的文本视图。当需要在视图中使用不同的格式时,可以使每个文本视图都不一样,然后将它们连接在一起形成单个组合文本视图。最方便的是,当使用朗读功能时,VoiceOver会自动将它们识别为一段文本。
Swift
struct FFCustomText: View {
var body: some View {
Text("SwiftUI")
.font(.largeTitle)
+ Text("is")
.font(.headline)
+ Text("awesome")
.font(.footnote)
//创建不同颜色或字体的文本
Text("SwiftUI")
.foregroundStyle(.red)
+ Text("is")
.foregroundStyle(.orange)
.fontWeight(.black)
+ Text("awesome")
.foregroundStyle(.blue)
}
}
调试结果

3、将视图存储为属性
如果有多个视图嵌套在另一个视图中,你可能会发现为其中一些或全部视图创建属性很重要,可以使布局代码更容易,然后,可以在视图代码中內联引用这些属性,可以保证视图结构更清晰。
Swift
struct FFViewStoreProperties: View {
let title = Text("metaBBLv").bold()
let subtitle = Text("Author").foregroundStyle(.secondary)
var body: some View {
VStack {
title
subtitle
//像这样,只需要在stack中写入属性名就可以使用了。
//但是更方便的是可以将修饰符附加到这些属性上进行自定义操作。
Divider()
title
.foregroundStyle(.red)
subtitle
//这不会改变标题的基础设定,只是在基础程度上附加的一种方式。
}
}
}
调试结果

4、创建自定义修饰符
如果发现重复的将同一组修饰符附加到视图(比如,背景色、padding、字体等等),那么可以通过创建一个修饰符来封装这些重复的修饰符。如果你想创建自己的结构,那么要遵守ViewModifier协议。并且要实现一个body(content:)函数。
Swift
//创建一个新的PrimaryLabel修饰符,添加padding、background、foregroundcolor和font等
struct PrimaryLabel: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(.red)
.foregroundStyle(.white)
.font(.largeTitle)
}
}
struct FFCustomModifiers: View {
var body: some View {
Text("Hello, SwiftUI")
.modifier(PrimaryLabel())
}
}
调试结果

5、包装自定义UIView
尽管SwiftUI在提供许多UIKit的UIView子类方面做的很好,但目前还没有全部具备,可以为想要的UIView创建自定义包装器。为UITextView创建SwiftUI包装器作为副文本编辑器的基础,分为四个步骤:
- 创建符合UIViewRepresentable的结构体
- 定义一个属性来存储正在使用的文本字符串
- 给它一个makeUIView()方法,范围TextView
- 添加updateUIView()方法,每当TextView的数据发生更改时都会调用该方法。
Swift
struct TextView: UIViewRepresentable {
@Binding var text: NSMutableAttributedString
func makeUIView(context: Context) -> some UIView {
UITextView()
}
func updateUIView(_ uiView: UIViewType, context: Context) {
uiView.accessibilityAttributedLabel = text
}
}
struct FFCustomViewWrap: View {
@State private var text = NSMutableAttributedString(string: "")
var body: some View {
//在SwiftUI视图中使用TextView组件
TextView(text: $text)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}
调试结果

6、如何为UIViewRepresentable结构创建修饰符
将UIView包装在UIViewRepresentable结构中是将现有UIKit引入SwiftUI工程的方式,甚至可以添加自定义修饰符来调整视图在运行时的状态。
要实现这个需求,应该要在底层UIView上调整的所有值创建私有属性,然后创建方法来调整它们,这些方法中的每一个都可以获取SwiftUI可表示副本(而不是底层UIView),然后调整自己创建的私有属性来更新状态。
完成后,SwiftUI将确保触发updateUIView()函数,此时你将私有属性复制到UIView中以确保更新。
SearchField
创建一个UIViewRepresentable将UISearchBar桥接到SwiftUI,但你可能希望它的某些方面是可定制的,例如她的占位符文本。首先,创建可表示对象,并为其占位符添加一个额外的私有属性。
Swift
struct SearchField: UIViewRepresentable {
@Binding var text: String
private var placeholder = ""
init(text: Binding<String>) {
_text = text
}
func makeUIView(context: Context) -> some UIView {
let searchBar = UISearchBar()
searchBar.placeholder = placeholder
return searchBar
}
func updateUIView(_ uiView: UIViewType, context: Context) {
let view: UISearchBar = uiView as! UISearchBar
view.text = text
view.placeholder = placeholder
}
}
创建修饰符调整私有属性
Swift
extension SearchField {
func placeholder(_ string: String) -> SearchField {
var view = self
view.placeholder = string
return view
}
}
使用自定义的修饰符
使用placeholder()修饰符创建了一个SearchField视图,但每次单击按钮时,会随机化占位符
Swift
struct FFCreateModifiers: View {
@State private var text = ""
@State private var placeHolder = "Hello, wrold"
var body: some View {
VStack {
SearchField(text: $text)
.placeholder(placeHolder)
//每次按下时随机更换占位符
Button("Tap me") {
placeHolder = UUID().uuidString
}
}
}
}
调试结果

7、如何在文本中插入图像
SwiftUI可以使用"+"来组合文本视图,也可以使用简单的文本初始值设定项将图像直接放到文本中。
Swift
struct FFTextInsertImage: View {
var body: some View {
//在Helloworld中添加一个星星icon
Text("Hello ") + Text(Image(systemName: "star")) + Text(" World!")
//文本中的图像将自动调整以匹配添加的修饰符(字体、颜色等),要用括号将链接的内容扩起来,以确保将修饰符应用于整个链接的文本。要不只能修饰最后一个Text
(Text("Hello ") + Text(Image(systemName: "star")) + Text(" World!"))
.font(.largeTitle)
.foregroundStyle(.blue)
//如果没有添加额外的括号,则只会修饰最后一个Text("World")
Text("Hello ") + Text(Image(systemName: "star")) + Text(" World!")
.font(.largeTitle)
.foregroundStyle(.blue)
}
}
调试结果
