这一节的内容,很简单,但却足够实用,因此,这是一个性价比很高的知识。在构建 App 的时候,我们经常会用到一些反复使用的样式,这些样式在 SwiftUI 里,可能是由很多个方面组合起来的,例如,一个标题可能是这样的:
Text("Title")
.foregroundColor(.black)
.font(Font(UIFont.preferredFont(forTextStyle: .title2)))
.lineLimit(1)
.minimumScaleFactor(0.8)
接下来,如果这种“黑色的,title2
字体的,只有一行的,最小缩放级别为 0.8” 的标题用上 10 遍会怎么样呢?最简单的做法,就是把同样的代码 copy 10 遍呗。然后,设计师脑洞大开,说:嘿,我觉得这个样式改成深灰色更好。然后,你只好耐着性子把代码改了 10 遍。这个时候,设计师又说了:我怎么觉得还是黑色更好。这时候,你就该满脸竖线了。
那么,该如何解决这种样式复用的问题呢?其实,SwiftUI 官方提供的做法已经给我们一些提示了。既然可以用 .foregroundColor(.black)
的形式把文字设置成黑色,为什么不能用类似 .blackTitle2withMinimum(0.8)
这样的形式一次性把文字设置成我们需要的状态呢?
答案是当然可以。在 SwiftUI 里,这种连接在 View
后面设置属性的东西,都是实现了 ViewModifier
协议的类型。并且,SwiftUI 给我们留了口子,还可以进行自定义。这里,我们就用设置学习路径卡片上的文字属性举例,来看看如何复用更复杂的样式。
当前,App 中用到的所有 Text
样式,都定义在 BoxueUIKit / Resuable / SwiftUI / Text+Ex.swift 里。为了实现 ViewModifier
,我们定义一个和 View
中类似的 body
方法就好了。例如,定义浅色封面标题的样式是这样的:
struct LearningPathTitleLightStyle: ViewModifier {
func body(content: Content) -> some View {
content
.foregroundColor(.black)
.font(Font(UIFont.preferredFont(forTextStyle: .title2)))
.lineLimit(1)
.minimumScaleFactor(0.8)
}
}
这里,Content
就是我们要设置属性的 View
,在 body
的实现里,直接把我们需要组合的样式都应用到 content
这个替代符上,并把结果返回就好了。理解了这个思路之后,我们用相同的方法分别定义了深浅两种学习路径卡片上,标题和简介文字的样式,以及稍后会用到的一些 Tag 文字的样式,这里就不一一列举了,大家自己去 GitHub 上看代码就好。
接下来,为了更好地使用这些扩展出来的文字样式,我们给 Text
定义一个扩展:
extension Text {
func textStyle<Style: ViewModifier>(_ style: Style) -> some View {
ModifiedContent(content: self, modifier: style)
}
}
它接受一个实现了 ViewModifier
的对象,也就是我们刚定义的样式作为参数。然后,使用 ModifiedContent
方法,把 style
应用到 Text
对象上。
DarkLearningPathCoverWidget
完成后,我们用暗色学习路径卡片举例,来看看自定义 ViewModifier
的用法:
public struct DarkLearningPathCoverWidget: LearningPathWidget {
@Binding var learningPath: LearningPath
public init(learningPath: Binding<LearningPath>) {
self._learningPath = learningPath
}
public func buildTitle() -> some View {
Text(learningPath.title)
.textStyle(LearningPathTitleDarkStyle())
}
public func buildSummary() -> some View {
Text(learningPath.summary)
.textStyle(LearningPathSummaryDarkStyle())
}
}
在 buildTitle
和 buildSummary
的实现里可以看到:
Text(learningPath.title)
.textStyle(LearningPathTitleDarkStyle())
这种形式,要比我们在 Text
后面串联上一大串设置各种属性的 ViewModifier
简单和清晰多了,并且,一旦需要对这些样式进行调整,也只需要修改一次,很方便。至于浅色的学习路径卡片,道理是一样的,我们就不重复了。
What's next?
下一节,我们要聊聊 SwiftUI 中和尺寸有关的话题,尽管 Apple 在 WWDC 上说过,SwiftUI 中每个元素会拥有它们自然的尺寸,无需我们关心具体的数值。但对现在这个版本的 SwiftUI 来说,这也只有在我们学习的阶段是成立的。
而在现实开发中,我们有太多情况需要知道一个 UI 组件的具体尺寸。但当你去探索 SwiftUI 中和这方面相关的 API 文档时,得到最多的答案,应该就是:No overview available。
因此,接下来,我们就用几小节的内容,结合泊学中特定 UI 的实现方法,聊聊 SwiftUI 中和 UI 尺寸相关的一些重要 API 的用法。