17

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.

mriddi
  • 423
  • 2
  • 4
  • 11

8 Answers8

38

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.

n00bProgrammer
  • 4,261
  • 3
  • 32
  • 60
  • @mriddi. I have given you a skeletal structure. You will *have* to add conditions as per your need. For instance, editing code in the middle of your text, or replacing words or characters. – n00bProgrammer Nov 15 '13 at 19:52
  • There are cases where this won't work. What about when the user pastes in text and the caret position is moved in the process? – rmaddy Nov 15 '13 at 20:03
  • Hence i mentioned that conditions *have to be added*. – n00bProgrammer Nov 15 '13 at 20:08
  • Please can anybody help me with the following question? If the user writes bulleted text (multiple line breaks) in UITextView, how to detect the total number of line breaks? – Markus Jan 21 '16 at 23:02
  • Will this help if textView's text is set programmatically? – Nikhil Manapure May 10 '17 at 12:16
  • @NikhilManapure, sure. Just make sure you call the relevant code after you're done setting the text. Setting text programmatically does not call the delegate method (I think, though I'm unsure). – n00bProgrammer May 10 '17 at 13:27
11

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
    }
d4Rk
  • 6,622
  • 5
  • 46
  • 60
Kiran Jasvanee
  • 6,362
  • 1
  • 36
  • 52
10

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

    }
Sourav Gupta
  • 762
  • 11
  • 13
4

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;
}
Stuart Campbell
  • 1,141
  • 11
  • 13
  • This should be the accepted answer. It is most elegant. The current accepted answer is very hacky. – nvrtd frst Jan 09 '17 at 20:13
  • 8
    This only works if user presses return key, not if the new line starts because it reached the end of the previous line. – JP Aquino Mar 12 '18 at 14:26
4

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
    }
}
2

SWIFT 5

Lets not overcomplicate things.

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

    if text == "\n" {
        // return pressed
    }
}
Waylan Sands
  • 321
  • 3
  • 11
1

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
    }
}
Carson Vo
  • 476
  • 6
  • 20
0

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.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • Just IOS7. Plz more details about using second one – mriddi Nov 15 '13 at 19:43
  • Look at the docs. It's similar to the `sizeWithAttributes:` method you were using. You just need to pass in a size. Pass in a size with the desired width and a really large height. The returned size will be the same with but with the needed height to fit the text in the width. – rmaddy Nov 15 '13 at 19:49