Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add function-call (not hyperlink) to part of an NSAttributedString in a UILabel?

I need to add some tapGestures to some part of a text, thats in a UILabel. It seems to be able to make a hyperlink - so I guess its possible to make a functioncall as well, but I'm just not sure how to do it.

Heres my code:

    let regularFont = UIFont(name: Constants.robotoRegular, size: 13)!
    let att1 = [NSFontAttributeName: regularFont]

    let turqoiseFont = UIFont(name: Constants.robotoBold, size: 13)!
    let att2 = [NSFontAttributeName: turqoiseFont]

    let attString1 = NSMutableAttributedString(string: "By creating a profile, I accept ", attributes: att1)
    attString1.addAttribute(NSForegroundColorAttributeName, value: UIColor.darkGrayColor(), range: NSMakeRange(0, attString1.length))

    let attString2 = NSMutableAttributedString(string: "Terms and Conditions ", attributes: att2)
    attString2.addAttribute(NSForegroundColorAttributeName, value: Colors.loginButtonColor, range: NSMakeRange(0, attString2.length))

    let attString3 = NSMutableAttributedString(string: "and ", attributes: att1)
    attString3.addAttribute(NSForegroundColorAttributeName, value: UIColor.darkGrayColor(), range: NSMakeRange(0, attString3.length))

    let attString4 = NSMutableAttributedString(string: "Private Policy.", attributes: att2)
    attString4.addAttribute(NSForegroundColorAttributeName, value: Colors.loginButtonColor, range: NSMakeRange(0, attString4.length))

    let index = "\(attString1.string.endIndex)"

    attString1.insertAttributedString(attString4, atIndex: Int(index)!)
    attString1.insertAttributedString(attString3, atIndex: Int(index)!)
    attString1.insertAttributedString(attString2, atIndex: Int(index)!)

    termsAndConditionLabel.attributedText = attString1

I want the turqoise parts to be able to take the user to either Terms and Conditions, or Private Policy. Can anyone please help me with this problem? :))

like image 757
Nicolai Harbo Avatar asked Feb 05 '23 00:02

Nicolai Harbo


2 Answers

I once made the same thing, but used textView instead of UILabel, you should create your own attributes, and add them to attributed string, and then handle tap, here some code

your code with my modifications

   let regularFont = UIFont(name: "HelveticaNeue", size: 13)!
    let att1 = [NSFontAttributeName: regularFont]

    let turqoiseFont = UIFont(name: "HelveticaNeue", size: 13)!
    let att2 = [NSFontAttributeName: turqoiseFont]
    let mattString : NSMutableAttributedString  = NSMutableAttributedString()
    let attString1 = NSMutableAttributedString(string: "By creating a profile, I accept ", attributes: att1)
    attString1.addAttribute(NSForegroundColorAttributeName, value: UIColor.darkGray, range: NSMakeRange(0, attString1.length))
    let attString2 = NSMutableAttributedString(string: "Terms and Conditions ", attributes: att2)
    attString2.addAttribute(NSForegroundColorAttributeName, value: UIColor.red, range: NSMakeRange(0, attString2.length))
    let attString3 = NSMutableAttributedString(string: "and ", attributes: att1)
    attString3.addAttribute(NSForegroundColorAttributeName, value: UIColor.darkGray, range: NSMakeRange(0, attString3.length))
    let attString4 = NSMutableAttributedString(string: "Private Policy.", attributes: att2)
    attString4.addAttribute(NSForegroundColorAttributeName, value: UIColor.red, range: NSMakeRange(0, attString4.length))
    mattString.append(attString1)
    mattString.append(attString2)
    mattString.append(attString3)
    mattString.append(attString4)
    let attributes = ["link" : "termsLink"]
    let attributes2 = ["link" : "policyLink"]
    let str : NSString = mattString.string as NSString
    mattString.addAttributes(attributes, range: str.range(of: "Terms and Conditions"))
    mattString.addAttributes(attributes2, range: str.range(of: "Private Policy"))
    _textView.attributedText = mattString
    _textView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.singleTap(tap:)))) 

and func to handle tap

