Limit the number of lines for UITextview
Asked Answered
L

11

84

I was wondering how to limit the amount of LINES (not characters as asked in other questions) a user can enter when editing a UITextField.

Ideally, I would like to limit the input to max. 10 lines.

Where would I need to start? Do I do this with a method? In

 - (BOOL)textViewShouldBeginEditing:(UITextView *)aTextView
Leptophyllous answered 7/3, 2011 at 21:58 Comment(2)
You can find your answer in this post.Sumac
possible duplicate of Limiting text in a UITextViewInterpretive
P
15

You have the right idea, but the wrong method. textView:shouldChangeTextInRange:replacementText: is called whenever the text is going to change; you can access the current content of the text view using its text property, and you can construct the new content from the passed range and replacement text with [textView.text stringByReplacingCharactersInRange:range withString:replacementText]. You can then count the number of lines and return YES to allow the change or NO to reject it.

Property answered 8/3, 2011 at 3:36 Comment(4)
Or did you mean on-screen lines, rather than logical lines of text?Property
Actually, yes, I want to know on-screen lines. I want to load a txt file into a UITextView and limit the lines shown on the screen to 10 or 15 and then have the rest on the next screen. It's really like a little word processor which has individual pages. That's why I want to limit the lines (on screen) displayed in the UITextView. But perhaps there is an easier way to do this? Perhaps use UIWebView?Leptophyllous
If all you want is paged behavior, note that UITextView is a subclass of UIScrollView and so has the pagingEnabled property.Property
I don't think this works as shouldChangeTextInRange can only count the number of lines prior to the new input. Adding a line for each time the new text is "\n" doesn't work either as the user could naturally reach the end of the line.Exoskeleton
C
264

Maciek Czarnik answer does not worked for me, but it got me insights what to do.

iOS 7+

Swift

textView.textContainer.maximumNumberOfLines = 10
textView.textContainer.lineBreakMode = .byTruncatingTail

ObjC

textView.textContainer.maximumNumberOfLines = 10;
textView.textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
Creatine answered 6/1, 2015 at 7:26 Comment(3)
This should be the accepted answer now, even if it is a few years late, it works!Poultry
This is buggy. It allows you to hit return past the end of the text view and then your cursor disappears and you have to hit delete to find it again.Source
Swift 2: textView.textContainer.lineBreakMode = .ByTruncatingTailAphotic
V
59

Maybe this can help (iOS 7+):

textView.textContainer.maximumNumberOfLines = 10;
[textView.layoutManager textContainerChangedGeometry:textView.textContainer];

Even first line should do the trick I guess, but doesn't... Maybe its a bug in SDK

Vicarial answered 15/1, 2014 at 22:8 Comment(0)
P
15

You have the right idea, but the wrong method. textView:shouldChangeTextInRange:replacementText: is called whenever the text is going to change; you can access the current content of the text view using its text property, and you can construct the new content from the passed range and replacement text with [textView.text stringByReplacingCharactersInRange:range withString:replacementText]. You can then count the number of lines and return YES to allow the change or NO to reject it.

Property answered 8/3, 2011 at 3:36 Comment(4)
Or did you mean on-screen lines, rather than logical lines of text?Property
Actually, yes, I want to know on-screen lines. I want to load a txt file into a UITextView and limit the lines shown on the screen to 10 or 15 and then have the rest on the next screen. It's really like a little word processor which has individual pages. That's why I want to limit the lines (on screen) displayed in the UITextView. But perhaps there is an easier way to do this? Perhaps use UIWebView?Leptophyllous
If all you want is paged behavior, note that UITextView is a subclass of UIScrollView and so has the pagingEnabled property.Property
I don't think this works as shouldChangeTextInRange can only count the number of lines prior to the new input. Adding a line for each time the new text is "\n" doesn't work either as the user could naturally reach the end of the line.Exoskeleton
M
14

in Swift 3.0 version:

