Skip to content

fix: guard nativeId lookup for UIKit responders - #1551

Draft
zxrl wants to merge 1 commit into
kirillzyusko:mainfrom
zxrl:native-alert-crash
Draft

fix: guard nativeId lookup for UIKit responders#1551
zxrl wants to merge 1 commit into
kirillzyusko:mainfrom
zxrl:native-alert-crash

Conversation

@zxrl

@zxrl zxrl commented Jul 15, 2026

Copy link
Copy Markdown

📜 Description

Restore the nativeId selector check before reading the property from a focused input's superview on iOS New Architecture.

UIKit-owned inputs, including the text field created by Alert.prompt, can become the current responder while KeyboardProvider is mounted. Their superviews do not expose React Native's nativeId property and must return nil instead of entering KVC.

This also adds native regression coverage for both sides of the lookup:

  • a UIKit superview without nativeId returns nil;
  • a React-style superview with nativeId still returns its value.

💡 Motivation and Context

On iOS 27 beta, opening a React Native Alert.prompt while KeyboardProvider is active aborts as the keyboard appears. The relevant crash path is:

valueForUndefinedKey:
NSObject._safeValue(forKey:)
Optional<UIResponder>.nativeID
KeyboardAreaExtender.keyboardDidAppear

The same class of crash was reported in #784 and fixed by #785 with a responds(to:) check. #1174 later replaced that check with exception-based safe KVC. The exception-based path does not contain this UIKit nativeId lookup on iOS 27, so this change restores the explicit capability check for nativeId while retaining safeValue(forKey:) for the actual read.

I checked the current main branch, release 1.22.1, and open PR #1524; all still contain the unchecked lookup. I also searched open and closed issues and pull requests for nativeId, valueForUndefinedKey, Alert.prompt, UIAlertController, KeyboardAreaExtender, safeValue, NSUnknownKeyException, and iOS 27. I did not find an existing fix for this regression.

Reproduction

  1. Mount KeyboardProvider in a New Architecture React Native app.
  2. Open Alert.prompt(...) on iOS.
  3. Focus the alert text field so the keyboard appears.
  4. Observe the abort from the nativeId lookup.

Observed with react-native-keyboard-controller 1.22.0, React Native 0.86.0, Expo 57, and an iPhone 15 Pro running iOS 27.0 beta. The affected lookup is unchanged in 1.22.1.

📢 Changelog

iOS

  • avoid crashing when a UIKit-owned responder does not expose React Native's nativeId property;
  • cover UIKit and React superview lookup behavior in the native test target.

🤔 How Has This Been Tested?

  • xcodebuild build-for-testing -scheme KeyboardControllerNative -destination 'generic/platform=iOS Simulator' CODE_SIGNING_ALLOWED=NO -quiet
  • rebuilt, installed, and launched the patched app on the same physical device that produced the crash, then verified that Alert.prompt remains open and stable with the keyboard visible;
  • added two New Architecture native regression tests. The local Xcode 26.5 simulator runtime shut down before XCTest execution, so execution is left to the repository's supported CI simulator matrix.

📝 Checklist

  • CI successfully passed
  • I added corresponding native regression tests

@kirillzyusko
kirillzyusko self-requested a review July 15, 2026 11:11
@kirillzyusko

Copy link
Copy Markdown
Owner

Hey @zxrl

Which iOS 27 beta did you test?

@kirillzyusko kirillzyusko self-assigned this Jul 15, 2026
@kirillzyusko kirillzyusko added 🍎 iOS iOS specific 🎯 crash Library triggers a crash of the app labels Jul 15, 2026
@kirillzyusko

Copy link
Copy Markdown
Owner

This is the video from iOS 27.0-beta.0, no crash so far:

Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-07-15.at.13.25.43.mov

Comment on lines +110 to +131
#if KEYBOARD_CONTROLLER_NEW_ARCH_ENABLED
func testNativeIDReturnsNilForUIKitSuperviewWithoutNativeID() {
let superview = UIView()
let textField = UITextField()
superview.addSubview(textField)

let responder: UIResponder? = textField

XCTAssertNil(responder.nativeID)
}

func testNativeIDReadsReactSuperviewNativeID() {
let superview = ReactSuperview()
let textField = UITextField()
superview.nativeId = "react-view"
superview.addSubview(textField)

let responder: UIResponder? = textField

XCTAssertEqual(responder.nativeID, "react-view")
}
#endif

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are testing mock implementation?

I don't think it makes a lot of sense to do that?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They exercise the actual Optional<UIResponder>.nativeID implementation from UIResponder.swift, which is compiled directly into the native test target. ReactSuperview is only a fixture that exposes the selector.

You're right that the test target supplies a safeValue stub because this standalone project does not currently link the production Objective-C SafeKVC helper. So these tests specifically cover the new guard:

  • a plain UIKit UIView returns nil before safeValue is called;
  • a view exposing nativeId passes the guard and is still read.

They do not cover the production NSObject+SafeKVC bridge itself. I can instead wire NSObject.swift and the Objective-C category into the native test target if you would prefer full production-path coverage over this isolated guard coverage.

@zxrl

zxrl commented Jul 23, 2026

Copy link
Copy Markdown
Author

Thanks — the crash was on a physical iPhone 15 Pro running iOS 27 beta 3 (24A5380h). The app used react-native-keyboard-controller 1.22.0, React Native 0.86.0, Expo 57, and New Architecture, and was built with Xcode 26.5.

The symbolicated .ips report from the device shows:

-[NSObject(NSKeyValueCoding) valueForUndefinedKey:]
-[NSObject(NSKeyValueCoding) valueForKey:]
-[NSObject(SafeKVC) _safeValueForKey:]
NSObject.safeValue(forKey:)
Optional<UIResponder>.nativeID.getter
KeyboardAreaExtender.keyboardDidAppear(_:)

It terminates with SIGABRT. I rebuilt with this guard and verified Alert.prompt remained open with its text field focused on the same device.

Your recording is from a simulator, so the physical-device/toolchain combination may be the relevant difference. Could you confirm the simulator's exact iOS build number and Xcode version? I can also provide a sanitized copy of the crash report if useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🎯 crash Library triggers a crash of the app 🍎 iOS iOS specific

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants