iOS app crashes when tapping URL within UITextView

1.1k views Asked by At

I'm trying to setup a UITextView with multiple links within the text. My implementation is based on the suggestion described here. Most links work as expected, however tapping on some of them makes the app crash with the following error EXC_BREAKPOINT (code=1, subcode=0x185646694):
Crash error
Call stack

My UITextView configuration code:

private var actionableLinks = [(String, ()->Void)]() // each link = (actionableString, tapAction)

private func setupMessageText() {
    guard messageTextView != nil else { return }
        
    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.paragraphSpacingBefore = 16
        
    let attributedText = NSMutableAttributedString(string: messageText, attributes: [
        .font: messageTextView.font!,
        .foregroundColor: messageTextView.textColor!,
        .paragraphStyle: paragraphStyle
    ])
        
    addActionableLinks(to: attributedText)
        
    messageTextView?.attributedText = attributedText
}
    
private func addActionableLinks(to attributedText: NSMutableAttributedString) {
    actionableLinks.forEach {
        let actionableString = $0.0
            
        if let nsRange = messageText.nsRange(of: actionableString) {
            attributedText.addAttribute(.link, value: actionableString, range: nsRange)
        }
    }
}

To handle the tap action, I've imlpemented the proper UITextViewDelegate method:

func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
    let tappedLinkString = URL.absoluteString

    if let link = actionableLinks.first(where: { $0.0 == tappedLinkString }) {
        let tapAction = link.1
        tapAction()

        return false
    }

    return true
}

My storyboard configuration for this screen (I have setup the UITextView delegate in the storyboard):
Storyboard Configuration

Any insights would be much appreciated! Thanks.

1

There are 1 answers

2
Ruben Dias On

Issue solved! Thanks to Larme for the quick insights and resources.

It was indeed a case of trying to use a bad string as a link within the UITextView that internally was being converted to a URL. Since this was a string with spaces in it, internal conversion to URL by Apple was failing.

My string "support" was linking properly and it worked, but a different string "new terms and conditions" was failing.

The solution

To solve the issue, I used percent encoding when adding the link attribute to the UITextView's attributed text.

private func addActionableLinks(to attributedText: NSMutableAttributedString) {
    actionableLinks.forEach {
        let actionableString = $0.0
            
        if let nsRange = messageText.nsRange(of: actionableString) {
            let escapedActionableString = escapedString(actionableString)
            attributedText.addAttribute(.link, value: escapedActionableString, range: nsRange)
        }
    }
}
    
private func escapedString(_ string: String) -> String {
    return string.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? string
}

I also changed the delegate method to check for a match to an escaped string:

func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
    let tappedLinkString = URL.absoluteString
        
    for (actionableString, tapAction) in actionableLinks {
        let escapedActionableString = escapedString(actionableString)
            
        if escapedActionableString == tappedLinkString {
            tapAction()
            return false
        }
    }

    return true
}

Thanks!