Input-mask-ios: Mauvais masque attaché après l'édition

Créé le 25 juil. 2018  ·  11Commentaires  ·  Source: RedMadRobot/input-mask-ios

J'ai le jeu de masques suivant :
self.login.affineFormats = [ "{+7} ([000]) [000] [00] [00]", "{8} ([000]) [000]-[00]-[00]" ]

Lorsque je tape un numéro de téléphone avec un masque +7, puis que je modifie la valeur dans ([000]), le masque passe à 8 au lieu de +7, puis 7 est mis à l'intérieur ([000])

enhancement

Commentaire le plus utile

Oui c'est corrigé, merci !

Tous les 11 commentaires

Peut-être existe-t-il un moyen de "verrouiller" le masque dans textField une fois qu'il a été initialisé avec l'un des masques?

Bonjour @MrJox , merci pour votre rapport.
Pourriez-vous s'il vous plaît fournir les états exacts du texte à l'intérieur de votre champ de texte lorsque cette erreur se produit ?

Je pourrai le tracer plus tard dans la journée et vous aider à trouver la solution.

Je ne sais pas exactement ce que vous entendez par états, mais voici ce qui se passe :

J'ai un numéro de téléphone lu à partir du cache et inséré dans textField (c'est un textField JVFloat personnalisé).
Le numéro de téléphone est le +7 (926) 000-00-00, puis je place le pointeur après '2' et supprime ce numéro. En conséquence, mon contenu textView est converti en 8 (796) 000-00-00.

Donc, pour une raison quelconque, il supprime + et traite 7 non pas comme un masque mais comme une partie réelle du numéro de téléphone et le met entre parenthèses, puis ajoute le masque 8.

Oui, votre description fonctionne pour moi, merci!
Restez à l'écoute.

@MrJox pendant que je travaille sur certaines améliorations et correctifs requis par notre dernière mise à jour de la bibliothèque, j'ai mis en place une solution rapide à votre problème.

En fait, il s'agit d'une modification PolyMaskTextFieldDelegate avec une méthode de calcul d'affinité personnalisée. Au lieu d'une affinité globale du texte avec le masque, cette méthode compare la longueur de l'intersection des préfixes, le nombre de caractères.

Ce code ou sa version modifiée pourrait éventuellement trouver son chemin vers les sources de la bibliothèque.

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, alors maintenant je fais ceci:
<strong i="6">@IBOutlet</strong> var loginListener: PrefixAffinityMaskedTextFieldDelegate!

Mais ça ne marchera toujours pas. Ce que j'ai remarqué, si vous supprimez des chiffres entre parenthèses avec la touche Suppr, le chiffre est supprimé et maintenant c'est un chiffre de moins entre parenthèses, mais lorsque je supprime avec le bouton Retour arrière, il place/déplace soit le chiffre de gauche, soit le chiffre de droite, donc le nombre de chiffres entre parenthèses reste inchangé.

Ce que je veux dire:
d'origine : +7 (916) 000-00-00
avec touche sup: +7 (96) 000-00-00
avec touche retour arrière : 8 (796) 000-00-00

Correction:
Maintenant, cela semble fonctionner, mais lorsque j'essaie de supprimer la chaîne dans le champ de texte, il ne me reste plus que le caractère '+', puis mon application se bloque lorsque j'essaie de la supprimer avec

Thread 1 : Erreur fatale : impossible d'incrémenter au-delà de endIndex

@MrJox Je ne suis pas sûr de vous avoir bien compris.
Attention, il n'y a pas de clé Del sur iOS, seulement Backspace . Ne laissez pas l'émulation de clavier virtuel vous tromper.

Concernant votre dernière erreur, vous pouvez envisager le correctif suivant pour le cas limite :

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

}

Oui c'est corrigé, merci !

@MrJox Je fermerai ce problème avec la prochaine mise à jour de la bibliothèque.
J'envisage d'ajouter ceci aux sources de la bibliothèque comme stratégie alternative.

@MrJox version optimisée et simplifiée :

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 contient ceci en tant que fonctionnalité.

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

Robuske picture Robuske  ·  4Commentaires

osterlind picture osterlind  ·  3Commentaires

DamascenoRafael picture DamascenoRafael  ·  4Commentaires

razalur picture razalur  ·  3Commentaires

beltik picture beltik  ·  6Commentaires