Skip to content

Conversation

@mazunin-v-jb
Copy link

@mazunin-v-jb mazunin-v-jb commented Nov 27, 2025

This change introduces an opt-in Native iOS Text Input (NITI) mode for Compose TextField on iOS, enabled via PlatformImeOptions.useNativeInputHandling(enabled) in the iOS source set

With the flag set to true, Compose text fields use UIKit-native text editing and interaction behavior, details in the release notes

Key changes

  • Added a new PlatformImeOptions flag: useNativeInputHandling
  • Split iOS text input and context menu logic into "NITI vs non-NITI" code paths in:
    • UIKitTextInputService.ios.kt
    • IntermediateTextInputUIView.ios.kt
    • ContextMenu.ios.kt
  • When NITI is enabled, Compose-drawn caret and selection handles are suppressed, so UIKit becomes the single source of truth for text controls UI
  • Replaced setNeedsRedraw with an explicit draw() call in updateView closure in UIKitTextInputService to eliminate a race condition between UIKit UITextInput queries and TextLayoutResult updates. This could leave iOS with stale text field state, which in NITI mode resulted in incorrect caret positioning and a crash. The fix applies to both NITI and non-NITI scenarios (non-NITI impact was largely not user-visible)
  • Implemented selection-aware context menu item updates (caret vs selection):
    • Legacy context menu: updates via LaunchedEffect (through startNotifyingAboutContextMenuItems).
    • New context menu (isNewContextMenu = true): updates via NitiContextMenuUpdaterElement.
  • Extracted existing Objective-C helpers into separate files:
    • CMPEditMenuCustomAction
    • CMPTextInputStringTokenizer
  • Introduced an additional CMPTextInputView to ensure correct UIKit text input behavior in NITI mode.
  • CMPEditMenuView now inherits from CMPTextInputView to unify UIKit text input handling for edit menu interactions

Common code changes

To allow the platform (UIKit) to draw cursor/selection when NITI is enabled, common text field rendering was refactored:

  • CoreTextField.kt
    • Introduced platformShouldDrawTextControls (expect) to let the platform decide whether it draws cursor/selection
    • Cursor is shown only when the platform is not drawing it
    • Selection highlight drawing can be suppressed when the platform handles it
  • TextFieldDelegate.kt
    • drawHighlight now accepts a drawSelectionHighlight flag and skips selection highlighting when the platform handles drawing
  • TextFieldCoreModifier.kt
    • Selection/cursor drawing is delegated to platform-specific drawPlatformSelection / drawPlatformCursor instead of inline drawing

Added API

  • UIKitNativeTextInputContext
    • Used to pass required data between ui and foundation modules
  • LocalNativeTextInputContext
    • CompositionLocal for accessing UIKitNativeTextInputContext
  • PlatformTextInputMethodRequest.textUnclippingOffsetInRoot
    • Used to compute a correct IntermediateTextInputUIView size/position when extra margins are applied and/or when the text field needs to be scrollable

Testing

Manual

  1. In a separate project
    Create an expect/actual for KeyboardOptions

Common source set:

@ExperimentalComposeUiApi
expect fun nativeKeyboardOptionsUseNativeInputHandling(enabled: Boolean): KeyboardOptions

iOS source set:

@ExperimentalComposeUiApi
actual fun nativeKeyboardOptionsUseNativeInputHandling(enabled: Boolean): KeyboardOptions {
    return KeyboardOptions(
        platformImeOptions = PlatformImeOptions {
            useNativeInputHandling(enabled)
        }
    )
}

Then use it on any TextField in common code (BasicTextField(TextFieldState) as an example):

BasicTextField(
    state,
    keyboardOptions = nativeKeyboardOptionsUseNativeInputHandling(true)
)
  1. In Compose Demo project
    Open Components -> NITI Tests (WIP: coming later)

Automated

  • (WIP: coming later)

Release Notes

Features - iOS

  • Added an opt-in Native iOS Text Input mode for Compose BasicTextField (both TextFieldValue and TextFieldState) enabled via PlatformImeOptions.useNativeInputHandling(enabled) in iOS source set
  • With the native iOS text input flag set to true, BasicTextField uses native UIKit editing and interaction, including:
    • Native caret placement and movement (tap-to-place caret, spacebar caret movement, “ghost caret”)
    • Native magnifier
    • Native selection gestures (double-tap word selection, triple-tap paragraph selection) and iOS-like selection UI (region + handles)
    • Native iOS context menu behavior is supported for both the legacy and the new context menu (isNewContextMenu = true) configurations.
    • Native context menu behavior when interacting with the caret, selection region, or selection handles
    • System iOS text actions in the text field context menu (e.g. Translate, Look Up, Share), provided by UIKit and not available without this mode
    • Autocorrect / typo replacement support
    • Autofill support for text fields, including filling from saved passwords one field at a time
    • Text field controls respect the colors provided by text field theme settings

