Defina o comprimento máximo de caracteres de um UITextField em Swift

88

Eu sei que existem outros tópicos sobre isso, mas não consigo descobrir como implementá-lo.

Estou tentando limitar um UITextField a apenas 5 caracteres

De preferência alfanumérico e - e. e _

Eu vi este código

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange,
                       replacementString string: String) -> Bool
{
    let maxLength = 4
    let currentString: NSString = textField.text
    let newString: NSString =
             currentString.stringByReplacingCharactersInRange(range, withString: string)
    return newString.length <= maxLength
}

e

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {

    let length = count(textField.text.utf16) + count(string.utf16) - range.length
    return length <= 10 
}

Só não sei como realmente implementá-lo ou qual "campo de texto" devo trocar por meu personalizado chamado UITextField

ishkur88
fonte
Implementação do Swift 4 stackoverflow.com/a/46513151/2303865
Leo Dabus
Alerta rápido - para encurtar um Stringno Swift atualmente, você pode finalmente apenas .prefix (n)
Fattie

Respostas:

136
  1. Seu controlador de visualização deve estar em conformidade com UITextFieldDelegate, como abaixo:

    class MyViewController: UIViewController, UITextFieldDelegate {
    
    }
    
  2. Defina o delegado do seu campo de texto: myTextField.delegate = self

  3. Implemente o método em seu controlador de visualização: textField(_:shouldChangeCharactersInRange:replacementString:)

Todos juntos:

class MyViewController: UIViewController,UITextFieldDelegate  //set delegate to class 

@IBOutlet var mytextField: UITextField             //  textfield variable 

override func viewDidLoad() {
    super.viewDidLoad()
    mytextField.delegate = self                  //set delegate
}


func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange,
                       replacementString string: String) -> Bool
{
    let maxLength = 4
    let currentString: NSString = textField.text
    let newString: NSString =
             currentString.stringByReplacingCharactersInRange(range, withString: string)
    return newString.length <= maxLength
}

Para Swift 4

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    let maxLength = 1
    let currentString: NSString = (textField.text ?? "") as NSString
    let newString: NSString =
        currentString.replacingCharacters(in: range, with: string) as NSString
    return newString.length <= maxLength
}

Permitindo que apenas um determinado conjunto de caracteres seja inserido em um determinado campo de texto

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
  var result = true
  
  if mytextField == numberField {
    if count(string) > 0 {
      let disallowedCharacterSet = NSCharacterSet(charactersInString: "0123456789.-").invertedSet
      let replacementStringIsLegal = string.rangeOfCharacterFromSet(disallowedCharacterSet) == nil
      result = replacementStringIsLegal
    }
  }
 
  return result
}

Como programar um campo de texto iOS que aceita apenas entrada numérica com um comprimento máximo

Aladin
fonte
Muito obrigado pela pronta resposta! Se eu definir este campo de texto como delegado, poderei modificar outros campos de texto?
ishkur88
sim, e você obterá o campo de texto em questão (em edição) como o primeiro parâmetro textFieldno método func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
Aladin
Mas onde eu colocaria o segundo parâmetro? Não faço referência a myTextField novamente depois de defini-lo como um delegado.
ishkur88
Como se eu quisesse fazer outro campo de texto 0-9 apenas para números de telefone.
ishkur88
cada vez que um textfield está sendo editado, o callback shouldChangeCharactersInRangeé chamado, isto é para todos os textfields, você recebe o callback no mesmo lugar shouldChangeCharactersInRangee dentro deste método você pode saber qual textfield está sendo editado graças ao parâmetro passado textFieldvocê pode por exemplo dar um tag para cada campo de texto e teste dentro do shouldChangeCharactersInRangee para cada campo de texto realizar a validação do conteúdo
Aladin
117

Swift moderno

Observe que grande parte do código de exemplo online referente a esse problema está extremamente desatualizado .

Cole o seguinte em qualquer arquivo Swift em seu projeto. (Você pode nomear o arquivo com qualquer coisa, por exemplo, "Handy.swift".)

Isso finalmente corrige um dos problemas mais estúpidos do iOS:

insira a descrição da imagem aqui

Seus campos de texto agora têm um .maxLength.

É totalmente normal definir esse valor no storyboard durante o desenvolvimento ou defini-lo no código enquanto o aplicativo está em execução.

// simply have this in any Swift file, say, Handy.swift

import UIKit
private var __maxLengths = [UITextField: Int]()
extension UITextField {
    @IBInspectable var maxLength: Int {
        get {
            guard let l = __maxLengths[self] else {
               return 150 // (global default-limit. or just, Int.max)
            }
            return l
        }
        set {
            __maxLengths[self] = newValue
            addTarget(self, action: #selector(fix), for: .editingChanged)
        }
    }
    func fix(textField: UITextField) {
        let t = textField.text
        textField.text = t?.prefix(maxLength)
    }
}

É simples assim.


Nota de rodapé - hoje em dia, para truncar com segurança um Stringcom rapidez, você simplesmente.prefix(n)


Uma versão única ainda mais simples ...

O procedimento acima corrige todos os campos de texto em seu projeto.

Se você deseja apenas que um determinado campo de texto seja simplesmente limitado a dizer "4", e pronto ...

class PinCodeEntry: UITextField {
    
    override func didMoveToSuperview() {
        
        super.didMoveToSuperview()
        addTarget(self, action: #selector(fixMe), for: .editingChanged)
    }
    
    @objc private func fixMe() { text = text?.prefix(4) }
}

Ufa! Isso é tudo que há para fazer.

(A propósito, aqui está uma dica semelhante muito útil relacionada ao UIText View , https://stackoverflow.com/a/42333832/294884 )


Para o programador OCD (como eu) ...

Como @LeoDabus lembra, .prefixretorna uma substring. Se você é extremamente atencioso, este

let t = textField.text
textField.text = t?.prefix(maxLength)

seria

if let t: String = textField.text {
    textField.text = String(t.prefix(maxLength))
}

Apreciar!

Fattie
fonte
2
Parece legítimo! Obrigado.
J. Doe
13
Que tudo isso tenha que ser feito para conseguir algo tão comum e tão simples me deixa boquiaberto. Eles não podiam simplesmente nos fornecer um textField.maxLength simples integrado ... de qualquer maneira, sua solução é ótima, obrigado!
mylovemhz
1
Uau, a melhor dica que já tive sobre o SO. Eu votaria 100 se pudesse!
jonathan3087
2
A solução é útil, mas o mapa de campos de texto no topo realmente produzirá um ciclo de retenção.
Angel G. Olloqui
3
AVISO PARA OS QUE UTILIZAM ESTA SOLUÇÃO! Esta solução tem alguns problemas. Uma delas é que se o usuário digitar no início do textField, você vai permitir que ele digite o novo caractere e o último será removido, também, o cursor irá pular para o último caractere do campo. Outro problema é que se você definir o texto programaticamente, isso permitirá que você defina um texto maior do que o limite. Outro problema ocorre se você desfazer uma alteração (CMD + Z com um teclado externo), ele travará se você tentar adicionar um dígito acima do limite anteriormente.
juancazalla
25

Swift 4, basta usar:

public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    return range.location < 10
}
Сергей Билык
fonte
esta deve ser a resposta selecionada. Simples e funciona bem.
user832
9
Não funciona. E se você tocar no meio da string e puder digitar mais de X caracteres.
Slavcho
em vez de range.location <10, você pode usar textField.text.length <10. Esta solução é simples e elegante.
Saqib Saud
Você pode usar esta solução: if textField.text? .Count> = 12 {return false}
Сергей Билык
1
não funciona quando é passado um texto; se você quiser trabalhar na ação anterior , deve adicionarstring.count < MAX_LENGTH
Reza Dehnavi
12

Da mesma forma que Steven Schmatz fez, mas usando o Swift 3.0:

//max Length
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
               replacementString string: String) -> Bool
{
    let maxLength = 4
    let currentString: NSString = textField.text! as NSString
    let newString: NSString = currentString.replacingCharacters(in: range, with: string) as NSString
    return newString.length <= maxLength
}
Pavlos
fonte
1
Boa resposta. Estimado.
Hasya
Obrigado @Hasya
Pavlos
9

Para Swift 5:
basta escrever uma linha para definir o comprimento máximo de caracteres:

 self.textField.maxLength = 10

Para mais detalhes clique aqui

Crédito: http://www.swiftdevcenter.com/max-character-limit-of-uitextfield-and-allowed-characters-swift/

Ashish Chauhan
fonte
Embora essa solução possa parecer direta, na verdade, é necessário muito mais código para implementá-la. Essa resposta não é muito útil por si só, e a adição de uma explicação e a inclusão de outras partes relevantes do código podem ser úteis.
FontFamily
6

Acho que a extensão é mais útil para isso. Veja a resposta completa aqui

private var maxLengths = [UITextField: Int]()

// 2
extension UITextField {

  // 3
  @IBInspectable var maxLength: Int {
    get {
      // 4
      guard let length = maxLengths[self] else {
        return Int.max
      }
      return length
    }
    set {
      maxLengths[self] = newValue
      // 5
      addTarget(
        self,
        action: #selector(limitLength),
        forControlEvents: UIControlEvents.EditingChanged
      )
    }
  }

  func limitLength(textField: UITextField) {
    // 6
    guard let prospectiveText = textField.text
      where prospectiveText.characters.count > maxLength else {
        return
    }

    let selection = selectedTextRange
    // 7
    text = prospectiveText.substringWithRange(
      Range<String.Index>(prospectiveText.startIndex ..< prospectiveText.startIndex.advancedBy(maxLength))
    )
    selectedTextRange = selection
  }

}
ZaEeM ZaFaR
fonte
4

Outras soluções postadas acima produzem um ciclo de retenção devido ao mapa de campo de texto. Além disso, a maxLengthpropriedade deve ser anulável se não for definida em vez de Int.maxconstruções artificiais ; e o destino será definido várias vezes se maxLength for alterado.

Aqui, uma solução atualizada para Swift4 com um mapa fraco para evitar vazamentos de memória e outras correções

private var maxLengths = NSMapTable<UITextField, NSNumber>(keyOptions: NSPointerFunctions.Options.weakMemory, valueOptions: NSPointerFunctions.Options.strongMemory)

extension UITextField {

