Created
June 6, 2021 09:49
-
-
Save Catherine-K-George/3bffbf1433b34b630b7bc572d6af9f6d to your computer and use it in GitHub Desktop.
UILabel extension with ReadMore/ ReadLess action
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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,
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!
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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?