Skip to content

Instantly share code, notes, and snippets.

@prafullakumar
Created February 27, 2021 07:54
Show Gist options
  • Save prafullakumar/80a33e8c512d3eac5e6b1b72d2f5d330 to your computer and use it in GitHub Desktop.
Save prafullakumar/80a33e8c512d3eac5e6b1b72d2f5d330 to your computer and use it in GitHub Desktop.
import SwiftUI
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
private var moreLessText: String {
if !truncated {
return ""
} else {
return self.expanded ? " read less" : " ... read more"
}
}
init(_ text: String, lineLimit: Int, font: UIFont = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)) {
self.text = text
self.lineLimit = lineLimit
_shrinkText = State(wrappedValue: text)
self.font = font
}
var body: some View {
ZStack(alignment: .bottomLeading) {
Group {
Text(self.expanded ? text : shrinkText)
+ Text(moreLessText)
.bold()
.foregroundColor(.blue)
}.animation(.easeInOut)
.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)
})
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
VStack(alignment: .leading, spacing: 10) {
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)
ExpandableText("Small text", lineLimit: 3)
ExpandableText("Render the limited text and measure its size, R", lineLimit: 1)
ExpandableText("Create a ZStack with unbounded height to allow the inner Text as much, Render the limited text and measure its size, Hide the background Indicates whether the text has been truncated in its display.", lineLimit: 3)
}.padding()
}
}
@martapaniti
Copy link

Hello, the code not working for smaller devices, as iPhone SE. The read more text is not visible on these devices

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment