@IBOutlet weak var testLabel: UILabel!
let message = "Please <a href='https://www.google.com'>click here</a> to search"
@override func viewDidLoad() {
   super.viewDidLoad()
   formatLabel(with: message.htmlToString)
}
func formatLabel(with message: String) {
   let formattedText = String.format(strings: ["click here"],
                                     boldFont:  UIFont.init(name: "Roboto-Bold", size: 16.0)!,
                                     boldColor: UIColor.blue,
                                     inString: message,
                                     font: UIFont.init(name: "Roboto-Regular", size: 16.0)!,
                                     color: UIColor(red: 33/255, green: 136/255, blue: 189/255, alpha: 1.0))
   testLabel.attributedText = formattedText
   testLabel.numberOfLines = 0
   let tap = UITapGestureRecognizer(target: self, action: #selector(handleTermTapped))
   testLabel.addGestureRecognizer(tap)
   testLabel.isUserInteractionEnabled = true
   testLabel.textAlignment = .center
}
@objc func handleTermTapped(gesture: UITapGestureRecognizer) {
   let clickString = (testLabel.text ?? "") as NSString
   let clickRange = clickString.range(of: "click here")
   let tapLocation = gesture.location(in: testLabel)
   let index = testLabel.indexOfAttributedTextCharacterAtPoint(point: tapLocation)
   if checkRange(clickRange, contain: index) == true {
       guard let url = URL(string: "https://www.google.com") else { return }
       UIApplication.shared.open(url, options: [:], completionHandler: nil)
       return
   }
}
Helper function and extensions:
func checkRange(_ range: NSRange, contain index: Int) -> Bool {
    return index > range.location && index < range.location + range.length
}
extension String {
    static func format(strings: [String],
                       boldFont: UIFont = UIFont.init(name: "Roboto-Bold", size: 16.0)!,
                       boldColor: UIColor = UIColor.blue,
                       inString string: String,
                       font: UIFont = UIFont.init(name: "Roboto-Regular", size: 16.0)!,
                       color: UIColor = UIColor(red: 33/255, green: 136/255, blue: 189/255, alpha: 1.0)) -> NSAttributedString {
        let attributedString =
            NSMutableAttributedString(string: string,
                                      attributes: [
                                        NSAttributedString.Key.font: font,
                                        NSAttributedString.Key.foregroundColor: color])
        let boldFontAttribute = [NSAttributedString.Key.font: boldFont, NSAttributedString.Key.foregroundColor: boldColor]
        for bold in strings {
            attributedString.addAttributes(boldFontAttribute, range: (string as NSString).range(of: bold))
        }
        return attributedString
    }
   var htmlToAttributedString: NSAttributedString? {
        guard let data = data(using: .utf8) else { return NSAttributedString() }
        do {
            return try NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding:String.Encoding.utf8.rawValue], documentAttributes: nil)
        } catch {
            return NSAttributedString()
        }
    }
    var htmlToString: String {
        return htmlToAttributedString?.string ?? ""
    }
}
extension UILabel {
    func indexOfAttributedTextCharacterAtPoint(point: CGPoint) -> Int {
        let textStorage = NSTextStorage(attributedString: self.attributedText!)
        let layoutManager = NSLayoutManager()
        textStorage.addLayoutManager(layoutManager)
        let textContainer = NSTextContainer(size: self.frame.size)
        textContainer.lineFragmentPadding = 0
        textContainer.maximumNumberOfLines = self.numberOfLines
        textContainer.lineBreakMode = self.lineBreakMode
        layoutManager.addTextContainer(textContainer)
        let index = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
        return index
    }
}