如何使用 UILabel 制作像 Instagram 一样的“...更多”功能

问题描述 投票:0回答:1

我目前正在开发一个 iOS 应用程序,并面临 UILabel 的挑战。 我希望实现两个主要目标:

  1. 将 UILabel 中的文本显示限制为仅两行。
  2. 在文本末尾附加“...更多”以表明还有其他内容可用。

现在我做了一个计算逻辑如下: 但计算时间太长,所以屏幕有点闪烁。

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
        }
    }
}
swift uikit uilabel instagram
1个回答
0
投票

多读少读请参考此。

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)
    }
}

输出

© www.soinside.com 2019 - 2024. All rights reserved.