我目前正在开发一个 iOS 应用程序,并面临 UILabel 的挑战。 我希望实现两个主要目标:
现在我做了一个计算逻辑如下: 但计算时间太长,所以屏幕有点闪烁。
import UIKit
extension UILabel {
func getMaxHeight(font: UIFont, width: CGFloat) -> CGFloat {
let maxSize = CGSize(width: width, height: CGFloat(MAXFLOAT))
let text = (self.text ?? "") as NSString
let textHeight = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil).height
return textHeight
}
func getNumberOfLines(font: UIFont, width: CGFloat) -> Int {
let maxSize = CGSize(width: width, height: CGFloat(MAXFLOAT))
let text = (self.text ?? "") as NSString
let textHeight = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil).height
let lineHeight = font.lineHeight
return Int(ceil(textHeight / lineHeight))
}
private func getNumberOfLines(text: String, font: UIFont, width: CGFloat) -> Int {
let maxSize = CGSize(width: width, height: CGFloat(MAXFLOAT))
let text = text as NSString
let textHeight = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil).height
let lineHeight = font.lineHeight
return Int(ceil(textHeight / lineHeight))
}
func replaceEllipsis(with string: String, font: UIFont, width: CGFloat, maxLine: Int, highlight: String? = nil) {
guard let text = self.text else { return }
lineBreakMode = .byClipping
if self.getNumberOfLines(font: font, width: width) <= maxLine {
return
}
let totalNumberOfLine = getNumberOfLines(text: text, font: font, width: width)
let charArray: [Character] = text.map { $0 }
let chunkSize = charArray.count / totalNumberOfLine
var resultArray: [String] = []
for i in 0..<totalNumberOfLine {
let startIndex = i * chunkSize
let endIndex = min((i + 1) * chunkSize, charArray.count)
let chunk = charArray[startIndex..<endIndex]
let chunkString = chunk.reduce("") { partialResult, char in
return partialResult + "\(char)"
}
resultArray.append(chunkString)
}
let textForTwo = resultArray[0..<maxLine+1].joined()
var estimatedText = textForTwo
var estimatedLine: Int = 10
repeat {
estimatedText.removeLast()
self.text = estimatedText
self.text?.append(string)
estimatedLine = self.getNumberOfLines(font: font, width: width)
} while estimatedLine > maxLine
estimatedText.append(string)
let attributedString = NSMutableAttributedString(string: estimatedText)
if let highlight = highlight {
let length = highlight.count
let lastIndex = estimatedText.index(estimatedText.endIndex, offsetBy: -length)
let startPoint = estimatedText.distance(from: estimatedText.startIndex, to: lastIndex)
let range = NSRange(location: startPoint, length: length)
attributedString.addAttribute(.foregroundColor, value: UIColor.gray, range: range)
self.text = ""
self.attributedText = attributedString
}
}
}
多读少读请参考此。
import UIKit
class ContentCreateViewController: UIViewController {
let readMoreText = " ...Read More"
let readLessText = " ...Read Less"
let text = "Are you looking to level up your UIImage manipulation skills in Swift? Today, let's dive into a handy extension that allows you to create UIImage instances with customized background colors. This extension can be incredibly useful for various applications, such as creating dynamic user interfaces or generating visually appealing graphics."
lazy var labelContent: UILabel! = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .black
label.font = .systemFont(ofSize: 16)
label.numberOfLines = 0
label.text = text
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
addLabelContent()
// Do any additional setup after loading the view.
}
}
extension ContentCreateViewController {
func addLabelContent(){
view.backgroundColor = .white
view.addSubview(labelContent)
NSLayoutConstraint.activate([
labelContent.topAnchor.constraint(greaterThanOrEqualTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
labelContent.leadingAnchor.constraint(greaterThanOrEqualTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20),
labelContent.centerXAnchor.constraint(equalTo: view.centerXAnchor),
labelContent.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
addReadMoreString()
}
}
extension ContentCreateViewController{
func addReadMoreString() {
let lengthForString = text.count
if lengthForString >= 30 {
let lengthForVisibleString = 256
let trimmedString = text.prefix(lengthForVisibleString)
let readMoreLength = readMoreText.count
let trimmedForReadMore = String(trimmedString.prefix(trimmedString.count - readMoreLength))
let answerAttributed = NSMutableAttributedString(string: trimmedForReadMore, attributes: [.font: labelContent.font ?? UIFont.systemFont(ofSize: 16)])
let readMoreAttributed = NSMutableAttributedString(string: readMoreText, attributes: [.font: UIFont.systemFont(ofSize: 16), .foregroundColor: UIColor.red])
answerAttributed.append(readMoreAttributed)
labelContent.attributedText = answerAttributed
let readMoreGesture = UITapGestureRecognizer(target: self, action: #selector(readMoreDidClickedGesture(_:)))
readMoreGesture.numberOfTapsRequired = 1
labelContent.addGestureRecognizer(readMoreGesture)
labelContent.isUserInteractionEnabled = true
} else {
print("No need for 'Read More'...")
}
}
@objc func readMoreDidClickedGesture(_ gesture: UITapGestureRecognizer) {
let readMoreRange = ((labelContent.text ?? "") as NSString).range(of: readMoreText)
// comment for now
let readLessRange = ((labelContent.text ?? "") as NSString).range(of: readLessText)
if gesture.didTapAttributedTextInLabel(label: labelContent, inRange: readMoreRange) {
let answerAttributed = NSMutableAttributedString(string: text, attributes: [.font: labelContent.font ?? UIFont.systemFont(ofSize: 16)])
let readMoreAttributed = NSMutableAttributedString(string: readLessText, attributes: [.font: UIFont.systemFont(ofSize: 16), .foregroundColor: UIColor.red])
answerAttributed.append(readMoreAttributed)
labelContent.attributedText = answerAttributed
print("Read More")
}else if gesture.didTapAttributedTextInLabel(label: labelContent, inRange: readLessRange) {
addReadMoreString()
print("Read Less")
} else {
print("Tapped none")
}
}
}
extension UITapGestureRecognizer {
func didTapAttributedTextInLabel(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 = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
//(labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
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 = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,
// locationOfTouchInLabel.y - textContainerOffset.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)
}
}
输出