iosswiftmacostextkit

changing the style of next paragraph using typingAttributes


I am working on an iOS app. In the app, I have a UITextView where the user should be typing some paragraphs of attributed text. My requirement is to change the style of the next paragraph every time the user hits Enter.

So, I implemented the following TextView Delegate function:

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    if (text == "\n") {
        textView.typingAttributes = [NSMutableAttributedString.Key.paragraphStyle: self.paragraphStyle]
    }
    return true
}

The problem is:

The style of next paragraph is changed, but not immediately after hitting Enter. Instead, a new line is returned first with the old style, and when the user hits Enter again, the paragraph style is changed.

Example:

The full code:

import UIKit

class ViewController: UIViewController, UITextViewDelegate {
    @IBOutlet weak var textView: UITextView!    
    // Create paragraph styles
    let paragraphStyle = NSMutableParagraphStyle()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        textView.delegate = self
        
        // Create paragraph style
        self.paragraphStyle.firstLineHeadIndent = 140
        // Create attributed string
        let string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"
        // Create attributed string
        let myAttrString = NSMutableAttributedString(string: string, attributes: [:])
        // Write it to text view
        textView.attributedText = myAttrString
    }

    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        if (text == "\n") {
            textView.typingAttributes = [NSMutableAttributedString.Key.paragraphStyle: self.paragraphStyle]
        }
        return true
    }
}

Solution

  • I think of the way shouldChangeTextIn works as similar to the way a spell checker or autocorrect works. When you first type a misspelled word, it doesn't get replaced until you press the space bar. Similarly, shouldChangeTextIn initially acknowledges \n when you enter it, but the replacement doesn't happen until the following is input initiated.

    You can use the textViewDidChange instance method instead, which is from the same UITextViewDelegate protocol:

    func textViewDidChange(_ textView: UITextView) {
        if (textView.text.last == "\n") {
            textView.typingAttributes = [NSMutableAttributedString.Key.paragraphStyle: self.paragraphStyle]
        }
    }