    var maxLength: Int? {
        get {
            return maxLengths.object(forKey: self)?.intValue
        }
        set {
            removeTarget(self, action: #selector(limitLength), for: .editingChanged)
            if let newValue = newValue {
                maxLengths.setObject(NSNumber(value: newValue), forKey: self)
                addTarget(self, action: #selector(limitLength), for: .editingChanged)
            } else {
                maxLengths.removeObject(forKey: self)
            }
        }
    }

    @IBInspectable var maxLengthInspectable: Int {
        get {
            return maxLength ?? Int.max
        }
        set {
            maxLength = newValue
        }
    }

    @objc private func limitLength(_ textField: UITextField) {
        guard let maxLength = maxLength, let prospectiveText = textField.text, prospectiveText.count > maxLength else {
            return
        }
        let selection = selectedTextRange
        text = String(prospectiveText[..<prospectiveText.index(from: maxLength)])
        selectedTextRange = selection
    }
}
Angel G. Olloqui
fonte
Obrigado pela sua resposta, você pode explicar maxLengths, por favor?
Keyhan Kamangar de
4

Solução simples sem usar delegado:

TEXT_FIELD.addTarget(self, action: #selector(editingChanged(sender:)), for: .editingChanged)


@objc private func editingChanged(sender: UITextField) {

        if let text = sender.text, text.count >= MAX_LENGHT {
            sender.text = String(text.dropLast(text.count - MAX_LENGHT))
            return
        }
}
ober
fonte
2
A resposta que procuro: D
Ankur Lahiry,
1
Esta é a solução, simples e elegante, sem código clichê.
zeeshan
3

Minha versão do Swift 4 de shouldChangeCharactersIn

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
               replacementString string: String) -> Bool {

        guard let preText = textField.text as NSString?,
            preText.replacingCharacters(in: range, with: string).count <= MAX_TEXT_LENGTH else {
            return false
        }

        return true
    }
Aviran
fonte
Esta deve ser a resposta aceita. Funciona perfeitamente, sem bugs.
10623169
2

Tenho algo a acrescentar à resposta de Aladin:

  1. Seu controlador de visualização deve estar em conformidade com UITextFieldDelegate

    class MyViewController: UIViewController, UITextViewDelegate {
    
    }
    
  2. Defina o delegado do seu campo de texto: para definir o delegado, você pode controlar o arrasto do campo de texto para o controlador de visualização no storyboard. Eu acho que isso é preferível a defini-lo em código

  3. Implemente o método em seu controlador de visualização: textField(_:shouldChangeCharactersInRange:replacementString:)

SwiftMatt
fonte
2

Dou uma resposta complementar baseada em @Frouo. Acho que a resposta dele é a mais linda. Porque é um controle comum que podemos reutilizar. E não há problema de vazamento aqui.

    private var kAssociationKeyMaxLength: Int = 0

    extension UITextField {

        @IBInspectable var maxLength: Int {
            get {
                if let length = objc_getAssociatedObject(self, &kAssociationKeyMaxLength) as? Int {
                    return length
                } else {
                    return Int.max
                }
            }
            set {
                objc_setAssociatedObject(self, &kAssociationKeyMaxLength, newValue, .OBJC_ASSOCIATION_RETAIN)
                self.addTarget(self, action: #selector(checkMaxLength), for: .editingChanged)
            }
        }

//The method is used to cancel the check when use Chinese Pinyin input method.
        //Becuase the alphabet also appears in the textfield when inputting, we should cancel the check.
        func isInputMethod() -> Bool {
            if let positionRange = self.markedTextRange {
                if let _ = self.position(from: positionRange.start, offset: 0) {
                    return true
                }
            }
            return false
        }


        func checkMaxLength(textField: UITextField) {

            guard !self.isInputMethod(), let prospectiveText = self.text,
                prospectiveText.count > maxLength
                else {
                    return
            }

            let selection = selectedTextRange
            let maxCharIndex = prospectiveText.index(prospectiveText.startIndex, offsetBy: maxLength)
            text = prospectiveText.substring(to: maxCharIndex)
            selectedTextRange = selection
        }



    }
Victor Choy
fonte
2

atualização para esta resposta Fattie

obrigado

extension UITextField {

    /// Runtime key
    private struct AssociatedKeys {
        /// max lenght key
        static var maxlength: UInt8 = 0
        /// temp string key
        static var tempString: UInt8 = 0
    }

    /// Limit the maximum input length of the textfiled
    @IBInspectable var maxLength: Int {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.maxlength) as? Int ?? 0
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.maxlength, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            addTarget(self, action: #selector(handleEditingChanged(textField:)), for: .editingChanged)
        }
    }

    /// temp string
    private var tempString: String? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.tempString) as? String
        }
        set {
            objc_setAssociatedObject(self, &AssociatedKeys.tempString, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    /// When the text changes, process the amount of text in the input box so that its length is within the controllable range.
    @objc private func handleEditingChanged(textField: UITextField) {

        /// Special Processing for Chinese Input Method
        guard markedTextRange == nil else { return }

        if textField.text?.count == maxLength {

            /// SET lastQualifiedString where text length == max lenght
            tempString = textField.text
        } else if textField.text?.count ?? 0 < maxLength {

            /// clear lastQualifiedString when text lengeht > maxlength
            tempString = nil
        }

        /// keep current text range in arcgives
        let archivesEditRange: UITextRange?

        if textField.text?.count ?? 0 > maxLength {

            /// if text length > maxlength,remove last range,to move to -1 postion.
            let position = textField.position(from: safeTextPosition(selectedTextRange?.start), offset: -1) ?? textField.endOfDocument
            archivesEditRange = textField.textRange(from: safeTextPosition(position), to: safeTextPosition(position))
        } else {

            /// just set current select text range
            archivesEditRange = selectedTextRange
        }

        /// main handle string max length
        textField.text = tempString ?? String((textField.text ?? "").prefix(maxLength))

        /// last config edit text range
        textField.selectedTextRange = archivesEditRange
    }

    /// get safe textPosition
    private func safeTextPosition(_ optionlTextPosition: UITextPosition?) -> UITextPosition {

        /* beginningOfDocument -> The end of the the text document. */
        return optionlTextPosition ?? endOfDocument
    }
}
zheng
fonte
1

Trabalhando em Swift4

// PASSO 1 definir UITextFieldDelegate

    class SignUPViewController: UIViewController , UITextFieldDelegate {

       @IBOutlet weak var userMobileNoTextFiled: UITextField!

        override func viewDidLoad() {
            super.viewDidLoad()

// PASSO 2 definir delegado
userMobileNoTextFiled.delegate = self // definir delegado}

     func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    //        guard let text = userMobileNoTextFiled.text else { return true }
    //        let newLength = text.count + string.count - range.length
    //        return newLength <= 10
    //    }

// PASSO 3 chamar função

        func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
            let maxLength = 10          // set your need
            let currentString: NSString = textField.text! as NSString
            let newString: NSString =
                currentString.replacingCharacters(in: range, with: string) as NSString
            return newString.length <= maxLength
        }
    }
Keshav Gera
fonte
1

Esta resposta é para o Swift 4 e é bastante simples com a capacidade de retroceder.

func textField(_ textField: UITextField, 
               shouldChangeCharactersIn range: NSRange, 
               replacementString string: String) -> Bool {
    return textField.text!.count < 10 || string == ""
}
CodeBender
fonte
Isto não suporta copiar e colar
dia
1

Basta verificar o número de caracteres na string

  1. Adicione um delegado para ver o controlador e atribuir um delegado
    class YorsClassName : UITextFieldDelegate {

    }
  1. verifique o número de caracteres permitidos para o campo de texto
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    if textField.text?.count == 1 {
        return false
    }
    return true
}

Nota: Aqui, verifiquei apenas os caracteres permitidos em textField

Akshay Digrase
fonte
1

Caractere de limite de TextField após bloquear o texto no Swift 4

func textField(_ textField: UITextField, shouldChangeCharactersIn range: 
    NSRange,replacementString string: String) -> Bool
{


    if textField == self.txtDescription {
        let maxLength = 200
        let currentString: NSString = textField.text! as NSString
        let newString: NSString = currentString.replacingCharacters(in: range, with: string) as NSString
        return newString.length <= maxLength
    }

    return true


}
Praveen Reddy
fonte
1

Por precaução, não se esqueça de proteger o tamanho do alcance antes de aplicá-lo à corda. Caso contrário, você travará se o usuário fizer isso:

Digite o texto de comprimento máximo Insira algo (nada será inserido devido à limitação de comprimento, mas o iOS não sabe sobre isso) Desfaça a inserção (você obtém travamento, pois o intervalo será maior do que o tamanho real da string)

Além disso, os usuários do iOS 13 podem acidentalmente acionar isso por meio de gestos

Eu sugiro que você adicione ao seu projeto este

extension String {
    func replace(with text: String, in range: NSRange) -> String? {
        guard range.location + range.length <= self.count else { return nil }
        return (self as NSString).replacingCharacters(in: range, with: text)
    }
}

E use-o assim:

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    guard let newText = textView.text.replace(with: text, in: range) else { return false }
    return newText.count < maxNumberOfCharacters
}

Caso contrário, você travará constantemente em seu aplicativo

kikiwora
fonte
0

Aqui está uma alternativa do Swift 3.2+ que evita a manipulação desnecessária de strings. Nesse caso, o comprimento máximo é 10:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    let text = textField.text ?? ""

