Input-mask-ios: Máscara incorrecta adjunta después de editar

Creado en 25 jul. 2018  ·  11Comentarios  ·  Fuente: RedMadRobot/input-mask-ios

Tengo las siguientes máscaras establecidas:
self.login.affineFormats = [ "{+7} ([000]) [000] [00] [00]", "{8} ([000]) [000]-[00]-[00]" ]

Cuando escribo un número de teléfono con máscara +7 y luego edito el valor dentro ([000]), la máscara cambia a 8 en lugar de +7 y luego se coloca 7 dentro ([000])

enhancement

Comentario más útil

Sí, lo solucionó, ¡gracias!

Todos 11 comentarios

¿Tal vez haya una forma de "bloquear" la máscara en textField una vez que se inicializó con alguna de las máscaras?

Hola @MrJox , gracias por tu informe.
¿Podría proporcionar los estados exactos del texto dentro de su campo de texto cuando ocurre este error?

Podré rastrearlo más tarde hoy y luego ayudarlo con la solución.

No estoy absolutamente seguro de lo que quiere decir con estados, pero esto es lo que sucede:

Tengo un número de teléfono leído del caché e insertado en textField (es un JVFloat textField personalizado).
El número de teléfono es +7 (926) 000-00-00 y luego configuro el puntero después de '2' y elimino este número. Como resultado, mi contenido de vista de texto se convierte en 8 (796) 000-00-00.

Entonces, por alguna razón, elimina + y trata a 7 no como una máscara, sino como una parte real del número de teléfono, lo pone entre paréntesis y luego agrega la máscara 8.

Sí, tu descripción funciona para mí, ¡gracias!
Manténganse al tanto.

@MrJox mientras trabajo en algunas mejoras y correcciones requeridas por nuestra última actualización de la biblioteca, he creado una solución rápida para su problema.

Efectivamente, es una modificación de PolyMaskTextFieldDelegate con un método de cálculo de afinidad personalizado. En lugar de una afinidad general del texto con la máscara, este método compara la longitud de la intersección de los prefijos, el recuento de caracteres.

Este código o su versión modificada podría llegar a las fuentes de la biblioteca con el tiempo.

import Foundation
import UIKit

import InputMask

<strong i="10">@IBDesignable</strong>
class PrefixAffinityMaskedTextFieldDelegate: MaskedTextFieldDelegate {
    public var affineFormats: [String]

    public init(primaryFormat: String, affineFormats: [String]) {
        self.affineFormats = affineFormats
        super.init(format: primaryFormat)
    }

    public override init(format: String) {
        self.affineFormats = []
        super.init(format: format)
    }

    override open func put(text: String, into field: UITextField) {
        let mask: Mask = pickMask(
            forText: text,
            caretPosition: text.endIndex,
            autocomplete: autocomplete
        )

        let result: Mask.Result = mask.apply(
            toText: CaretString(
                string: text,
                caretPosition: text.endIndex
            ),
            autocomplete: autocomplete
        )

        field.text = result.formattedText.string
        field.caretPosition = result.formattedText.string.distance(
            from: result.formattedText.string.startIndex,
            to: result.formattedText.caretPosition
        )

        listener?.textField?(field, didFillMandatoryCharacters: result.complete, didExtractValue: result.extractedValue)
    }

    override open func deleteText(inRange range: NSRange, inTextInput field: UITextInput) -> Mask.Result {
        let text: String = replaceCharacters(
            inText: field.allText,
            range: range,
            withCharacters: ""
        )

        let mask: Mask = pickMask(
            forText: text,
            caretPosition: text.index(text.startIndex, offsetBy: range.location),
            autocomplete: false
        )

        let result: Mask.Result = mask.apply(
            toText: CaretString(
                string: text,
                caretPosition: text.index(text.startIndex, offsetBy: range.location)
            ),
            autocomplete: false
        )

        field.allText = result.formattedText.string
        field.caretPosition = range.location

        return result
    }

    override open func modifyText(
        inRange range: NSRange,
        inTextInput field: UITextInput,
        withText text: String
    ) -> Mask.Result {
        let updatedText: String = replaceCharacters(
            inText: field.allText,
            range: range,
            withCharacters: text
        )

        let mask: Mask = pickMask(
            forText: updatedText,
            caretPosition: updatedText.index(updatedText.startIndex, offsetBy: field.caretPosition + text.count),
            autocomplete: autocomplete
        )

        let result: Mask.Result = mask.apply(
            toText: CaretString(
                string: updatedText,
                caretPosition: updatedText.index(updatedText.startIndex, offsetBy: field.caretPosition + text.count)
            ),
            autocomplete: autocomplete
        )

        field.allText = result.formattedText.string
        field.caretPosition = result.formattedText.string.distance(
            from: result.formattedText.string.startIndex,
            to: result.formattedText.caretPosition
        )

        return result
    }

    func pickMask(forText text: String, caretPosition: String.Index, autocomplete: Bool) -> Mask {
        let primaryAffinity: Int = calculateAffinity(
            ofMask: mask,
            forText: text,
            caretPosition: caretPosition,
            autocomplete: autocomplete
        )

        var masks: [(Mask, Int)] = affineFormats.map { (affineFormat: String) -> (Mask, Int) in
            let mask:     Mask = try! Mask.getOrCreate(withFormat: affineFormat, customNotations: customNotations)
            let affinity: Int  = calculateAffinity(
                ofMask: mask,
                forText: text,
                caretPosition: caretPosition,
                autocomplete: autocomplete
            )

            return (mask, affinity)
        }

        masks.sort { (left: (Mask, Int), right: (Mask, Int)) -> Bool in
            return left.1 > right.1
        }

        var insertIndex: Int = -1

        for (index, maskAffinity) in masks.enumerated() {
            if primaryAffinity >= maskAffinity.1 {
                insertIndex = index
                break
            }
        }

        if (insertIndex >= 0) {
            masks.insert((mask, primaryAffinity), at: insertIndex)
        } else {
            masks.append((mask, primaryAffinity))
        }

        return masks.first!.0
    }

    func calculateAffinity(
        ofMask mask: Mask,
        forText text: String,
        caretPosition: String.Index,
        autocomplete: Bool
    ) -> Int {
        return mask.apply(
            toText: CaretString(
                string: text,
                caretPosition: caretPosition
            ),
            autocomplete: autocomplete
        ).formattedText.string.prefixIntersection(with: text).count
    }

    func replaceCharacters(inText text: String, range: NSRange, withCharacters newText: String) -> String {
        if 0 < range.length {
            let result = NSMutableString(string: text)
            result.replaceCharacters(in: range, with: newText)
            return result as String
        } else {
            let result = NSMutableString(string: text)
            result.insert(newText, at: range.location)
            return result as String
        }
    }

}


extension String {

    func prefixIntersection(with string: String) -> Substring {
        let lhsStartIndex = startIndex
        var lhsEndIndex = startIndex
        let rhsStartIndex = string.startIndex
        var rhsEndIndex = string.startIndex

        while (self[lhsStartIndex...lhsEndIndex] == string[rhsStartIndex...rhsEndIndex]) {
            lhsEndIndex = lhsEndIndex != endIndex ? index(after: lhsEndIndex) : endIndex
            rhsEndIndex = rhsEndIndex != string.endIndex ? string.index(after: rhsEndIndex) : endIndex

            if (lhsEndIndex == endIndex || rhsEndIndex == string.endIndex) {
                return self[lhsStartIndex..<lhsEndIndex]
            }
        }

        return self[lhsStartIndex..<lhsEndIndex]
    }

}


extension UITextInput {
    var allText: String {
        get {
            guard let all: UITextRange = allTextRange
                else { return "" }
            return self.text(in: all) ?? ""
        }

        set(newText) {
            guard let all: UITextRange = allTextRange
                else { return }
            self.replace(all, withText: newText)
        }
    }

