1

I'm working with a TextView and I wanted to create two different links within a text where the user accepts the terms and conditions and the privacy policy.

Also I need each link to open a different UIViewController.

Can anyone help me with an example to understand how to achieve this?

I need to understand how to create two Hyper links and how to open them in two different ViewControllers

Thank you all for any help you can give me


For example ... I would like to get a TextView similar to this

enter image description here

kAiN
  • 2,559
  • 1
  • 26
  • 54
  • NSAttributedString is what you need to look into. Have a look here: https://stackoverflow.com/questions/1256887/create-tap-able-links-in-the-nsattributedstring-of-a-uilabel – Eloy B. Jan 20 '20 at 12:44

3 Answers3

8

You can use the following UITextView delegate Method and Attributed string Tested on swift 5.1 :

 let attributedString = NSMutableAttributedString(string: "By continueing you agree terms and conditions and the privacy policy")

        attributedString.addAttribute(.link, value: "terms://termsofCondition", range: (attributedString.string as NSString).range(of: "terms and conditions"))

        attributedString.addAttribute(.link, value: "privacy://privacypolicy", range: (attributedString.string as NSString).range(of: "privacy policy"))

        textView.linkTextAttributes = [ NSAttributedString.Key.foregroundColor: UIColor.blue]
        textView.attributedText = attributedString
        textView.delegate = self
        textView.isSelectable = true
        textView.isEditable = false
        textView.delaysContentTouches = false
        textView.isScrollEnabled = false

    func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {

        if URL.scheme == "terms" {
            //push view controller 1
            return false
        } else  if URL.scheme == "privacy"{
           // pushViewcontroller 2
             return false
        }
        return true
        // let the system open this URL
    }

The UITextView call this function if the user taps or longPresses the URL link. Implementation of this method is optional. By default, the UITextview opens those applications which are responsible for handling the URL type and pass them the URL. You can use this method to trigger an alternative action

Jawad Ali
  • 13,556
  • 3
  • 32
  • 49
  • this was just what I needed thank you very much ... just a small question .. because when I select the link my log gives me this instead of a simple print that I entered? [general] Connection to daemon was invalidated – kAiN Jan 20 '20 at 13:22
  • i did not get your question ... can you rephrase or elaborate ? so that i become able to answer you properly – Jawad Ali Jan 20 '20 at 14:11
  • I entered the code as you recommended but I can't get any action when I select any link in the textView. My xcode console only gives me this back: 2020-01-20 14: 56: 26.319182 + 0100 [30070: 8614610] [general] Connection to daemon was invalidated – kAiN Jan 20 '20 at 14:44
  • try it now ... its functional and tested now – Jawad Ali Jan 20 '20 at 15:12
  • link was not proper ... i forget to remove spaces from the link string ... sorry for the delay – Jawad Ali Jan 20 '20 at 15:13
3

Set your textView properties like this.

textView.attributedText = "By Continuing, you aggree to terms <a href='http://termsandservicelink'>Terms Of Services</a> and <a href='https://privacypolicylink'>Privacy Policy</a>".convertHtml()
textView.isEditable = false
textView.dataDetectorTypes = [.link]
textView.linkTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.blue, NSAttributedString.Key.underlineColor: UIColor.clear]

You can handle tap event on your link in this delegate.

  func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
       //Check your url whether it is privacy policy or terms and do accordigly
        return true
    }

Here is String extension.

extension String{
    func convertHtml() -> NSAttributedString{
        guard let data = data(using: .utf8) else { return NSAttributedString() }
        do{
            return try NSAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html, NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil)
        }catch{
            return NSAttributedString()
        }
    }
}
shraddha11
  • 783
  • 4
  • 17
1

This result is reached using NSAttributedString, Using NSAttributedString, we can style the text,

myLabel.text = "By signing up you agree to our Terms & Conditions and Privacy Policy"
let text = (myLabel.text)!
let underlineAttriString = NSMutableAttributedString(string: text)
let range1 = (text as NSString).rangeOfString("Terms & Conditions")
underlineAttriString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: range1)
let range2 = (text as NSString).rangeOfString("Privacy Policy")
underlineAttriString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: range2)
myLabel.attributedText = underlineAttriString

Extend UITapGestureRecognizer to provide a convenient function to detect if a certain range (NSRange) is tapped on in a UILabel.

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.locationInView(label)
        let textBoundingBox = layoutManager.usedRectForTextContainer(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 locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,
            locationOfTouchInLabel.y - textContainerOffset.y);
        let indexOfCharacter = layoutManager.characterIndexForPoint(locationOfTouchInTextContainer, inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)

        return NSLocationInRange(indexOfCharacter, targetRange)
    }
}

UITapGestureRecognizer send action to tapLabel:, and detect using the extension method didTapAttributedTextInLabel:inRange:.

@IBAction func tapLabel(gesture: UITapGestureRecognizer) {
    let text = (myLabel.text)!
    let termsRange = (text as NSString).rangeOfString("Terms & Conditions")
    let privacyRange = (text as NSString).rangeOfString("Privacy Policy")

    if gesture.didTapAttributedTextInLabel(myLabel, inRange: termsRange) {
        print("Tapped terms")
    } else if gesture.didTapAttributedTextInLabel(myLabel, inRange: privacyRange) 
    {
        print("Tapped privacy")
    } else {
        print("Tapped none")
    }
}
Komal Goyani
  • 779
  • 6
  • 25