Search code examples
swiftuitextviewspecial-charactersuitextviewdelegate

How can I prevent a user from adding too many line breaks in a UITextView in Swift 5?


I have a UITextView to let users type-in comments for some audio file they have just recorded.
I would like to limit the amount of the "\n" (newline characters) they can use to 5 (i.e., the comment should be up to 5 lines long). If they try going to the sixth line I would like to show an alert with a sensible message and, upon pressing the OK button in the relative action, I would like to let the user be able to edit their text.

Delegation is already set up and implementation of the optional func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool is what I am doing now.
The logic I put inside shows the alert correctly but then, upon clicking OK and trying to delete some characters, the method is called again and I get the alert.
I know this is due to the counter still being stuck at 5 but resetting it to 0 allows then for 9 lines or more, so it is not a solution.

Here is the code that I tried and that is not working as intended:

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    if text == characterToCheck /* \n, declared globally */ {
        characterCounter += 1 // this was also declared as a global property
    }

    if characterCounter > 4 {
        let newlineAC = UIAlertController(title: "Too many linebreaks", message: "Please go back and make your comment fit into a maximum of 5 lines", preferredStyle: .alert)
        newlineAC.addAction(UIAlertAction(title: "OK", style: .default) { [weak self] (_) in
            let currentText = textView.text ?? ""
            guard let currentTextRange = Range(range, in: currentText) else { return }

            self?.comments.text = currentText.replacingOccurrences(of: "\n", with: "@ ", range: currentTextRange)
        })
        present(newlineAC, animated: true)

        return false
    } else {
        return true
    }
}  

No error message is thrown because the code does exactly what I ask him but I'm clearly asking it in the wrong way. What can I do?


Solution

  • Here is my solution:

    UPDATED WITH Matt Bart feedback

     func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        if text == String(characterToCheck) /* \n, declared globally */ {
            characterCounter += 1 // this was also declared as a global property
        }
    
        if text == "" {
            let characters = Array(textView.text)
            if characters.count >= range.location {
                let deletedCharacter = characters[range.location]
                if  deletedCharacter == characterToCheck {
                    characterCounter -= 1
                }
            }
        }
    
        if characterCounter > 4 {
            let newlineAC = UIAlertController(title: "Too many linebreaks", message: "Please go back and make your comment fit into a maximum of 5 lines", preferredStyle: .alert)
            newlineAC.addAction(UIAlertAction(title: "OK", style: .default) { [weak self] (_) in
                let currentText = textView.text ?? ""
                guard let currentTextRange = Range(range, in: currentText) else { return }
    
                self?.comments.text = currentText.replacingOccurrences(of: "\n", with: "@ ", range: currentTextRange)
            })
            present(newlineAC, animated: true, completion: { [weak self] in
                self?.characterCounter -= 1
            })
    
            return false
        } else {
            return true
        }
    }
    

    }

    Also, declare characterToCheck as a Character instead of String

    characterCounter must start at 0