Establecer la longitud máxima de caracteres de un UITextField en Swift

85

Sé que hay otros temas sobre esto, pero parece que no puedo averiguar cómo implementarlo.

Estoy tratando de limitar un UITextField a solo 5 caracteres

Preferiblemente alfanumérico y - y. y _

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

y

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 
}

Simplemente no sé cómo implementarlo o qué "campo de texto" debería cambiar por mi personalizado llamado UITextField

ishkur88
fuente
Implementación de Swift 4 stackoverflow.com/a/46513151/2303865
Leo Dabus
Alerta rápida: para acortar un Stringen Swift en estos días, finalmente puede simplemente .prefix (n)
Fattie

Respuestas:

134
  1. Su controlador de vista debe cumplir UITextFieldDelegate, como se muestra a continuación:

    class MyViewController: UIViewController, UITextFieldDelegate {
    
    }
    
  2. Configure el delegado de su campo de texto: myTextField.delegate = self

  3. Implemente el método en su controlador de vista: 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
}

Permitir que solo se ingrese un conjunto específico de caracteres en un campo de texto dado

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
}

Cómo programar un campo de texto de iOS que solo toma entrada numérica con una longitud máxima

Aladin
fuente
¡Muchas gracias por la pronta respuesta! Si configuro este campo de texto como delegado, ¿podré modificar otros campos de texto?
ishkur88
sí, y obtendrá el campo de texto en cuestión (que se está editando) como el primer parámetro textFielden el método func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
Aladin
Pero, ¿dónde colocaría el segundo parámetro? No vuelvo a hacer referencia a myTextField después de configurarlo como delegado.
ishkur88
Como si quisiera hacer otro campo de texto 0-9 solo para números de teléfono.
ishkur88
cada vez que se edita un campo de texto, shouldChangeCharactersInRangese llama a la devolución de llamada, esto es para todos los campos de texto, recibe la devolución de llamada en el mismo lugarshouldChangeCharactersInRange y dentro de este método puede saber qué campo de texto se está editando gracias al parámetro pasado textField, por ejemplo, puede dar un etiqueta para cada campo de texto y prueba dentro de shouldChangeCharactersInRangey para cada campo de texto realiza la validación del contenido
Aladin
113

Diciembre de 2017. Swift 4.

Tenga cuidado de que gran parte del código de ejemplo que verá en línea con respecto a este problema esté muy desactualizado .

Pegue lo siguiente en cualquier archivo Swift de su proyecto. Puedes nombrar el archivo como quieras, por ejemplo, "Handy.swift".

Esto finalmente soluciona uno de los problemas más estúpidos en iOS:

enter image description here

Sus campos de texto ahora tienen un .maxLength.

Está completamente bien establecer ese valor en el guión gráfico durante el desarrollo, o configurarlo en código mientras la aplicación se está ejecutando.

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

Es así de simple.


Nota al pie: en estos días, para truncar de forma segura un Stringin swift, simplemente.prefix(n)


Una versión única aún más simple ...

Lo anterior corrige todos los campos de texto en su proyecto.

Si solo desea que un campo de texto en particular se limite a decir "4", y eso es todo ...

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

¡Uf! Eso es todo al respecto.

(Solo por cierto, aquí hay un consejo muy útil similar relacionado con UIText View , https://stackoverflow.com/a/42333832/294884 )


Para el programador de TOC (como yo) ...

Como recuerda @LeoDabus, .prefix devuelve una subcadena. Si eres increíblemente cariñoso, esto

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

sería

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

¡Disfrutar!

Fattie
fuente
2
¡Parece legitimo! Gracias.
J. Doe
2
¿hay algo como esto para textView?
Dory
13
Que todo esto tenga que hacerse para lograr algo tan común y tan simple me deja boquiabierto. No podían simplemente darnos un simple textField.maxLength incorporado ... de todos modos, su solución es excelente, ¡gracias!
mylovemhz
2
La solución es útil, pero el mapa de campos de texto en la parte superior producirá un ciclo de retención.
Angel G. Olloqui
3
ADVERTENCIA PARA AQUELLOS QUE USAN ESTA SOLUCIÓN! Esta solución tiene algunos problemas. Una de ellas es que si el usuario escribe al principio del campo de texto, le vas a permitir que escriba el nuevo carácter y se quitará el último, además, el cursor saltará al último carácter del campo. Otro problema es que si configura el texto mediante programación, le permitirá establecer un texto más grande que el límite. Otro problema ocurre si deshace un cambio (CMD + Z con un teclado externo) se bloqueará si intentó agregar un dígito por encima del límite anteriormente.
juancazalla
26

Swift 4, simplemente usa:

public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    return range.location < 10
}
Сергей Билык
fuente
esta debería ser la respuesta seleccionada. Simple y funciona bien.
user832
9
No funciona. ¿Qué pasa si toca en el medio de la cadena y puede escribir más de X caracteres?
Slavcho
en lugar de range.location <10, puede usar textField.text.length <10. Esta solución es simple y elegante.
Saqib Saud
Puede usar esta solución: if textField.text? .Count> = 12 {return false}
Сергей Билык
1
no funciona cuando se pasa un texto, si desea trabajar en una acción anterior , debe agregarstring.count < MAX_LENGTH
Reza Dehnavi
12

