icerockdev / moko-mvvm

Model-View-ViewModel architecture components for mobile (android & ios) Kotlin Multiplatform development
https://moko.icerock.dev/
Apache License 2.0
995 stars 95 forks source link

Add livedata twoway bindings with text formatting #142

Open Alex009 opened 3 years ago

Alex009 commented 3 years ago

Now we implement textformatting support inside project, but we can implement support of formatting out of box. for example with integration of https://github.com/redmadrobot/input-mask-android and https://github.com/redmadrobot/input-mask-ios as we do inside our projects.

or with https://github.com/artemkrachulov/AKMaskField on iOS

or by custom code:

import Foundation
import UIKit

class DefaultTextFormatter: NSObject {

    public let textPattern: String

    /// Symbol that will be replace by input symbols
    public let patternSymbol: Character

    public init(textPattern: String,
                patternSymbol: Character = "#") {
        self.textPattern = textPattern
        self.patternSymbol = patternSymbol
    }

    func format(_ unformattedText: String?) -> String? {
        guard let unformattedText = unformattedText else { return nil }
        var formatted = String.init()
        var unformattedIndex = 0
        var patternIndex = 0

        while patternIndex < textPattern.count && unformattedIndex < unformattedText.count {
            guard let patternCharacter = textPattern.characterAt(patternIndex) else { break }
            if patternCharacter == patternSymbol {
                if let unformattedCharacter = unformattedText.characterAt(unformattedIndex) {
                    formatted.append(unformattedCharacter)
                }
                unformattedIndex += 1
            } else {
                formatted.append(patternCharacter)
            }
            patternIndex += 1
        }
        return formatted
    }

    func unformat(_ formatted: String?) -> String? {
        guard let formatted = formatted else { return nil }

        if textPattern.starts(with: formatted) {
            return formatted
        }
        var unformatted = String()
        var formattedIndex = 0

        while formattedIndex < formatted.count {
            if let formattedCharacter = formatted.characterAt(formattedIndex) {
                if formattedIndex >= textPattern.count {
                    unformatted.append(formattedCharacter)
                } else if formattedCharacter != textPattern.characterAt(formattedIndex) {
                    unformatted.append(formattedCharacter)
                }
                formattedIndex += 1
            }
        }
        return unformatted
    }
}

extension DefaultTextFormatter: UITextFieldDelegate {
    func textField(
        _ textField: UITextField,
        shouldChangeCharactersIn range: NSRange,
        replacementString string: String
    ) -> Bool {
        let newText = (textField.text as NSString?)?.replacingCharacters(in: range, with: string)
        let unformattedText = unformat(newText)

        // Обход проблемы, когда юзер начинает ввод с символа, с которого начинается паттерн
        // (не возможно было начать ввод с 8)
        if textPattern.starts(with: newText ?? "") {
            textField.text = newText
        } else {
            textField.text = format(unformattedText)?.uppercased()
        }

        textField.sendActions(for: UIControl.Event.editingChanged)
        return false
    }
}