I have a very long text and I want to show just 3 lines with more button just like the picture and also a less button when the text is expand. Any idea of how to do it with SwiftUI?

var body: some View{
VStack{
Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
}
}
You can read this Medium article
struct ExpandableText: View {
@State private var expanded: Bool = false
@State private var truncated: Bool = false
@State private var shrinkText: String
private var text: String
let font: UIFont
let lineLimit: Int
init(_ text: String, lineLimit: Int, font: UIFont = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)) {
self.text = text
_shrinkText = State(wrappedValue: text)
self.lineLimit = lineLimit
self.font = font
}
var body: some View {
ZStack(alignment: .bottomLeading) {
Group {
Text(self.expanded ? text : shrinkText) + Text(moreLessText)
.bold()
.foregroundColor(.black)
}
.animation(.easeInOut(duration: 1.0), value: false)
.lineLimit(expanded ? nil : lineLimit)
.background(
// Render the limited text and measure its size
Text(text)
.lineLimit(lineLimit)
.background(GeometryReader { visibleTextGeometry in
Color.clear.onAppear() {
let size = CGSize(width: visibleTextGeometry.size.width, height: .greatestFiniteMagnitude)
let attributes:[NSAttributedString.Key:Any] = [NSAttributedString.Key.font: font]
///Binary search until mid == low && mid == high
var low = 0
var heigh = shrinkText.count
var mid = heigh ///start from top so that if text contain we does not need to loop
///
while ((heigh - low) > 1) {
let attributedText = NSAttributedString(string: shrinkText + moreLessText, attributes: attributes)
let boundingRect = attributedText.boundingRect(with: size, options: NSStringDrawingOptions.usesLineFragmentOrigin, context: nil)
if boundingRect.size.height > visibleTextGeometry.size.height {
truncated = true
heigh = mid
mid = (heigh + low)/2
} else {
if mid == text.count {
break
} else {
low = mid
mid = (low + heigh)/2
}
}
shrinkText = String(text.prefix(mid))
}
if truncated {
shrinkText = String(shrinkText.prefix(shrinkText.count - 2)) //-2 extra as highlighted text is bold
}
}
})
.hidden() // Hide the background
)
.font(Font(font)) ///set default font
///
if truncated {
Button(action: {
expanded.toggle()
}, label: {
HStack { //taking tap on only last line, As it is not possible to get 'see more' location
Spacer()
Text("")
}.opacity(0)
})
}
}
}
private var moreLessText: String {
if !truncated {
return ""
} else {
return self.expanded ? " read less" : " ... read more"
}
}
}
And use this ExpandableText in your view like bellow
ExpandableText("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut laborum", lineLimit: 6)
This answer is a bit of a hack, because it does not truncate the actual string and apply the "..." suffix, which in my humble opinion would be the better engineered solution. That would require the programmer to determine the length of the string that fits within three lines, remove the last two words (to allow for the More/Less button) and apply the "..." suffix.
This solution limits the number of lines shown and literally covers the trailing end of the third line with a white background and the button. But it may be suitable for your case...
@State private var isExpanded: Bool = false
var body: some View{
VStack{
Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
.lineLimit(isExpanded ? nil : 3)
.overlay(
GeometryReader { proxy in
Button(action: {
isExpanded.toggle()
}) {
Text(isExpanded ? "Less" : "More")
.font(.caption).bold()
.padding(.leading, 8.0)
.padding(.top, 4.0)
.background(Color.white)
}
.frame(width: proxy.size.width, height: proxy.size.height, alignment: .bottomTrailing)
}
)
}
}
You can learn how to do this by following Apple's "Introducing SwiftUI" tutorials. Specifically the "Creating a macOS app" tutorial, "Section 9 Build the Detail View".
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