    return text.count - range.length + string.count <= 10
}
Greg Brown
fonte
0

Eu uso esta etapa, primeiro Set delegate texfield em viewdidload.

    override func viewDidLoad() {
        super.viewDidLoad()

        textfield.delegate = self

    }

e, em seguida, deveChangeCharactersIn depois de incluir UITextFieldDelegate.

extension viewController: UITextFieldDelegate {
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
                let newLength = (textField.text?.utf16.count)! + string.utf16.count - range.length
                if newLength <= 8 { 
                    return true
                } else {
                    return false
                }
            }
    }
Jeri PM
fonte
0

Se você tiver vários textField com várias verificações de comprimento em uma página, encontrei uma solução fácil e curta.

class MultipleTextField: UIViewController {

    let MAX_LENGTH_TEXTFIELD_A = 10
    let MAX_LENGTH_TEXTFIELD_B = 11

    lazy var textFieldA: UITextField = {
        let textField = UITextField()
        textField.tag = MAX_LENGTH_TEXTFIELD_A
        textField.delegate = self
        return textField
    }()
    lazy var textFieldB: UITextField = {
        let textField = UITextField()
        textField.tag = MAX_LENGTH_TEXTFIELD_B
        textField.delegate = self
        return textField
    }()
}

extension MultipleTextField: UITextFieldDelegate {
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        return (range.location < textField.tag) && (string.count < textField.tag)
    }
}
Reza Dehnavi
fonte