De la misma manera que lo hizo Steven Schmatz pero usando 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
fuente
1
Buena respuesta. Apreciado.
Hasya
Gracias @Hasya
Pavlos
6

Creo que la extensión es más útil para esto. Vea la respuesta completa aquí

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
fuente
4

Otras soluciones publicadas anteriormente producen un ciclo de retención debido al mapa de campo de texto. Además, la maxLengthpropiedad debe ser anulable si no se establece en lugar de Int.maxconstrucciones artificiales ; y el objetivo se establecerá varias veces si se cambia maxLength.

Aquí una solución actualizada para Swift4 con un mapa débil para evitar fugas de memoria y otras correcciones

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
    }
}
Ángel G. Olloqui
fuente
Gracias por tu respuesta, ¿puedes explicar maxLengths por favor?
Keyhan Kamangar
3

Solución simple sin 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
fuente
2
La respuesta que estoy buscando: D
Ankur Lahiry
1
Esta es la solución, simple y elegante, sin código repetitivo.
zeeshan
3

Mi versión 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
fuente
Esta debería ser la respuesta aceptada. Funciona perfectamente, sin errores.
10623169
2

Tengo algo que agregar a la respuesta de Aladin:

  1. Su controlador de vista debe cumplir con UITextFieldDelegate

    class MyViewController: UIViewController, UITextViewDelegate {
    
    }
    
  2. Configure el delegado de su campo de texto: para configurar el delegado, puede controlar el arrastre desde el campo de texto a su controlador de vista en el guión gráfico. Creo que esto es preferible a configurarlo en código.

  3. Implemente el método en su controlador de vista: textField(_:shouldChangeCharactersInRange:replacementString:)

SwiftMatt
fuente
2

Doy una respuesta complementaria basada en @Frouo. Creo que su respuesta es la forma más hermosa. Porque es un control común que podemos reutilizar. Y aquí no hay problema de fugas.

    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
        }



    }
Víctor Choy
fuente
2

actualización para esta respuesta Fattie

Gracias

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
fuente
1

Trabajando en Swift4

// PASO 1 establece UITextFieldDelegate

    class SignUPViewController: UIViewController , UITextFieldDelegate {

       @IBOutlet weak var userMobileNoTextFiled: UITextField!

        override func viewDidLoad() {
            super.viewDidLoad()

// PASO 2 establecer delegado
userMobileNoTextFiled.delegate = self // establecer 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
    //    }

// PASO 3 llamar a func

        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
fuente
1

Esta respuesta es para Swift 4, y es bastante sencilla con la capacidad de dejar pasar el retroceso.

func textField(_ textField: UITextField, 
               shouldChangeCharactersIn range: NSRange, 
               replacementString string: String) -> Bool {
    return textField.text!.count < 10 || string == ""
}
CodeBender
fuente
Esto no maneja copiar y pegar
cornr
1

Simplemente verifique con el número de caracteres en la cadena

  1. Agregar un delegado para ver el controlador y un delegado
    class YorsClassName : UITextFieldDelegate {

    }
  1. comprobar la cantidad de caracteres permitidos para el campo de texto
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    if textField.text?.count == 1 {
        return false
    }
    return true
}

Nota: Aquí verifiqué solo los caracteres permitidos en textField

Akshay Digrase
fuente
1

Carácter de límite de TextField después de bloquear el texto en 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
fuente
1

Por si acaso, no olvide proteger el tamaño del rango antes de aplicarlo a la cuerda. De lo contrario, se bloqueará si el usuario hace esto:

Escriba el texto de longitud máxima Inserte algo (No se insertará nada debido a la limitación de longitud, pero iOS no lo sabe) Deshacer la inserción (Se bloquea, porque el rango será mayor que el tamaño real de la cadena)

Además, los usuarios de iOS 13 pueden activar esto accidentalmente mediante gestos

Le sugiero que agregue a su proyecto esto

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

Y utilícelo así:

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
}

De lo contrario, constantemente se bloqueará su aplicación

kikiwora
fuente
0

Aquí hay una alternativa de Swift 3.2+ que evita la manipulación innecesaria de cadenas. En este caso, la longitud máxima es 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
fuente
0

Utilizo este paso, primero establezca el campo de texto delegado en viewdidload.

    override func viewDidLoad() {
        super.viewDidLoad()

        textfield.delegate = self

    }

y luego shouldChangeCharactersIn después 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
fuente
0

Si tiene varios campos de texto que tienen varias verificaciones de longitud en una página, he encontrado una solución fácil y breve.

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
fuente