func singleTap(tap : UITapGestureRecognizer) {
    let layoutManager = _textView.layoutManager
    let location = tap.location(in: _textView)
    let index = layoutManager.characterIndex(for: location, in: _textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
    if index > _textView.textStorage.length {return}
    var range : NSRange = NSRange()
    if let type = _textView.attributedText.attribute("link", at: index, effectiveRange: &range) as? String {
        if type == "termsLink" {
            //.. do smth
        } else {
            //.. do smth
        }
    }
}

UPDATE:

here is the class I wrote to use Label

https://github.com/barbados88/TapableLabel/blob/master/TapableLabel.swift

like image 171
Alexandr Kolesnik Avatar answered Feb 07 '23 19:02

Alexandr Kolesnik


I have changed Alexandr Kolesnik his answer for UILabel with Swift 4. If someone want to use with UILabel too

    let regularFontAttribute = [NSAttributedStringKey.font: UIFont(name: "Lato-Regular", size: 15.0)!]

    let boldFontAttribute = [NSAttributedStringKey.font: UIFont(name: "Lato-Bold", size: 15.0)!]
    let mattString : NSMutableAttributedString  = NSMutableAttributedString()

    let attString1 = NSMutableAttributedString(string: "By creating a profile, I accept ", attributes: regularFontAttribute)
    let anotherAttribute1 = [NSAttributedStringKey.foregroundColor: UIColor.black]
    attString1.addAttributes(anotherAttribute1, range: NSMakeRange(0, attString1.length))

    let attString2 = NSMutableAttributedString(string: "Terms and Conditions ", attributes: boldFontAttribute)
    let anotherAttribute2 = [NSAttributedStringKey.foregroundColor: UIColor.red]
    attString2.addAttributes(anotherAttribute2, range: NSMakeRange(0, attString2.length))

    let attString3 = NSMutableAttributedString(string: "and ", attributes: regularFontAttribute)
    let anotherAttribute3 = [NSAttributedStringKey.foregroundColor: UIColor.black]
    attString3.addAttributes(anotherAttribute3, range: NSMakeRange(0, attString3.length))

    let attString4 = NSMutableAttributedString(string:  "Private Policy.", attributes: boldFontAttribute)
    let anotherAttribute4 = [NSAttributedStringKey.foregroundColor: UIColor.red]
    attString4.addAttributes(anotherAttribute4, range: NSMakeRange(0, attString4.length))

    mattString.append(attString1)
    mattString.append(attString2)
    mattString.append(attString3)
    mattString.append(attString4)

    let attributes: [NSAttributedStringKey : Any] = [NSAttributedStringKey(rawValue: "link") : "termsLink"]
    let attributes2: [NSAttributedStringKey : Any] = [NSAttributedStringKey(rawValue: "link") : "policyLink"]
    let str : NSString = mattString.string as NSString
    mattString.addAttributes(attributes, range: str.range(of: "Terms and Conditions"))
    mattString.addAttributes(attributes2, range: str.range(of: "Private Policy"))

    self.attributedLabel.attributedText = mattString
    self.attributedLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.singleTap(_:))))
    self.attributedLabel.isUserInteractionEnabled = true

and func to handle tap and detect tapped text

    @objc func singleTap(_ tap : UITapGestureRecognizer) {
        let attributedText = NSMutableAttributedString(attributedString: self.attributedLabel!.attributedText!)
        attributedText.addAttributes([NSAttributedStringKey.font: self.attributedLabel!.font], range: NSMakeRange(0, (self.attributedLabel!.attributedText?.string.count)!))

        // Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
        let layoutManager = NSLayoutManager()
        let textContainer = NSTextContainer(size: CGSize(width: (self.attributedLabel?.frame.width)!, height: (self.attributedLabel?.frame.height)!+100))
        let textStorage = NSTextStorage(attributedString: attributedText)

        // Configure layoutManager and textStorage
        layoutManager.addTextContainer(textContainer)
        textStorage.addLayoutManager(layoutManager)

        // Configure textContainer
        textContainer.lineFragmentPadding = 0.0
        textContainer.lineBreakMode = self.attributedLabel!.lineBreakMode
        textContainer.maximumNumberOfLines = self.attributedLabel!.numberOfLines
        let labelSize = self.attributedLabel!.bounds.size
        textContainer.size = labelSize

        let tapLocation = tap.location(in: self.attributedLabel)

        // get the index of character where user tapped
        let index = layoutManager.characterIndex(for: tapLocation, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)

        if index > (self.attributedLabel.text?.count)! { return }
        var range : NSRange = NSRange()
        if let type = self.attributedLabel.attributedText?.attribute(NSAttributedStringKey(rawValue: "link"), at: index, effectiveRange: &range) as? String {

            // to get full text which was clicked
            print((self.attributedLabel.text! as NSString).substring(with: range))

            if type == "termsLink" {
                //.. do smth
                print("termsLink click")

            } else { //policyLink
                //.. do smth
                print("policyLink click")

            }
        }
    }
like image 25
Shahrukh Avatar answered Feb 07 '23 19:02

Shahrukh