-
-
Save Catherine-K-George/3bffbf1433b34b630b7bc572d6af9f6d to your computer and use it in GitHub Desktop.
import UIKit | |
enum TrailingContent { | |
case readmore | |
case readless | |
var text: String { | |
switch self { | |
case .readmore: return "...Read More" | |
case .readless: return " Read Less" | |
} | |
} | |
} | |
extension UILabel { | |
private var minimumLines: Int { return 4 } | |
private var highlightColor: UIColor { return .red } | |
private var attributes: [NSAttributedString.Key: Any] { | |
return [.font: self.font ?? .systemFont(ofSize: 18)] | |
} | |
public func requiredHeight(for text: String) -> CGFloat { | |
let label = UILabel(frame: CGRect(x: 0, y: 0, width: frame.width, height: CGFloat.greatestFiniteMagnitude)) | |
label.numberOfLines = minimumLines | |
label.lineBreakMode = NSLineBreakMode.byTruncatingTail | |
label.font = font | |
label.text = text | |
label.sizeToFit() | |
return label.frame.height | |
} | |
func highlight(_ text: String, color: UIColor) { | |
guard let labelText = self.text else { return } | |
let range = (labelText as NSString).range(of: text) | |
let mutableAttributedString = NSMutableAttributedString.init(string: labelText) | |
mutableAttributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: color, range: range) | |
self.attributedText = mutableAttributedString | |
} | |
func appendReadmore(after text: String, trailingContent: TrailingContent) { | |
self.numberOfLines = minimumLines | |
let fourLineText = "\n\n\n" | |
let fourlineHeight = requiredHeight(for: fourLineText) | |
let sentenceText = NSString(string: text) | |
let sentenceRange = NSRange(location: 0, length: sentenceText.length) | |
var truncatedSentence: NSString = sentenceText | |
var endIndex: Int = sentenceRange.upperBound | |
let size: CGSize = CGSize(width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude) | |
while truncatedSentence.boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attributes, context: nil).size.height >= fourlineHeight { | |
if endIndex == 0 { | |
break | |
} | |
endIndex -= 1 | |
truncatedSentence = NSString(string: sentenceText.substring(with: NSRange(location: 0, length: endIndex))) | |
truncatedSentence = (String(truncatedSentence) + trailingContent.text) as NSString | |
} | |
self.text = truncatedSentence as String | |
self.highlight(trailingContent.text, color: highlightColor) | |
} | |
func appendReadLess(after text: String, trailingContent: TrailingContent) { | |
self.numberOfLines = 0 | |
self.text = text + trailingContent.text | |
self.highlight(trailingContent.text, color: highlightColor) | |
} | |
} |
Hi.
I liked the simplicity and clarity of your solution. But I'm just a beginner and a little confused. It seems that I do everything just like you, but I don't understand how the action is called. My label just doesn't respond to clicks. How does the setup work highlight "read more" and "read less" on tap?
Can I see your test(demo) project?
Hi,
You've to add a tap gesture to the label to catch the tap and an extension UITapGesture to know which part of the label has tapped.
Step 1: Enable UILabel user interaction.
textLabel.isUserInteractionEnabled = true
Step 2: Add Tap gesture to UILabel and set action
@IBAction func didTapLabel(_ sender: UITapGestureRecognizer) { }
Step 3: Add an extension UITapGestureRecognizer to identify which part of the label has tapped, readmore/readless.
extension UITapGestureRecognizer {
func didTap(label: UILabel, inRange targetRange: NSRange) -> Bool {
// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
let textStorage = NSTextStorage(attributedString: label.attributedText!)
// Configure layoutManager and textStorage
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
// Configure textContainer
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = label.lineBreakMode
textContainer.maximumNumberOfLines = label.numberOfLines
let labelSize = label.bounds.size
textContainer.size = labelSize
// Find the tapped character location and compare it to the specified range
let locationOfTouchInLabel = self.location(in: label)
let textBoundingBox = layoutManager.usedRect(for: textContainer)
let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y)
let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x, y: locationOfTouchInLabel.y - textContainerOffset.y)
let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return NSLocationInRange(indexOfCharacter, targetRange)
}
}
Step 4: Handle the readmore/readless in tap action
@IBAction func didTapLabel(_ sender: UITapGestureRecognizer) {
guard let text = textLabel.text else { return }
let readmore = (text as NSString).range(of: TrailingContent.readmore.text)
let readless = (text as NSString).range(of: TrailingContent.readless.text)
if sender.didTap(label: textLabel, inRange: readmore) {
textLabel.appendReadLess(after: textDescription, trailingContent: .readless)
} else if sender.didTap(label: textLabel, inRange: readless) {
textLabel.appendReadmore(after: textDescription, trailingContent: .readmore)
} else { return }
}
Try this!
https://github.com/Catherine-K-George/Readmore-Readless.git
guard let text = descLbl.text else { return }
let readmore = (text as NSString).range(of: TrailingContent.readmore.text)
let readless = (text as NSString).range(of: TrailingContent.readless.text)
print("check Sender CLick" ,sender.didTap(label: descLbl, inRange: readmore))
if sender.didTap(label: descLbl, inRange: readmore) {
descLbl.appendReadLess(after: textDescription, trailingContent: .readless)
} else if sender.didTap(label: descLbl, inRange: readless) {
descLbl.appendReadmore(after: textDescription, trailingContent: .readmore)
} else { return }
its not working for me , please alaborated in storyboard and other configuration
let textDescription: String = "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."
To collapase content with Readmore
textLabel.appendReadmore(after: textDescription, trailingContent: .readmore)
To expand content with ReadLess
textLabel.appendReadLess(after: textDescription, trailingContent: .readless)