web-dev-qa-db-fra.com

Limitez le nombre de lignes pour UITextview

Je me demandais comment limiter la quantité de LIGNES (pas de caractères comme demandé dans d'autres questions) qu'un utilisateur peut entrer lors de la modification d'un UITextField.

Idéalement, je voudrais limiter l'entrée à max. 10 lignes.

Par où devrais-je commencer? Dois-je le faire avec une méthode? Dans

 - (BOOL)textViewShouldBeginEditing:(UITextView *)aTextView
65
n.evermind

Vous avez la bonne idée, mais la mauvaise méthode. textView:shouldChangeTextInRange:replacementText: est appelé chaque fois que le texte va changer; vous pouvez accéder au contenu actuel de la vue texte à l'aide de sa propriété text, et vous pouvez construire le nouveau contenu à partir de la plage passée et du texte de remplacement avec [textView.text stringByReplacingCharactersInRange:range withString:replacementText]. Vous pouvez ensuite compter le nombre de lignes et retourner OUI pour autoriser la modification ou NON pour la rejeter.

15
Anomie

La réponse de Maciek Czarnik ne fonctionne pas pour moi, mais elle m'a permis de savoir quoi faire.

iOS 7+

Swift

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

ObjC

textView.textContainer.maximumNumberOfLines = 10;
textView.textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
204
Jamagas

Cela peut peut-être aider (iOS 7+):

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

Même la première ligne devrait faire l'affaire, je suppose, mais ce n'est pas le cas ... Peut-être que c'est un bogue dans le SDK

52
Maciek Czarnik

La réponse de Maciek Czarnik ne semble pas fonctionner pour moi, même dans iOS7. Cela me donne un comportement étrange, je ne sais pas pourquoi.

Ce que je fais pour limiter le nombre de lignes dans UITextView est simplement:

(testé uniquement dans iOS7) Dans la méthode UITextViewDelegate suivante:

- (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];
    }
}
12
Numereyes

dans Swift 3.0 version:

self.textView.textContainer.maximumNumberOfLines = self.textViewNumberOflines
self.textView.textContainer.lineBreakMode = .byTruncatingTail
11
levin varghese

Voici une version améliorée de la réponse Numereyes dans Swift 4.2/Swift 5

J'ai fait une petite extension pour pouvoir réutiliser le code. J'utilise une boucle While pour vérifier si la taille convient. Cela fonctionne également lorsque l'utilisateur colle beaucoup de texte à la fois.

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

Les autres solutions proposées ne résolvent pas un problème lié à la création d'une dernière ligne à la fin (une 11ème ligne dans le cas de la question).

Voici une solution de travail avec Swift 4.0 & Xcode 9.0 beta (trouvé sur cet article de blog )

   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: Cette solution ne gère pas le scénario où la dernière ligne est trop longue à afficher (le texte sera masqué à l'extrême droite de l'UITextView ).

2
standousset

Similaire à d'autres réponses, mais utilisable directement à partir de Storyboard et sans sous-classement:

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
        }
    }
}
1
Antzi

pour Swift 4 celui-ci a fonctionné sans aucun bogue:

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
}

et si vous voulez aussi limiter les caractères:

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

n'oubliez pas d'ajouter le nombre de lignes dont vous voulez que la limite soit en viewDidLoad:

txtView.textContainer.maximumNumberOfLines = 2
0
Ben Shabat