    var caretPosition: Int {
        get {
            if let responder = self as? UIResponder {
                // Workaround for non-optional `beginningOfDocument`, which could actually be nil if field doesn't have focus
                guard responder.isFirstResponder
                    else { return allText.count }
            }

            if let range: UITextRange = selectedTextRange {
                let selectedTextLocation: UITextPosition = range.start
                return offset(from: beginningOfDocument, to: selectedTextLocation)
            } else {
                return 0
            }
        }

        set(newPosition) {
            if let responder = self as? UIResponder {
                // Workaround for non-optional `beginningOfDocument`, which could actually be nil if field doesn't have focus
                guard responder.isFirstResponder
                    else { return }
            }

            if newPosition > allText.count {
                return
            }

            let from: UITextPosition = position(from: beginningOfDocument, offset: newPosition)!
            let to:   UITextPosition = position(from: from, offset: 0)!
            selectedTextRange = textRange(from: from, to: to)
        }
    }

    var allTextRange: UITextRange? {
        return self.textRange(from: self.beginningOfDocument, to: self.endOfDocument)
    }
}

Hm, entonces ahora hago esto:
<strong i="6">@IBOutlet</strong> var loginListener: PrefixAffinityMaskedTextFieldDelegate!

Pero todavía no funcionará. Lo que noté, si elimina dígitos entre paréntesis con la tecla Supr, el dígito se elimina y ahora es un dígito menos entre paréntesis; sin embargo, cuando elimino con el botón Retroceso, coloca/mueve el dígito de la izquierda o el dígito de la derecha, por lo que la cantidad de dígitos dentro de los corchetes permanece sin cambios.

Lo que quiero decir:
originales: +7 (916) 000-00-00
con tecla del: +7 (96) 000-00-00
con tecla de retroceso: 8 (796) 000-00-00

Corrección:
Ahora parece funcionar, sin embargo, cuando intento eliminar la cadena en el campo de texto, solo me queda el carácter '+' y luego mi aplicación falla cuando intento eliminarla con

Subproceso 1: error fatal: no se puede incrementar más allá de endIndex

@MrJox No estoy seguro de haberte entendido correctamente.
Tenga en cuenta que no hay una tecla Del en iOS, solo Backspace . No dejes que la emulación de teclado virtual te engañe.

Con respecto a su último error, puede considerar el siguiente parche para el caso de borde:

extension String {

    func prefixIntersection(with string: String) -> Substring {
        guard !self.isEmpty && !string.isEmpty
        else { return "" }

        let lhsStartIndex = startIndex
        var lhsEndIndex = startIndex
        let rhsStartIndex = string.startIndex
        var rhsEndIndex = string.startIndex

        while (self[lhsStartIndex...lhsEndIndex] == string[rhsStartIndex...rhsEndIndex]) {
            lhsEndIndex = lhsEndIndex != endIndex ? index(after: lhsEndIndex) : endIndex
            rhsEndIndex = rhsEndIndex != string.endIndex ? string.index(after: rhsEndIndex) : endIndex

            if (lhsEndIndex == endIndex || rhsEndIndex == string.endIndex) {
                return self[lhsStartIndex..<lhsEndIndex]
            }
        }

        return self[lhsStartIndex..<lhsEndIndex]
    }

}

Sí, lo solucionó, ¡gracias!

@MrJox Cerraré este problema con la próxima actualización de la biblioteca.
Estoy considerando agregar esto a las fuentes de la biblioteca como una estrategia alternativa.

@MrJox versión optimizada y simplificada:

extension String {

    func prefixIntersection(with string: String) -> Substring {
        var lhsIndex = startIndex
        var rhsIndex = string.startIndex

        while lhsIndex != endIndex && rhsIndex != string.endIndex {
            if self[...lhsIndex] == string[...rhsIndex] {
                lhsIndex = index(after: lhsIndex)
                rhsIndex = string.index(after: rhsIndex)
            } else {
                return self[..<lhsIndex]
            }
        }

        return self[..<lhsIndex]
    }

}

4.0.0 contiene esto como una característica.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

Robuske picture Robuske  ·  4Comentarios

razalur picture razalur  ·  3Comentarios

KompoD picture KompoD  ·  5Comentarios

TikhonovAlexander picture TikhonovAlexander  ·  3Comentarios

beltik picture beltik  ·  6Comentarios