self.textView.textContainer.maximumNumberOfLines = self.textViewNumberOflines
self.textView.textContainer.lineBreakMode = .byTruncatingTail
Moulding answered 4/1, 2017 at 4:16 Comment(0)
H
12

Maciek Czarnik's answer does not seem to work for me, even in iOS7. It gives me strange behavior, I don't know why.

What I do to limit the number of lines in the UITextView is simply :

(tested only in iOS7) In the following UITextViewDelegate method :

- (void)textViewDidChange:(UITextView *)textView
{
    NSUInteger maxNumberOfLines = 5;
    NSUInteger numLines = textView.contentSize.height/textView.font.lineHeight;
    if (numLines > maxNumberOfLines)
    {
        textView.text = [textView.text substringToIndex:textView.text.length - 1];
    }
}
Heel answered 2/4, 2014 at 14:20 Comment(2)
CGSize size = [textView sizeThatFits:CGSizeMake(textView.width, MAXFLOAT)]; for me I had to calc the new height it will be for this to workBoynton
nice solution easy and perfectBetel
Z
7

Here's a improved Version of Numereyes answer in Swift 4.2 / Swift 5

I made a little extension so I can reuse the code. I'm using a While-Loop to check if the size fits. This also works when the user pastes a lot of text at once.

extension UITextView {        
    var numberOfCurrentlyDisplayedLines: Int {
        let size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
        //for Swift <=4.0, replace with next line:
        //let size = systemLayoutSizeFitting(UILayoutFittingCompressedSize)

        return Int(((size.height - layoutMargins.top - layoutMargins.bottom) / font!.lineHeight))
    }

    /// Removes last characters until the given max. number of lines is reached
    func removeTextUntilSatisfying(maxNumberOfLines: Int) {
        while numberOfCurrentlyDisplayedLines > (maxNumberOfLines) {
            text = String(text.dropLast())
            layoutIfNeeded()
        }
    }
}

// Use it in UITextView's delegate method:
func textViewDidChange(_ textView: UITextView) {        
    textView.removeTextUntilSatisfying(maxNumberOfLines: 10)
}        
Zomba answered 6/12, 2017 at 14:16 Comment(3)
This seems to rely on newlines present in the text, it doesn't work if there aren't any, always reports 0 lines even when there are multiple visible / displayed lines.Amabelle
@Amabelle i think you wanted to comment this answer: https://mcmap.net/q/239894/-limit-the-number-of-lines-for-uitextview ?Zomba
I can see why you're confused, let me explain better: when I tried your solution and the text contained newlines, it worked. But when the text contained no newlines yet was still visibly wrapped into multiple lines, the code you've given didn't calculate the number of visible lines, it returned zero.Amabelle
G
5

Swift 4

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    let existingLines = textView.text.components(separatedBy: CharacterSet.newlines)
    let newLines = text.components(separatedBy: CharacterSet.newlines)
    let linesAfterChange = existingLines.count + newLines.count - 1
    return linesAfterChange <= textView.textContainer.maximumNumberOfLines
}

And if you want to limit characters also:

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        let existingLines = textView.text.components(separatedBy: CharacterSet.newlines)
        let newLines = text.components(separatedBy: CharacterSet.newlines)
        let linesAfterChange = existingLines.count + newLines.count - 1
        if(text == "\n") {
            return linesAfterChange <= textView.textContainer.maximumNumberOfLines
        }

        let newText = (textView.text as NSString).replacingCharacters(in: range, with: text)
        let numberOfChars = newText.count
        return numberOfChars <= 30 // 30 characters limit
    }
}

don't forget to add how many lines you want the limit to be in viewDidLoad:

txtView.textContainer.maximumNumberOfLines = 2
Gainsborough answered 28/2, 2019 at 11:23 Comment(0)
R
3

