I'm using Text
view to display some text. I need to limit the line number to 3, and text should be truncated at the end (trail mode). As you could expect, if you add the 2 quotes (opening and closing), then truncate the text, the last one could be truncated too and won't be displayed.
I tried to concatenate 3 Text
(code below) but it will not compile as the lineLimit()
modifier returns a some View
not a Text
.
And finally, using a HStack
is not a good idea as then it will add a blank between the text and the closing quote.
Text(Image(systemName: "quote.opening"))
.font(.caption)
.baselineOffset(4)
+
Text("Some long string"))
.font(.subheadline)
.lineLimit(3)
+
Text(Image(systemName: "quote.closing"))
.font(.caption)
.baselineOffset(4)
It might be possible to implement a precise solution for this problem using TextRenderer
, but it probably won't be easy.
However, an approximate solution can be based on the following approach:
.imageScale(.small)
to the concatenated text, to control the size of the symbols..trailingLastTextBaseline
.So in the case of non-truncated text, the quotes are shown in the right places. In the case of truncated text, the end of the text is covered by the overlay. This is not perfect, but it might be fit for purpose.
Here is how a general purpose view can be implemented this way:
struct QuotedText: View {
let text: String
var lineLimit: Int?
@State private var isTruncated = false
private var fullText: some View {
(
Text(Image(systemName: "quote.opening")) +
Text(text) +
Text(Image(systemName: "quote.closing"))
)
.imageScale(.small)
}
var body: some View {
fullText
.lineLimit(lineLimit)
.background {
GeometryReader { proxy in
fullText
.fixedSize(horizontal: false, vertical: true)
.hidden()
.onGeometryChange(for: CGFloat.self, of: \.size.height) { height in
isTruncated = height > proxy.size.height
}
}
}
.overlay(alignment: .trailingLastTextBaseline) {
if isTruncated {
(
Text("...") +
Text(Image(systemName: "quote.closing"))
)
.imageScale(.small)
.padding(.leading, 10)
.background {
LinearGradient(
colors: [.clear, Color(.systemBackground)],
startPoint: .leading,
endPoint: UnitPoint(x: 0.3, y: 0.5)
)
}
}
}
}
}
Example use:
VStack(spacing: 20) {
QuotedText(text: "Some short text")
QuotedText(text: loremIpsum, lineLimit: 3)
.font(.subheadline)
QuotedText(text: "The quick brown fox jumps over the lazy dog", lineLimit: 2)
.font(.title)
}
.multilineTextAlignment(.leading)
.padding()