这一节的内容,很简单,但却足够实用,因此,这是一个性价比很高的知识。在构建 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())
  }
}

buildTitlebuildSummary 的实现里可以看到:

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 的用法。

所有订阅均支持 12 期免息分期

¥ 59

按月订阅

一个月,观看并下载所有视频内容。初来泊学,这可能是个最好的开始。

开始订阅

¥ 512

按年订阅

一年的时间,让我们一起疯狂地狩猎知识吧。比按月订阅优惠 28%

开始订阅

¥ 1280

泊学终身会员

永久观看和下载所有泊学网站视频,并赠送 100 元商店优惠券。

我要加入
如需帮助,欢迎通过以下方式联系我们