17
votes

I try to detect when carriage goes at new line in UITextView. I can detect it by comparison total later width with UITextView width:

CGSize size = [textView.text sizeWithAttributes:textView.typingAttributes];
if(size.width > textView.bounds.size.width)
      NSLog (@"New line");

But it dose not work proper way because -sizeWithAttributes:textView returns only width of letters without indentation width. Help please solve this.

8
Is this the standard UITextView you're using, or a subclassed version ? - n00bProgrammer
Sorry, you want to know if the user presses "enter" while typing? Or you want to just count the total number of line breaks in the text? - nielsbot

8 Answers

35
votes

This is how I would do it:

  • Get the UITextPosition of the last character.
  • Call caretRectForPosition on your UITextView.
  • Create a CGRect variable and initially store CGRectZero in it.
  • In your textViewDidChange: method, call caretRectForPosition: by passing the UITextPosition.
  • Compare it with the current value stored in the CGRect variable. If the new y-origin of the caretRect is greater than the last one, it means a new line has been reached.

Sample code:

CGRect previousRect = CGRectZero;
- (void)textViewDidChange:(UITextView *)textView{

    UITextPosition* pos = yourTextView.endOfDocument;//explore others like beginningOfDocument if you want to customize the behaviour
    CGRect currentRect = [yourTextView caretRectForPosition:pos];

    if (currentRect.origin.y > previousRect.origin.y){
            //new line reached, write your code
        }
    previousRect = currentRect;

}

Also, you should read the documentation for UITextInput protocol reference here. It is magical, I'm telling you.

Let me know if you have any other issues with this.

10
votes

For Swift use this

previousRect = CGRectZero

 func textViewDidChange(textView: UITextView) {

        var pos = textView.endOfDocument
        var currentRect = textView.caretRectForPosition(pos)
        if(currentRect.origin.y > previousRect?.origin.y){
            //new line reached, write your code
        }
        previousRect = currentRect

    }
8
votes

answer of @n00bProgrammer in Swift-4 with more precise line break detection.

@n00bProgrammer answer is perfect except one thing it reacts differently when the user starts typing in a first line, it presents that Started New Line too.
Overcoming issue, here is the refined code

    var previousRect = CGRect.zero
    func textViewDidChange(_ textView: UITextView) {
        let pos = textView.endOfDocument
        let currentRect = textView.caretRect(for: pos)
        self.previousRect = self.previousRect.origin.y == 0.0 ? currentRect : self.previousRect
        if currentRect.origin.y > self.previousRect.origin.y {
            //new line reached, write your code
            print("Started New Line")
        }
        self.previousRect = currentRect
    }
4
votes

You can use the UITextViewDelegate

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText: (NSString *)text
{
     BOOL newLine = [text isEqualToString:@"\n"];
     if(newLine)
     {
          NSLog(@"User started a new line");
     }
     return YES;
}
4
votes

Swift 3

The accepted answer and the swift version works fine, but here is a Swift 3 version for the lazy people out there.

class CustomViewController: UIViewController, UITextViewDelegate {

    let textView = UITextView(frame: .zero)
    var previousRect = CGRect.zero

    override func viewDidLoad(){
        textView.frame = CGRect(
            x: 20, 
            y: 0, 
            width: view.frame.width, 
            height: 50
        )
        textView.delegate = self
        view.addSubview(textView)
    }

    func textViewDidChange(_ textView: UITextView) {
        let pos = textView.endOfDocument
        let currentRect = textView.caretRect(for: pos)
        if previousRect != CGRect.zero {
            if currentRect.origin.y > previousRect.origin.y {
                print("new line")
            }
        }
        previousRect = currentRect
    }
}
1
votes

SWIFT 4

If you don't want to use previousRect. Let's try this:

func textViewDidChange(_ textView: UITextView) {
    let pos = textView.endOfDocument
    let currentRect = textView.caretRect(for: pos)
    if (currentRect.origin.y == -1 || currentRect.origin.y == CGFloat.infinity){
       print("Yeah!, I've gone to a new line") 
       //-1 for new line with a char, infinity is new line with a space
    }
}
0
votes

You need to get the height of the text, not the width. Use either sizeWithFont:constrainedToSize:lineBreakMode: (if you need to support iOS 6 or earlier) or use boundingRectWithSize:options:attributes:context: if you only support iOS 7.

0
votes

SWIFT 5

Lets not overcomplicate things.

    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {

    if text == "\n" {
        // return pressed
    }
}