diff --git a/ios/AdvancedTextInputMaskDecoratorView.swift b/ios/AdvancedTextInputMaskDecoratorView.swift index 44b74fa..a0d3e14 100644 --- a/ios/AdvancedTextInputMaskDecoratorView.swift +++ b/ios/AdvancedTextInputMaskDecoratorView.swift @@ -14,95 +14,71 @@ import UIKit @objc(AdvancedTextInputViewDecoratorView) class AdvancedTextInputMaskDecoratorView: UIView { - @objc var textView: UITextField? - @objc var maskInputListener: MaskedTextInputListener? - @objc var lastDispatchedEvent: [String: String] = [:] - - @objc weak var delegate: AdvancedTextInputMaskDecoratorViewDelegate? - - @objc var textFieldDelegate: UITextFieldDelegate? + // MARK: - Public Properties @objc var onAdvancedMaskTextChange: RCTDirectEventBlock? + @objc weak var delegate: AdvancedTextInputMaskDecoratorViewDelegate? - @objc var onAdvancedMaskTextChangedCallback: ( - _ extracted: String, - _ formatted: String, - _ tailPlaceholder: String - ) -> Void { - { [weak self] extracted, formatted, tailPlaceholder in - guard let self = self else { return } - - let eventData: [String: String] = [ - "extracted": extracted, - "formatted": formatted, - "tailPlaceholder": tailPlaceholder, - ] - - if NSDictionary(dictionary: eventData).isEqual(to: lastDispatchedEvent) { - return - } + // MARK: - Private Properties - lastDispatchedEvent = eventData - if self.onAdvancedMaskTextChange != nil { - self.onAdvancedMaskTextChange!(eventData) - } - self.delegate?.onAdvancedMaskTextChange(eventData: eventData as NSDictionary) - } - } + private weak var textField: UITextField? + private var maskInputListener: MaskedTextInputListener? + private var lastDispatchedEvent: [String: String] = [:] + private var textFieldDelegate: UITextFieldDelegate? - @objc var primaryMaskFormat: NSString = "" { + @objc private var primaryMaskFormat: NSString = "" { didSet { maskInputListener?.primaryMaskFormat = primaryMaskFormat as String - maybeUpdateText(text: textView?.allText ?? "") + maybeUpdateText(text: textField?.allText ?? "") } } - @objc var autocomplete: Bool = true { + @objc private var autocomplete: Bool = true { didSet { maskInputListener?.autocomplete = autocomplete } } - @objc var autocompleteOnFocus: Bool = false { + @objc private var autocompleteOnFocus: Bool = false { didSet { maskInputListener?.autocompleteOnFocus = autocompleteOnFocus } } - @objc var allowSuggestions: Bool = true { + @objc private var allowSuggestions: Bool = true { didSet { maskInputListener?.allowSuggestions = allowSuggestions } } - @objc var autoSkip: Bool = false { + @objc private var autoSkip: Bool = false { didSet { maskInputListener?.autoskip = autoSkip } } - @objc var isRTL: Bool = false { + @objc private var isRTL: Bool = false { didSet { maskInputListener?.rightToLeft = isRTL - maybeUpdateText(text: textView?.allText ?? "") + maybeUpdateText(text: textField?.allText ?? "") } } - @objc var affinityFormat: [String] = [] { + @objc private var affinityFormat: [String] = [] { didSet { maskInputListener?.affineFormats = affinityFormat } } - @objc var affinityCalculationStrategy: NSNumber? = 0 { + @objc private var affinityCalculationStrategy: NSNumber? = 0 { didSet { maskInputListener?.affinityCalculationStrategy = AffinityCalculationStrategy.forNumber(number: affinityCalculationStrategy) } } - @objc var customNotations: NSArray? { + @objc private var customNotations: NSArray? { didSet { let customNotations = (customNotations as? [[String: Any]])?.compactMap { $0.toNotation() } ?? [] @@ -110,20 +86,46 @@ class AdvancedTextInputMaskDecoratorView: UIView { } } - @objc var defaultValue: NSString = "" { + @objc private var defaultValue: NSString = "" { didSet { updateTextWithoutNotification(text: defaultValue as String) } } - @objc var value: NSString = "" { + @objc private var value: NSString = "" { didSet { updateTextWithoutNotification(text: value as String) } } + // MARK: - Event Handlers + + private func onAdvancedMaskTextChangedCallback( + extracted: String, + formatted: String, + tailPlaceholder: String + ) { + let eventData: [String: String] = [ + "extracted": extracted, + "formatted": formatted, + "tailPlaceholder": tailPlaceholder, + ] + + if NSDictionary(dictionary: eventData).isEqual(to: lastDispatchedEvent) { + return + } + + lastDispatchedEvent = eventData + if onAdvancedMaskTextChange != nil { + onAdvancedMaskTextChange!(eventData) + } + delegate?.onAdvancedMaskTextChange(eventData: eventData as NSDictionary) + } + + // MARK: - Utility Methods + @objc private func updateTextWithoutNotification(text: String) { - if text == textView?.allText { + if text == textField?.allText { return } @@ -135,14 +137,14 @@ class AdvancedTextInputMaskDecoratorView: UIView { ) let result = primaryMask.apply(toText: caretString) - textView?.allText = result.formattedText.string + textField?.allText = result.formattedText.string } @objc private func maybeUpdateText(text: String) { guard let primaryMask = maskInputListener?.primaryMask else { return } - guard let textView = textView else { return } + guard let textField = textField else { return } - if text == textView.allText { + if text == textField.allText { return } @@ -157,54 +159,23 @@ class AdvancedTextInputMaskDecoratorView: UIView { return } - textView.allText = result.formattedText.string - maskInputListener?.notifyOnMaskedTextChangedListeners(forTextInput: textView, result: result) + textField.allText = result.formattedText.string + maskInputListener?.notifyOnMaskedTextChangedListeners(forTextInput: textField, result: result) } - private func findFirstTextField(in view: UIView) -> UITextField? { - // Loop through each subview - for subview in view.subviews { - // If the subview is a UITextField, return it - if let textField = subview as? UITextField { - return textField - } - } - // If no UITextField is found, return nil - return nil - } + // MARK: - Configuration Methods - private func findTextFieldFabric() { - if let parent = superview?.superview { - for elementIndex in 1 ..< parent.subviews.count where parent.subviews[elementIndex] == superview { - textView = findFirstTextField(in: parent.subviews[elementIndex - 1]) - break - } - } - } + private func configureTextField() { + findTextField() + guard let textField = textField else { return } - private func findTextFieldPaper() { - if let parent = superview { - for elementIndex in 1 ..< parent.subviews.count where parent.subviews[elementIndex] == self { - textView = findFirstTextField(in: parent.subviews[elementIndex - 1]) - break - } - } - } - - private func finTextField() { - #if ADVANCE_INPUT_MASK_NEW_ARCH_ENABLED - findTextFieldFabric() - #else - findTextFieldPaper() - #endif + textFieldDelegate = AdvancedInputMaskDelegateWrapper(textFieldDelegate: textField.delegate) + textField.delegate = textFieldDelegate } - @objc override func didMoveToWindow() { - super.didMoveToWindow() - - finTextField() + private func configureMaskInputListener() { + guard let textField = textField else { return } - guard let textView = textView else { return } maskInputListener = MaskedTextInputListener( primaryFormat: primaryMaskFormat as String, autocomplete: autocomplete, @@ -214,18 +185,59 @@ class AdvancedTextInputMaskDecoratorView: UIView { affineFormats: affinityFormat, affinityCalculationStrategy: AffinityCalculationStrategy.forNumber(number: affinityCalculationStrategy), customNotations: (customNotations as? [[String: Any]])?.compactMap { $0.toNotation() } ?? [], - onMaskedTextChangedCallback: { input, value, _, tailPlaceholder in - self.onAdvancedMaskTextChangedCallback(value, input.allText, tailPlaceholder) + onMaskedTextChangedCallback: { [weak self] input, value, _, tailPlaceholder in + self?.onAdvancedMaskTextChangedCallback( + extracted: value, + formatted: input.allText, + tailPlaceholder: tailPlaceholder + ) }, allowSuggestions: allowSuggestions ) - textFieldDelegate = AdvancedInputMaskDelegateWrapper(textFieldDelegate: textView.delegate) maskInputListener?.textFieldDelegate = textFieldDelegate - textView.delegate = maskInputListener + textField.delegate = maskInputListener + } + + // MARK: - View Lifecycle + + override func didMoveToWindow() { + super.didMoveToWindow() + configureTextField() + configureMaskInputListener() updateTextWithoutNotification(text: defaultValue as String) } } -class RCTMaskedTextFieldDelegateAdapter: RCTBackedTextViewDelegateAdapter {} +extension AdvancedTextInputMaskDecoratorView { + private func findFirstTextField(in view: UIView) -> UITextField? { + // Loop through each subview + for subview in view.subviews { + // If the subview is a UITextField, return it + if let textField = subview as? UITextField { + return textField + } + } + // If no UITextField is found, return nil + return nil + } + + private func findTextField() { + #if ADVANCE_INPUT_MASK_NEW_ARCH_ENABLED + if let parent = superview?.superview { + for elementIndex in 1 ..< parent.subviews.count where parent.subviews[elementIndex] == superview { + textField = findFirstTextField(in: parent.subviews[elementIndex - 1]) + break + } + } + #else + if let parent = superview { + for elementIndex in 1 ..< parent.subviews.count where parent.subviews[elementIndex] == self { + textField = findFirstTextField(in: parent.subviews[elementIndex - 1]) + break + } + } + #endif + } +}