@mazunin-v-jb mazunin-v-jb self-assigned this Nov 27, 2025
@mazunin-v-jb mazunin-v-jb force-pushed the v.mazunin/native-ios-text-input branch from dd397ec to c0251cd Compare November 27, 2025 14:33
@mazunin-v-jb mazunin-v-jb marked this pull request as draft November 27, 2025 14:33
@mazunin-v-jb mazunin-v-jb force-pushed the v.mazunin/native-ios-text-input branch from 7b04420 to 78ec253 Compare December 15, 2025 12:00
@mazunin-v-jb mazunin-v-jb force-pushed the v.mazunin/native-ios-text-input branch from a2d64d8 to e1a2e1f Compare January 12, 2026 12:39
@mazunin-v-jb mazunin-v-jb force-pushed the v.mazunin/native-ios-text-input branch from c4fb706 to d3380e7 Compare January 26, 2026 16:11
@mazunin-v-jb mazunin-v-jb changed the title (WIP) Native iOS text input Native iOS text input Jan 26, 2026
- deleted debug prints
- fixed crashes during text input in NITI which had been caused by the late update of TextLayoutResult, reverted hack with checking MultiParagraph text length
- Fixed crash with text autocorrection
- Fix cursor width, fix context menu appearance by cursor tap
- Fix tint color
- Fix text field cursor activation
- fixed generated comments in IOSSkikoInput.uikit.kt
- clean up IntermediateTextInputUIView.uikit.kt
- removed unnecessary imports from IntermediateTextInputUIView.uikit.kt, removed unnecessary method from IOSSkikoInput.uikit.kt
- reverted observeSelectionChanges in commonMain part of BTF2, which seem unnecessary
- reverted accidental commit of debug flags
- remove unnecessary macos actuals, added one universal macos actual
- fixed CMPUtils header
- reverting unnecessary changes
- xcodeproj fixes
- Fixed lack of items in the context menu with NITI
- Forwarded cursor / selection colors in TF1 / TF2
- Disabled Compose cursor in NITI mode in both TFs
- Created expect / actual for the Compose text selection highlight in TF1, hidden it under NITI flag in uikit sourceset
- Renamed UIKitTextContextMenuHandler.kt into more appropriate UIKitNativeTextInputContext
- Created expect / actual for altering drawing selection rect on different platforms
- fixed incorrect positioning of context menu
- disabled back newContextMenu flag, rewrote showEditMenu logic in UIKitTextInputService.uikit.kt with using NITI toggle, created a signle entry point for context menu, removed some comments
- context menu without NITI fixes + added existing obj-c files to the project which were forgotten to add during the merge
- merge fixes after commit with refactoring InputViews.uikit.kt
- Reverted pointerInput handling in TF1,2, reverted view hierarchy, hidden almost everything related to the NITI under the flag, added some TODOs with bad fixes
- Added feature flag for NITI to platform ime options
- added NITI tests screens to the example app
- Forwarded custom actions to the IntermediateTextInputUIView.uikit.kt
- Enabled native context menu, added public API for updating context menu state (foundation -> ui), extracted all UITextInput methods from objc file to separate class, temporarily disabled current context menu
- Extracted UITextInput methods from CMPEditMenuView to CMPTextInputView, fixed NewContextMenuApi menu
- (wip) commented CMPEditMenu methods calls after rebase with new context menu api on ios
- (wip) turned on native selection rects (LTR only!) + fixed appearing of the text editing menu by tapping on the selection rects + more appropriate naming + fixed imports
- (wip) disabled custom iOS tap handlers in TF1,2
- (wip) fixed native text views positioning in TF1
- (wip) disabled compose selection handles
- (wip) fixed scroll positioning in BTF2
- (wip) fixed touch forwarding
- (wip) fixed positioning of ScrollView and TextView issue
- (wip) removed unnecessary comments
- (wip) fixed sizing of TextScrollView and TextView
- (wip) transfered changes from previous niti branch
- (wip) rebase fixes
@mazunin-v-jb mazunin-v-jb force-pushed the v.mazunin/native-ios-text-input branch from b38a420 to 00d1528 Compare January 30, 2026 13:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants