Reputation: 424
I want to have a Text
view that is limited to 3 lines.
But, if the text could continue for more than 3 lines, I want to have a button that removes the line limit; this button is hidden otherwise.
In order to do this, I would need some way to know whether the limit has been reached.
Is there any way to do this without doing some hacky stuff with GeometryReader
?
Upvotes: 2
Views: 1582
Reputation: 53121
In iOS 16, you can achieve this using ViewThatFits
, which will pick the largest child view that fits the available space:
struct LineLimitView: View {
let text: String
let limit: Int
@State private var isExpanded = false
@State private var canBeExpanded = false
var body: some View {
VStack {
Text(text)
.lineLimit(isExpanded ? nil : limit)
// Background matches the size of the Text
.background {
// Pick the first child view that fits vertically
ViewThatFits(in: .vertical) {
// This Text has no line limit, so if it's is larger than the
// "outer" Text, ViewThatFits will pick the next view
Text(text)
.hidden()
// Color expands to fill the background, so will always be picked
// if the text above is too large
Color.clear
.onAppear {
canBeExpanded = true
}
}
}
if canBeExpanded {
Button(isExpanded ? "Less" : "More…") {
withAnimation {
isExpanded.toggle()
}
}
}
}
}
}
struct ContentView: View {
let short = "Hello, World! This is a short example text that fits in three lines."
let long = "Hello, World! This is a longer example text that will not fit in three lines. Hello, World! This is a longer example text. Hello, World! This is a longer example text. Hello, World! This is a longer example text."
var body: some View {
VStack(alignment: .leading) {
GroupBox {
LineLimitView(text: short, limit: 3)
}
GroupBox {
LineLimitView(text: long, limit: 3)
}
HStack(alignment: .top) {
LineLimitView(text: short, limit: 4)
LineLimitView(text: long, limit: 4)
}
}
.padding()
}
}
I pinched @ChrisR's ContentView
for this 🦹♂️
Upvotes: 7
Reputation: 12125
Having said that GeometryReader
is just fine to use, here is a solution to your question that uses it:
struct ContentView: View {
let short = "Hello, World! This is a short example text that fits in three lines."
let long = "Hello, World! This is a longer example text that will not fit in three lines. Hello, World! This is a longer example text. Hello, World! This is a longer example text. Hello, World! This is a longer example text."
var body: some View {
VStack(alignment: .leading) {
GroupBox {
LineLimitView(text: short, limit: 3)
}
GroupBox {
LineLimitView(text: long, limit: 3)
}
HStack(alignment: .top) {
LineLimitView(text: short, limit: 4)
LineLimitView(text: long, limit: 4)
}
}
.padding()
}
}
struct LineLimitView: View {
let text: String
let limit: Int
@State private var height = CGFloat.zero
@State private var showAll = false
var body: some View {
VStack(alignment: .leading) {
if showAll {
Text(text)
} else if height < CGFloat(limit) * 21 {
Text(text)
.lineLimit(limit)
} else {
Text(text)
.lineLimit(limit)
Button("Show all") { showAll = true }
}
}
.frame(maxWidth: .infinity, alignment: .leading)
// calculates the height of the text
.background( GeometryReader { geo in
Color.clear.onAppear { height = geo.size.height }
} )
}
}
Upvotes: -1