开发者

Limit the number of lines for UITextview

开发者 https://www.devze.com 2023-02-15 04:31 出处:网络
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.

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


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;


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


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.


in Swift 3.0 version:

self.textView.textContainer.maximumNumberOfLines = self.textViewNumberOflines
self.textView.textContainer.lineBreakMode = .byTruncatingTail


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];
    }
}


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


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


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).


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

精彩评论

暂无评论...
验证码 换一张
取 消