Skip to content

iOS: autoCapitalize prop ignored on focused TextInput due to delegate swap clobbering autocapitalization state #1463

@Bondrro

Description

@Bondrro

Description

When KeyboardProvider is mounted in App.tsx (standard setup), iOS TextInputs with autoCapitalize="words", "sentences", or "characters" silently fall back to "none" on real devices. Simulator works correctly because its UIKit stub doesn't have the same delegate-coupled state machine.

Root cause

FocusedInputObserver.swift::substituteDelegate(_:) swaps the input view's delegate via DispatchQueue.main.async on textDidBeginEditingNotification. This swap clobbers iOS's autocapitalization state machine (which is keyed to the delegate identity at session start).

Trait-only props (keyboardType, textContentType, autoCorrect) survive because they're queried fresh each time. Only autocapitalizationType breaks because it's stateful — the trait says HOW to capitalize, the state machine decides WHEN, and the delegate swap resets the state machine.

Reproduction

  1. Mount <KeyboardProvider> at app root
  2. Add a <TextInput autoCapitalize="sentences" /> anywhere
  3. Run on a real iOS device (not simulator)
  4. Tap the field — keyboard never shifts to uppercase on first letter

Affected version

react-native-keyboard-controller@1.20.7, React Native 0.79.2, iOS 18 (tested on real iPhone)

Proposed fix

Patch substituteDelegate(_:) to save/restore autocapitalizationType around the swap, and call reloadInputViews() to rebuild the iOS input session:

private func substituteDelegate(_ input: UIResponder?) {
  if let textField = input as? UITextField {
    if !(textField.delegate is KCTextInputCompositeDelegate),
       delegate.canSubstituteTextFieldDelegate(delegate: textField.delegate)
    {
      delegate.setTextFieldDelegate(delegate: textField.delegate, textField: textField)
      let savedAutocap = textField.autocapitalizationType
      textField.delegate = delegate
      textField.autocapitalizationType = savedAutocap
      if textField.isFirstResponder {
        textField.reloadInputViews()
      }
    }
  } else if let textView = input as? UITextView {
    if !(textView.delegate is KCTextInputCompositeDelegate) {
      delegate.setTextViewDelegate(delegate: textView.delegate)
      let savedAutocap = textView.autocapitalizationType
      textView.setForceDelegate(delegate)
      textView.autocapitalizationType = savedAutocap
      if textView.isFirstResponder {
        textView.reloadInputViews()
      }
    }
  }
}

Risk: LOW — uses public UIKit API only. No new attack surface. Behavior preserved when autocapitalizationType == .none.

Happy to open a PR if helpful.

Metadata

Metadata

Assignees

Labels

focused input 📝Anything about focused input functionality🍎 iOSiOS specific🐛 bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions