Skip to content

Appearance.setColorScheme() crashes any CarPlay app: RCTAppearance sends windows to every connected scene #57863

Description

@ipechy

Description

RCTAppearance's setColorScheme: iterates every connected scene and asks each one for windows. The loop variable is declared UIWindowScene *, but that is a compile-time claim, not a runtime filter — connectedScenes returns UIScene objects of any concrete class.

A CarPlay app has a CPTemplateApplicationScene among its connected scenes. It is a UIScene, it is not a UIWindowScene, and it has no windows property. Sending it that message aborts the process.

RCTAppearance.mm#L112-L123, unchanged on main as of 2026-08-08:

- (void)setColorScheme:(NSString *)style
{
  UIUserInterfaceStyle userInterfaceStyle = [RCTConvert UIUserInterfaceStyle:style];
  NSMutableArray<UIWindow *> *windows = [NSMutableArray new];
  for (UIWindowScene *scene in RCTSharedApplication().connectedScenes) {
    [windows addObjectsFromArray:scene.windows];   // crashes on CPTemplateApplicationScene
  }

  for (UIWindow *window in windows) {
    window.overrideUserInterfaceStyle = userInterfaceStyle;
  }
}

On 0.86.0 the same body sits under RCT_EXPORT_METHOD(setColorScheme : (NSString *)style). Reproduced with the linked reproducer — a bare @react-native-community/cli init app on 0.86.0, no Expo and no third-party CarPlay library

Why this is easy to miss. The crash needs an app that has both a CarPlay scene and a call to Appearance.setColorScheme(). Ours is a delivery-driver app with a light/dark/system theme setting, so setColorScheme runs at every launch. The failure mode is misleading: the app dies on the phone, every time, but only while the phone is connected to CarPlay — and only this one app out of everything on the device. That reads like an application bug rather than a React Native one; it took a crash report to find.

This is distinct from the frequently reported RCTAppDelegate window-setup CarPlay problems (expo/expo#32702, #41777, birkir/react-native-carplay#184), which are about launching into CarPlay with no phone window. This one kills an app that is already running normally, the moment JS sets the color scheme.

Suggested fix — filter by class instead of trusting the loop variable's declared type:

for (UIScene *scene in RCTSharedApplication().connectedScenes) {
  if (![scene isKindOfClass:[UIWindowScene class]]) {
    continue;
  }
  [windows addObjectsFromArray:((UIWindowScene *)scene).windows];
}

We have run this as a patch-package patch in production since 2026-08-02; it fixes the crash with no other observable change. Other connectedScenes loops in the codebase may make the same assumption and are worth a look.

Steps to reproduce

Using the linked reproducer:

  1. npm install && (cd ios && pod install)
  2. Build and run on a simulator.
  3. In the Simulator, open I/O → External Displays → CarPlay.
  4. Open the app on the CarPlay screen — this is the step that matters, and it is easy to skip. The CPTemplateApplicationScene only connects once the app is opened on the car display.
  5. Now launch (or relaunch) the app on the phone screen.

Expected: the app launches and the color scheme is applied to the phone's windows. Actual: the process aborts immediately — -[CPTemplateApplicationScene windows]: unrecognized selector (see Stacktrace).

If step 4 is skipped the app launches fine, because no CarPlay scene is connected yet and the loop only sees window scenes. In a real car this ordering is invisible: the app is already on the car display, so every launch on the phone crashes — which is what made it look like an application bug rather than a React Native one.

This does not require Apple's CarPlay entitlement grant. The Simulator will run an app that simply declares the entitlement locally.

React Native Version

0.86.0

Affected Platforms

Runtime - iOS

Output of npx @react-native-community/cli info

System:
  OS: macOS 26.5.2
  CPU: (10) arm64 Apple M1 Pro
  Memory: 392.13 MB / 32.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 22.17.0
    path: ~/.nvm/versions/node/v22.17.0/bin/node
  Yarn:
    version: 1.22.22
    path: ~/.nvm/versions/node/v22.17.0/bin/yarn
  npm:
    version: 11.6.2
    path: ~/.nvm/versions/node/v22.17.0/bin/npm
  Watchman:
    version: 2025.06.23.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.16.2
    path: /opt/homebrew/bin/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 25.2
      - iOS 26.2
      - macOS 26.2
      - tvOS 26.2
      - visionOS 26.2
      - watchOS 26.2
IDEs:
  Xcode:
    version: 26.2/17C52
    path: /usr/bin/xcodebuild
Languages:
  Ruby:
    version: 3.4.4
    path: /opt/homebrew/opt/ruby/bin/ruby
npmPackages:
  "@react-native-community/cli":
    installed: 20.1.0
    wanted: 20.1.0
  react:
    installed: 19.2.3
    wanted: 19.2.3
  react-native:
    installed: 0.86.0
    wanted: 0.86.0
iOS:
  hermesEnabled: true
  newArchEnabled: true

Stacktrace or Logs

*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[CPTemplateApplicationScene windows]: unrecognized selector sent to instance 0x10380caa0'

 0   CoreFoundation             __exceptionPreprocess + 160
 1   libobjc.A.dylib            objc_exception_throw + 72
 2   CoreFoundation             +[NSObject(NSObject) instanceMethodSignatureForSelector:] + 0
 3   UIKitCore                  -[UIResponder doesNotRecognizeSelector:] + 232
 4   CoreFoundation             ___forwarding___ + 1216
 5   CoreFoundation             _CF_forwarding_prep_0 + 92
 6   React                      -[RCTAppearance setColorScheme:] + 204
 7   CoreFoundation             __invoking___ + 144
 8   CoreFoundation             -[NSInvocation invoke] + 276
 9   CoreFoundation             -[NSInvocation invokeWithTarget:] + 60
10   React                      invocation function for block in facebook::react::ObjCTurboModule::performVoidMethodInvocation(facebook::jsi::Runtime&, char const*, NSInvocation*, NSMutableArray*) + 68
11   React                      std::__1::__function::__func<facebook::react::ObjCTurboModule::performVoidMethodInvocation(...)::$_0, ...>::operator()() + 104
12   libdispatch.dylib          _dispatch_call_block_and_release + 24
13   libdispatch.dylib          _dispatch_client_callout + 12
15   libdispatch.dylib          _dispatch_main_queue_drain + 1184
16   libdispatch.dylib          _dispatch_main_queue_callback_4CF + 40
17   CoreFoundation             __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
18   CoreFoundation             __CFRunLoopRun + 1920
19   CoreFoundation             CFRunLoopRunSpecific + 536
20   GraphicsServices           GSEventRunModal + 164
21   UIKitCore                  -[UIApplication _run] + 796
22   UIKitCore                  UIApplicationMain + 124
24   RNCarPlayAppearanceRepro   specialized static UIApplicationDelegate.main() + 28
25   RNCarPlayAppearanceRepro   static AppDelegate.$main() + 28
26   RNCarPlayAppearanceRepro   main + 120
27   dyld_sim                   start_sim + 20

MANDATORY Reproducer

https://github.com/ipechy/RNCarPlayAppearanceRepro

Screenshots and Videos

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions