Input-mask-ios: 編集後に間違ったマスクが取り付けられました

作成日 2018年07月25日  ·  11コメント  ·  ソース: RedMadRobot/input-mask-ios

私は次のマスクを設定しています:
self.login.affineFormats = [ "{+7} ([000]) [000] [00] [00]", "{8} ([000]) [000]-[00]-[00]" ]

+7マスクで電話番号を入力し、([000])内の値を編集すると、マスクは+7ではなく8に切り替わり、7が([000])内に配置されます。

enhancement

最も参考になるコメント

ええ、それはそれを修正しました、ありがとう!

全てのコメント11件

マスクのいずれかで初期化された後、textFieldでマスクを「ロック」する方法があるのでしょうか。

こんにちは@MrJox 、あなたの報告に感謝します。
このエラーが発生したときのテキストフィールド内のテキストの正確な状態を教えてください。

今日の後半にそれを追跡し、解決策を支援することができます。

州が何を意味するのか完全にはわかりませんが、次のようになります。

キャッシュから電話番号を読み取り、textFieldに挿入します(これはカスタムJVFloat textFieldです)。
電話番号は+7(926)000-00-00で、「2」の後にポインターを設定して、この番号を削除します。 その結果、私のtextViewコンテンツは8(796)000-00-00に変換されます。

したがって、何らかの理由で+を削除し、7をマスクとしてではなく、電話番号の実際の部分として扱い、角かっこで囲んでから8マスクを追加します。

ええ、あなたの説明は私のために働きます、ありがとう!
乞うご期待。

@MrJoxは、前回のライブラリの更新に必要ないくつかの改善と修正に取り組んでいますが、問題の簡単な解決策をまとめました。

事実上、これはカスタムアフィニティ計算方法を使用したPolyMaskTextFieldDelegateの変更です。 このメソッドは、マスクとの全体的なテキストアフィニティの代わりに、プレフィックスの交差の長さ、つまり文字数を比較します。

このコードまたはその変更されたバージョンは、最終的にライブラリソースに到達する可能性があります。

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

うーん、だから今私はこれを行います:
<strong i="6">@IBOutlet</strong> var loginListener: PrefixAffinityMaskedTextFieldDelegate!

しかし、それでも機能しません。 私が気付いたのは、Delキーで括弧内の数字を削除すると、数字が削除され、括弧内の数字が1つ少なくなりますが、Backspaceボタンで削除すると、左からの数字または右からの数字のいずれかが配置/移動されるためです。角かっこ内の桁数は変更されません。

私が意味したのは:
オリジナル:+7(916)000-00-00
デルキー付き:+7(96)000-00-00
バックスペースキー付き:8(796)000-00-00

修正:
今は動作しているようですが、textFieldの文字列を削除しようとすると、「+」文字しか残っていないので、で削除しようとするとアプリがクラッシュします

スレッド1:致命的なエラー:endIndexを超えてインクリメントできません

@MrJox私はあなたを正しく理解したかどうかわかりません。
iOSにはDelキーはなく、 Backspaceのみであることに注意してください。 仮想キーボードエミュレーションにだまされてはいけません。

最後のエラーに関しては、エッジケースの次のパッチを検討してください。

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

}

ええ、それはそれを修正しました、ありがとう!

@MrJox次のライブラリアップデートでこの問題を解決します。
別の戦略として、これをライブラリソースに追加することを検討しています。

@MrJoxの最適化および簡略化されたバージョン:

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には、これが機能として含まれています。

このページは役に立ちましたか?
0 / 5 - 0 評価

関連する問題

SteynMarnus picture SteynMarnus  ·  11コメント

LinusGeffarth picture LinusGeffarth  ·  4コメント

TikhonovAlexander picture TikhonovAlexander  ·  3コメント

KompoD picture KompoD  ·  5コメント

beltik picture beltik  ·  6コメント