The other solutions given do not solve an issue related to a last line being created at the end (an 11th line in the question's case).

Here is a working solution with Swift 4.0 & Xcode 9.0 beta (found on this blog post)

   class ViewController: UIViewController, UITextViewDelegate {

      @IBOutlet weak var textView: UITextView!

    override func viewDidLoad() {
        super.viewDidLoad()
        textView.delegate = self
        textView.textContainer.maximumNumberOfLines = 10
        textView.textContainer.lineBreakMode = .byWordWrapping
      }

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

        let existingLines = textView.text.components(separatedBy: CharacterSet.newlines)
        let newLines = text.components(separatedBy: CharacterSet.newlines)
        let linesAfterChange = existingLines.count + newLines.count - 1

        return linesAfterChange <= textView.textContainer.maximumNumberOfLines
    }

Nota bene: This solution does not handle the scenario where the last line is too long to display (text will be hidden on the far right side of the UITextView).

Rothstein answered 28/8, 2017 at 13:1 Comment(0)
A
2

Swift 5.7, iOS 16

Inside your UIViewRepresentable wrapper for UITextView (or UITextField)

func makeUIView(context: Context) -> UITextView {

    let textView = UITextView() // ..or MyCustomUITextView()

    // ..your existing makeUIView body here..

    // Limit to N = 3 visible (displayed) lines.
    textView.textContainer.maximumNumberOfLines = 3

    // When visible line count is exceeded typing can continue,
    // but what's entered will be truncated so hidden from view.
    textView.textContainer.lineBreakMode = .byTruncatingTail

    // ..or prevent further text entry when limit is hit.
    // See https://developer.apple.com/documentation/uikit/nslinebreakmode
    //textView.textContainer.lineBreakMode = .byClipping

    // Set delegate now that other changes are made.
    textView.delegate = self

    return textView
}

Amabelle answered 20/2, 2023 at 19:27 Comment(0)
H
1

Similar to other answers, but usable directly from Storyboard and without subclassing:

extension UITextView {
    @IBInspectable var maxNumberOfLines: NSInteger {
        set {
            textContainer.maximumNumberOfLines = maxNumberOfLines
        }
        get {
            return textContainer.maximumNumberOfLines
        }
    }
    @IBInspectable var lineBreakByTruncatingTail: Bool {
        set {
            if lineBreakByTruncatingTail {
                textContainer.lineBreakMode = .byTruncatingTail
            }
        }
        get {
            return textContainer.lineBreakMode == .byTruncatingTail
        }
    }
}
Hospitalization answered 20/11, 2017 at 1:37 Comment(0)
P
0

Refer to APPLE documentation: Counting Lines of Text https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/TextLayout/Tasks/CountLines.html

    - (void)textViewDidChangeSelection:(UITextView *)textView{
NSLayoutManager *layoutManager = [textView layoutManager];
unsigned numberOfLines, index, numberOfGlyphs =
(unsigned)[layoutManager numberOfGlyphs];
NSRange lineRange;
for (numberOfLines = 0, index = 0; index < numberOfGlyphs; numberOfLines++){
    (void) [layoutManager lineFragmentRectForGlyphAtIndex:index
            effectiveRange:&lineRange];
    index = (unsigned)NSMaxRange(lineRange);
}
if(numberOfLines > 10){
    self.textField.text = self.lastString;
}

}

handle "\n"

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{
if([text containsString:@"\n"]){
    NSLayoutManager *layoutManager = [textView layoutManager];
    unsigned numberOfLines, index, numberOfGlyphs =
            (unsigned)[layoutManager numberOfGlyphs];
    NSRange lineRange;
    for (numberOfLines = 0, index = 0; index < numberOfGlyphs; numberOfLines++){
        (void) [layoutManager lineFragmentRectForGlyphAtIndex:index
                effectiveRange:&lineRange];
        index = (unsigned)NSMaxRange(lineRange);
    }
    NSLog(@"lines = %d",numberOfLines);
    if(numberOfLines >= 10){
        return NO;
    }
}
self.lastString = textView.text;

return YES;

}

Putrescible answered 16/2, 2023 at 15:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.