Highlight text of UILabel in Swift

by DaoNM2
252 views

Hi mọi người, mấy năm trước mình có làm một dự án mobile về mảng Logistics, trong ứng dụng thường sử dụng khá nhiều các text, một số từ được Highlight text đi kèm với các action tương tự như là một button nhằm mục đích gây sự chú ý với người dùng. Để làm được việc này chúng ta cần xác định được vị trí text cần Highlight để thực hiện thay đổi UI cho đoạn text đó gắn action cho nó. Mình thấy đây là một tính năng khá thú vị và sẽ gặp nhiều trong các dự án sắp tới của các bạn. Vậy nên mình xin chia sẻ cách làm của mình như sau.

Cách Highlight text

Về lý thuyết để highlight text trong swift thì chúng ta cần l xác định vị trí(position) và độ dài(lenght) của text cần highlight sau đó thực hiện thay đổi thuộc tính của đoạn text đó bằng NSAttributedString.

Việc bắt sư kiện khi chúng ta tương tác với Highlight text cũng phải tìm vị trí và độ dài của đoạn text, sau đó chúng ta dựa vào điểm người dùng bấm vào trên Label để xác định xem người dùng có bấm đúng vị trí text được highlight hay không.

1. Cách thực hiện Highlight text

Đầu tiên chúng ta sẽ tạo một func trong extension của UILabel như sau:

extension UILabel {
    func highlightText(_ text: String, highlightColor: UIColor, in mainText: String) {
        // chuyển mainText sang NSMutableAttributedString để xử lý tìm range của highlight text
        let highlightAttributedString = NSMutableAttributedString(string: mainText)
        // xác định vị trí, độ dài của text cần highlight
        let range = (mainText as NSString).range(of: text)
        // thêm thuộc tính cho đoạn text cần highlight
        highlightAttributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: highlightColor, range: range)
        // gán giá trị vào label
        self.attributedText = highlightAttributedString
    }
}

Khi sử dụng chúng ta sẽ làm như sau:

Trường hợp này mình sẽ biến chữ “DaoNM2” trong “Good evening! DaoNM2” thành màu cam như sau

lbInfo.highlightText("DaoNM2", highlightColor: .red, in: "Good evening! DaoNM2")
Kết quả

2. Bắt sự kiện và thực hiện hành động khi bấm vào text được Highlight

Ở mục 1 mình đã hướng dẫn cách thực hiện đổi màu text của 1 đoạn text trong UILabel, vậy để bắt sự kiện khi chúng ta bấm vào thì làm cách nào?

Đầu tiên chúng ta sẽ tạo một func trong extension của UITapGestureRecognizer, nhằm mục đích xử lí điểm chạm của người dùng và xác định xem có đúng vị trí của text đã được Highlight hay không như sau:

extension UITapGestureRecognizer {
    func didTapHighlightedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
        guard let attributedText = label.attributedText else {
            return false
        }

        let mutableStr = NSMutableAttributedString(attributedString: attributedText)
        mutableStr.addAttributes([NSAttributedString.Key.font: label.font!], range: NSRange(location: 0, length: attributedText.length))
           
        // If the label have text alignment. Delete this code if label have a default (left) aligment. Possible to add the attribute in previous adding.
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.alignment = label.textAlignment
        mutableStr.addAttributes([NSAttributedString.Key.paragraphStyle: paragraphStyle], range: NSRange(location: 0, length: attributedText.length))

        // Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
        let layoutManager = NSLayoutManager()
        let textContainer = NSTextContainer(size: CGSize.zero)
        let textStorage = NSTextStorage(attributedString: mutableStr)
           
        // 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)
    }
}

Trong hàm này mình sẽ dùng NSLayoutManager để xác định xem điểm chạm của người dùng mà UITapGestureRecognizer bắt được có đúng vị trí highlight text hay không.

func này sẽ trả về cho chúng ta biết được vị trí người dùng đang chạm vào UILabel có trùng với range truyền vào hay không.

Để ứng dụng nó vào bài toán của chúng ta thì chúng ta sẽ thực hiện nó như sau:

    private func setUpLabelInfo() {
        lbInfo.highlightText("More...", highlightColor: .red, in: "Two one-offs, a roadster and a coupé, mark the end of production of super sports cars powered by the V12 combustion engine in the lead-up to the hybrid era. More...")
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapMore(_:)))
        lbInfo.isUserInteractionEnabled = true
        lbInfo.addGestureRecognizer(tapGesture)
    }
    
    @objc
    private func tapMore(_ gesture: UITapGestureRecognizer) {
        let mainText = lbInfo.text ?? ""
        let highlightText = "More..."
        let highlightTextRange = ((mainText) as NSString).range(of: highlightText)
        if gesture.didTapHighlightedTextInLabel(label: lbInfo, inRange: highlightTextRange) {
            print("Did tap: \(highlightText), hightlightRange: \(highlightTextRange)")
        }
    }

Ở viewDidLoad chúng ta chỉ cần gọi nó ra là xong.

   override func viewDidLoad() {
        super.viewDidLoad()

        lbTitle.textColor = .orange
        lbTitle.textAlignment = .center
        lbTitle.text = "Lambo"
        setUpLabelInfo()
    }

Kết quả thu được như sau:

Vậy là chúng ta đã có thể thêm action vào cho text được highlight.

Từ ví dụ này chúng ta có thể tuỳ biến cho phù hợp dự án của bạn hoặc phát triển nó lên theo cách mà các bạn mong muốn.

Mình hi vọng bài viết giúp cho các bạn có thêm phương án lựa chọn khi tham gia những dự án có yêu cầu tương tự. Chúc các bạn thành công!

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.

You may also like