I have a dynamic text, it can be small or large I what by default show only 3 lines and only if needed add "more" button. When the user tap on this button ("More") - I will show all test.
I ask, how to know in SwiftUI if text it more of 3 lines or not?
To force a minimum number of lines to a text view, you append a new line character ( \n ) to your text.
Text in SwiftUI is a view that lets you display one or more lines of text. This is suitable for read-only information that's not editable. To display a line of text, you initialize Text and set a String value.
You can do this via the modifier . multilineTextAlignment(. center) . Save this answer.
You can use a GeometryReader
to determine the width of the text field and then use that together with information about the font to calculate the size of the bounding rect that would be required to show the entire text. If that height exceeds the text view then we know that the text has been truncated.
struct LongText: View {
/* Indicates whether the user want to see all the text or not. */
@State private var expanded: Bool = false
/* Indicates whether the text has been truncated in its display. */
@State private var truncated: Bool = false
private var text: String
init(_ text: String) {
self.text = text
}
private func determineTruncation(_ geometry: GeometryProxy) {
// Calculate the bounding box we'd need to render the
// text given the width from the GeometryReader.
let total = self.text.boundingRect(
with: CGSize(
width: geometry.size.width,
height: .greatestFiniteMagnitude
),
options: .usesLineFragmentOrigin,
attributes: [.font: UIFont.systemFont(ofSize: 16)],
context: nil
)
if total.size.height > geometry.size.height {
self.truncated = true
}
}
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text(self.text)
.font(.system(size: 16))
.lineLimit(self.expanded ? nil : 3)
// see https://swiftui-lab.com/geometryreader-to-the-rescue/,
// and https://swiftui-lab.com/communicating-with-the-view-tree-part-1/
.background(GeometryReader { geometry in
Color.clear.onAppear {
self.determineTruncation(geometry)
}
})
if self.truncated {
self.toggleButton
}
}
}
var toggleButton: some View {
Button(action: { self.expanded.toggle() }) {
Text(self.expanded ? "Show less" : "Show more")
.font(.caption)
}
}
}
This is then how it looks for both long and short texts:
Hope this helps.
Building on the excellent work from bhuemer, this version respects SwiftUI's local Font rather than requiring a hard-coded UIFont. Rather than reading the size of the "full" text using String layout, this renders Text three times: once for real, once with a line limit, and once without a line limit. It then uses two GRs to compare the last two.
struct LongText: View {
/* Indicates whether the user want to see all the text or not. */
@State private var expanded: Bool = false
/* Indicates whether the text has been truncated in its display. */
@State private var truncated: Bool = false
private var text: String
var lineLimit = 3
init(_ text: String) {
self.text = text
}
var body: some View {
VStack(alignment: .leading) {
// Render the real text (which might or might not be limited)
Text(text)
.lineLimit(expanded ? nil : lineLimit)
.background(
// Render the limited text and measure its size
Text(text).lineLimit(lineLimit)
.background(GeometryReader { displayedGeometry in
// Create a ZStack with unbounded height to allow the inner Text as much
// height as it likes, but no extra width.
ZStack {
// Render the text without restrictions and measure its size
Text(self.text)
.background(GeometryReader { fullGeometry in
// And compare the two
Color.clear.onAppear {
self.truncated = fullGeometry.size.height > displayedGeometry.size.height
}
})
}
.frame(height: .greatestFiniteMagnitude)
})
.hidden() // Hide the background
)
if truncated { toggleButton }
}
}
var toggleButton: some View {
Button(action: { self.expanded.toggle() }) {
Text(self.expanded ? "Show less" : "Show more")
.font(.caption)
}
}
}
The following shows the behavior with surrounding views. Note that this approach supports LongText(...).font(.largeTitle)
just like a regular Text.
struct ContentView: View {
let longString = "This is very long text designed to create enough wrapping to force a More button to appear. Just a little more should push it over the edge and get us to one more line."
var body: some View {
VStack {
Text("BEFORE TEXT")
LongText(longString).font(.largeTitle)
LongText(longString).font(.caption)
Text("AFTER TEXT")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With