SwiftUI:Text约束

当显示不同长度的文本时,我们的设计需要明确是否应该有一个最大的可显示行数,或者是否应该始终显示全文。

target.jpg

Text的行为并不总是可以预测的:有时候,文本会在没有明显原因的情况下被截断,尽管我们有足够的处理空间(例如在Form表单和List列表中)。
在本文中,让我们看看如何处理这些场景,以及更多内容。

lineLimit

Text提供了实例方法lineLimit(_:):

// This text instance won't exceed 3 rows.
Text(myString).lineLimit(3)

这个方法保证我们的文本实例不会超过给定的行数。我们也可以传递nil作为lineLimit参数,让Text按照需要取任意多行:

Text(myString).lineLimit(nil)

传递nil意味着Text将遵循它的默认行为,仅此而已:如果SwiftUI认为这样做是正确的,那么我们的Text仍然会被截断。

如果文本长是“5行”,并且我们设置了.linelimit(3),这并不保证Text将接受3行,它只是保证Text不会超过3行。

fixedSize(horizontal:vertical:)

fixedSize让任何视图占用所需的空间,完全忽略了建议的大小。
fixedSize接受两个布尔值,horizontal水平方向的和vertical垂直方向的,让我们决定视图是否应该忽略两个轴建议的大小,只是忽略一个轴,或都不忽略。
fixedSize还提供了一个方便的fixedSize()方法,它等价于fixedSize(horizontal: true, vertical: true)
据我所知,让Text使用.fixedSize是保证文本总是完全显示的唯一方法。

// 这个Text将遵守建议的水平空间大小,并根据需要占据尽可能多的垂直空间
Text(myLongString).fixedSize(horizontal: false, vertical: true)
fixed.png

这里有一个问题:虽然使用.fixedSize可以保证完整的显示全文,但如果没有足够的空间,UI:布局将被破坏:

Rectangle()
  .stroke()
  .frame(width: 200, height: 100)
  .overlay(Text(myLongString).fixedSize(horizontal: false, vertical: true))
broken.png

兼容设置

现在我们已经介绍了主要的两种方法,让我们看看如何回答“如何检查Text是否被截断?”的问题。
与UIKit类似,我们需要知道的是我们文本的内在大小是否与布局中文本的实际大小相同。

为了得到Text的内在大小和实际大小,我们需要在我们的视图层次结构中添加这两种情况,然而我们只想显示这两种情况之一,用隐藏视图的技巧,同时仍然计算它们的布局:

  • 添加它们作为另一个视图的background,背景视图不参与父视图的大小
  • 应用hidden()修饰符,隐藏的视图不是由SwiftUI绘制的
    我们最终的布局:
Text(myString)
  .lineLimit(myLineLimit)
  .background(
    Text(myString)
      .fixedSize(horizontal: false, vertical: true)
      .hidden()
  )

现在我们需要得到两个Text的大小,为此我们将使用readSize:

Text(myString)
  .lineLimit(myLineLimit)
  .readSize { size in
    print("truncated size: \(size)")
  }
  .background(
    Text(myString)
      .fixedSize(horizontal: false, vertical: true)
      .hidden()
      .readSize { size in
        print("intrinsic size: \(size)")
      }
  )

最后,我们可以将这两个值保存在我们的视图中,并随意使用它们:

struct TruncableText: View {
  let text: Text
  let lineLimit: Int?
  @State private var intrinsicSize: CGSize = .zero
  @State private var truncatedSize: CGSize = .zero
  let isTruncatedUpdate: (_ isTruncated: Bool) -> Void

  var body: some View {
    text
      .lineLimit(lineLimit)
      .readSize { size in
        truncatedSize = size
        isTruncatedUpdate(truncatedSize != intrinsicSize)
      }
      .background(
        text
          .fixedSize(horizontal: false, vertical: true)
          .hidden()
          .readSize { size in
            intrinsicSize = size
            isTruncatedUpdate(truncatedSize != intrinsicSize)
          }
      )
  }
}

在每次大小变化时,TruncableText都会调用一个带有最新截断状态的isTruncatedUpdate回调方法,然后可以使用该信息来适应各种情况下的UI。
例如,这里有一个视图,当Text内容被截断时显示“Show All”按钮,当点击时显示全文:

import SwiftUI
struct ContentView: View {
  @State var isTruncated: Bool = false
  @State var forceFullText: Bool = false

  var body: some View {
    VStack {
      if forceFullText {
        text
          .fixedSize(horizontal: false, vertical: true)
      } else {
        TruncableText(
          text: text,
          lineLimit: 3
        ) {
          isTruncated = $0
        }
      }
      if isTruncated && !forceFullText {
        Button("show all") {
          forceFullText = true
        }
      }
    }
    .padding()
  }

  var text: Text {
    Text("Introducing a new kind of fitness experience. One that dynamically integrates your personal metrics from Apple Watch, along with music from your favorite artists, to inspire like no other workout in the world.")
  }
}

struct TruncableText: View {
  let text: Text
  let lineLimit: Int?
  @State private var intrinsicSize: CGSize = .zero
  @State private var truncatedSize: CGSize = .zero
  let isTruncatedUpdate: (_ isTruncated: Bool) -> Void

  var body: some View {
    text
      .lineLimit(lineLimit)
      .readSize { size in
        truncatedSize = size
        isTruncatedUpdate(truncatedSize != intrinsicSize)
      }
      .background(
        text
          .fixedSize(horizontal: false, vertical: true)
          .hidden()
          .readSize { size in
            intrinsicSize = size
            isTruncatedUpdate(truncatedSize != intrinsicSize)
          }
      )
  }
}

public extension View {
    func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
        background(
            GeometryReader { geometryProxy in
                Color.clear
                    .preference(key: SizePreferenceKey.self, value: geometryProxy.size)
            }
        )
        .onPreferenceChange(SizePreferenceKey.self, perform: onChange)
    }
}

private struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
example.gif

总结

在UIKit中发现显示的文本是否被截断并不容易,目前在SwiftUI中也不容易。

你可能感兴趣的:(SwiftUI:Text约束)