Input-mask-ios: Falsche Maske nach Bearbeitung angebracht

Erstellt am 25. Juli 2018  ·  11Kommentare  ·  Quelle: RedMadRobot/input-mask-ios

Ich habe folgende Masken eingestellt:
self.login.affineFormats = [ "{+7} ([000]) [000] [00] [00]", "{8} ([000]) [000]-[00]-[00]" ]

Wenn ich eine Telefonnummer mit +7-Maske eingebe und dann den Wert in ([000]) bearbeite, wechselt die Maske zu 8 anstelle von +7 und dann wird 7 in ([000]) eingefügt.

enhancement

Hilfreichster Kommentar

Ja, es hat es behoben, danke!

Alle 11 Kommentare

Vielleicht gibt es eine Möglichkeit, die Maske in textField zu "sperren", sobald sie mit einer der Masken initialisiert wurde?

Hallo @MrJox , danke für deinen Bericht.
Könnten Sie bitte die genauen Zustände des Textes in Ihrem Textfeld angeben, wenn dieser Fehler auftritt?

Ich kann es später heute nachverfolgen und Ihnen dann bei der Lösung helfen.

Ich bin mir nicht ganz sicher, was Sie mit Zuständen meinen, aber Folgendes passiert:

Ich habe eine Telefonnummer aus dem Cache gelesen und in TextField eingefügt (es ist ein benutzerdefiniertes JVFloat-TextField).
Die Telefonnummer ist +7 (926) 000-00-00 und dann setze ich den Zeiger hinter '2' und lösche diese Nummer. Als Ergebnis wird mein TextView-Inhalt in 8 (796) 000-00-00 konvertiert.

Aus irgendeinem Grund löscht es also + und behandelt 7 nicht als Maske, sondern als tatsächlichen Teil der Telefonnummer und setzt es in Klammern und fügt dann die 8-Maske hinzu.

Ja, deine Beschreibung funktioniert für mich, danke!
Bleiben Sie dran.

@MrJox Während ich an einigen Verbesserungen und Korrekturen arbeite, die für unser letztes Bibliotheksupdate erforderlich sind, habe ich eine schnelle Lösung für Ihr Problem zusammengestellt.

Tatsächlich handelt es sich um eine PolyMaskTextFieldDelegate -Modifikation mit einer benutzerdefinierten Affinitätsberechnungsmethode. Anstelle einer allgemeinen Textaffinität mit der Maske vergleicht diese Methode die Länge der Präfixschnittmenge, die Anzahl der Zeichen.

Dieser Code oder seine modifizierte Version könnte schließlich seinen Weg in die Bibliotheksquellen finden.

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, also ich mache jetzt folgendes:
<strong i="6">@IBOutlet</strong> var loginListener: PrefixAffinityMaskedTextFieldDelegate!

Aber es wird immer noch nicht funktionieren. Was mir aufgefallen ist, wenn Sie Ziffern in Klammern mit der Entf-Taste löschen, wird die Ziffer gelöscht und jetzt ist es eine Ziffer weniger in Klammern. Wenn ich jedoch mit der Rücktaste lösche, wird entweder die Ziffer von links oder die Ziffer von rechts platziert / verschoben die Anzahl der Ziffern in Klammern bleibt unverändert.

Was ich meine:
Original: +7 (916) 000-00-00
mit Entf-Taste: +7 (96) 000-00-00
mit Backspace-Taste: 8 (796) 000-00-00

Korrektur:
Jetzt scheint es zu funktionieren, aber wenn ich versuche, die Zeichenfolge im Textfeld zu löschen, habe ich nur das Zeichen „+“ übrig und dann stürzt meine App ab, wenn ich versuche, sie mit zu löschen

Thread 1: Schwerwiegender Fehler: Kann nicht über endIndex hinaus inkrementieren

@MrJox Ich bin mir nicht sicher, ob ich dich richtig verstanden habe.
Beachten Sie, dass es unter iOS keine Taste Del gibt, sondern nur Backspace . Lassen Sie sich nicht von der Emulation der virtuellen Tastatur täuschen.

In Bezug auf Ihren letzten Fehler können Sie den folgenden Patch für den Grenzfall in Betracht ziehen:

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

}

Ja, es hat es behoben, danke!

@MrJox Ich werde dieses Problem mit dem nächsten Bibliotheksupdate schließen.
Ich erwäge, dies als alternative Strategie zu den Bibliotheksquellen hinzuzufügen.

@MrJox optimierte & vereinfachte Version:

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 enthält dies als Feature.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

osterlind picture osterlind  ·  3Kommentare

LinusGeffarth picture LinusGeffarth  ·  4Kommentare

SteynMarnus picture SteynMarnus  ·  11Kommentare

caioremedio picture caioremedio  ·  6Kommentare

DamascenoRafael picture DamascenoRafael  ·  4Kommentare