Closed zerobertelprivat closed 11 months ago
:warning: | Missing Reproducible Example |
---|---|
:information_source: | We could not detect a reproducible example in your issue report. Please provide either:
|
Have you considered using https://github.com/birkir/react-native-carplay or other similar community contributed projects to do Carplay with React Native?
Have you considered using https://github.com/birkir/react-native-carplay or other similar community contributed projects to do Carplay with React Native?
I have considered, but i wasnt sure if this lib will fill my needs and it seems to be used rarely. Because the carplay template logic is managed from React Native environment, i cannot imagine how carplay should work without launching the phone app (?)
I have found the problem:
[RNSplashScreen show];
blocks the application function, if i comment it out, the carplay screen gives me content without iphone app open ....
I have found the problem:
Closing. I also advice you to seek support over at https://github.com/birkir/react-native-carplay/issues
@cortinico Why was this issue closed? This is a genuine issue/shortcoming in how React Native is set up in the AppDelegate.
It is not compatible with how CarPlay requires the use of scenes to setup either the phone or car app. See hits excerpt from this PR on the react-native-carplay
repository.
A React Native app usually calls the super-method in its AppDelegate, which is implemented in React Native's own RCTAppDelegate. The problem with this is that RCTAppDelegate assumes a phone usage and creates a rootViewController along with a window for the app to be displayed in. This leads to problems when launching the app on the CarPlay-client first, since CarPlay does not require a rootViewController or a window to display its views.
Developers shouldn't have to go through hoops like those in the linked PR when wanting to add CarPlay support to a React Native app.
Ideally the initialisation code in the AppDelegate can be more efficiently tailored/applied when using scenes without having to go copy/paste from internals.
TLDR: This is not an issue that can only be solved by react-native-carplay
, ideally developers can add scenes and are able to move initialization logic over to the scenes logic without too much hassle.
This is a genuine issue/shortcoming in how React Native is set up in the AppDelegate.
I'll pass this over to @cipolleschi to answer when he'll be back (as he's on vacation now). He can provide more insights on what's the best course of action here
Maybe I should also add that this issue (which stems from using scenes) is not unique to CarPlay. If you want your app to support multiple windows, you also have to opt-in to using scenes on iOS (on iPad you can place multiple windows of the same app next to eachother). Each of those windows will need to have its own root view controller.
The issue now is that a root view controller already gets created in the application:didFinishLaunchingWithOptions:
method. While when using scenes you are supposed to create the root view controllers in the scene delegates. This root view controller (created in the app delegate) will live next to te ones created in the scenes.
This leads to an unneccessary extra root view controller being present (which doesn't play nice with CarPlay apparently).
I think one way to make the current RN initialization setup compatible with scenes on iOS, would be to abstract the logic that is now contained in application:didFinishLaunchingWithOptions:
into separate methods (one for setting up the bridge and one for create the root view & view controller and call those from the app AppDelegate as such:
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
BOOL result = [super application:application didFinishLaunchingWithOptions:launchOptions]; // this doesn't contain any actual setup anymore
if (result) {
[self initBridge];
self.window = [self createWindow];
self.window.makeKeyAndVisible()
}
return result;
}
@end
The initBridge
method would do all initialization (mainly setting up the bridge if needed), and the createWindow
method would contain teh code for creating the root view controller (with root view) and creating a window to which to asign these.
This would allow apps that need to use scenes to remove app delegate implementation and only call initBridge
in for instance the CarPlay scene (via UIApplication.shared.delegate
).
In a PhoneScene you could opt to invoke both initBridge
and createWindow
and assign the result of createWindow
to the window property of the scene.
Any feedback welcome ofcourse.
I'm going to go ahead and tag @danielkuhn in here as well, maybe he has some feedback (or corrections) on this too, since this is how I understand the problem space at this moment.
Note: I know this kind of re-introduces some "complexity" into the AppDelegate vs. merely extending RCTAppDelegate.mm
, and this probably will require tools like Expo to adapt as well (which now wrap the AppDelegate logic).
Another option to work around this is to introduce some extra decision logic in RCTAppDelegate based on a new instance variable set in the app delegate. This way you could keep on extending RCTAppDelegate
, e.g. introducing a preventWindowCreation
boolean:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.moduleName = @"rntestapp";
// You can add your custom initial props in the dictionary below.
// They will be passed down to the ViewController used by React Native.
self.initialProps = @{};
self.preventWindowCreation = YES; // should be NO by default
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
This preventWindowCreation
could be than taken into account to conditionally create the root view controller. Ideally this creation is still abstracted away into its own method so it can still be called directly from a (phone) scene.
Hey @bitcrumb , thanks for keeping me in the loop. Of course it would be awesome if React Native could support Scenes and switch the basic iOS setup to SceneDelegates instead of the single AppDelegate. Scenes have become the standard ever since iOS 13, therefore if you want to write a React Native app that supports multiple windows (like CarPlay, multitasking/split view on iPad or supporting external displays on iOS or iPadOS) you need to implement SceneDelegates anyways.
Here's some literature on the topic: Mutli Window Support is happening (from 2019): "Next time you create a new Xcode project you’ll see your AppDelegate has split in two: AppDelegate.swift and SceneDelegate.swift. This is a result of the new multi-window support that landed with iPadOS [...] scene delegates are there to handle one instance of your app’s user interface. So, if the user has created two windows showing your app, you have two scenes, both backed by the same app delegate."
Single vs. Multiple instances:
https://www.appypie.com/scene-delegate-app-delegate-xcode-11-ios-13 https://manasaprema04.medium.com/scene-delegate-vs-appdelegate-86e22dc17fcb ...
There's a ton of articles about this, no need to link them all.
It would be a great step forward for iOS development in React Native to switch from the old AppDelegate.h
and AppDelegate.mm
to a modern AppDelegate.swift
and SceneDelegate.swift
setup - just like on the Java side React Native now switched from Java to Kotlin.
I outlined pretty much everything I know about the topic in my PR over at react-native-carplay and the README in the included example app. I'd love to see this come to future versions of React Native and would love to help of course!
Hi everyone, sorry for the late reply, I was on PTO.
On the matter of moving from the AppDelegate to the SceneDelegate: yes, that would be amazing and the right thing to do. However, right now, we don't have capacity to work on that as we have some more urgent task to tackle. The change is not big per sé, but has the implication that it will "force" a migration from the AppDelegate to the SceneDelegate to the whole ecosystem, and we don't have capacity to support the users in that migration as we are focused on the New Architecture at the moment.
So, for your specific use case at the moment, you should implement the Scene Delegate in your app yourself.
On the Swift topic, migrating to Swift from Objective-C is a theme dear to me and very close to my heart. However, there are technical limitation at the moment that prevent us from doing it, mainly some incompatibilities between C++ and Swift. Yes, this year Apple started adding Swift/C++ interop, but it is not complete yet and there are some feature missing that we need (for example Inheritance: the Fabric Components base class is in C++ and Swift Classes cannot extends C++ classes).
Plus, the C++ structure of React Native is not compatible with Swift (they are not Clang modules, sadly). Changing that will require a huge refactoring of the codebase and changes to almost all the #import/#include
directives and, again, we don't have capacity to follow with this effort at the moment.
Hi @cipolleschi , thank you for your reply.
I understand that this is a big task that needs lots of planning and migration support.
But wouldn't it be a fairly easy and straightforward first step to at least change the AppDelegate
implementation to Swift, while keeping everything within react native (i.e. RCTAppDelegate
) in ObjC? Or at least make it an option for create-react-native-app
, react-native init
and create-expo-app
?
Of course this would require maintainers of packages with native wiring in AppDelegate
to also update their documentation to offer example code in both languages (like Apple does on their docs pages) but in times of ChatGPT and the like, I would guess that most developers could also figure out these rather small adjustments/translations themselves - especially if they explicitly opted in to Swift on app creation.
That's actually one of the biggest! 😅 Converting the AppDelegate to Swift is troublesome, for various reasons:
react-native-upgrade-helper
will see the new Swift files as completely new files and will ask all the user to change their current implementation.modulemaps
, among other problems) so Swift has trouble building it.
As we are focusing on the New Architecture right now, this is a big blocker. Hopefully, when the rollout of the New Arch is done, I'll have more time to look into the Swift blockers and try to solve them.
Description
I have build a well working React Native App (thanks to the RN Team and supporters) with the ability to play podcasts via App and Carplay. To implement Carplay i have splitted the App in 2 Scenes via the Info.plist. Additional adjustments in the AppDelegate.m were also neccessary, i have copied the whole RCT AppDelegate to ensure the correct sequence of code in the adjusted function:
` @implementation AppDelegate
(BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions { / Original Code Start -> code of clean default function / self.moduleName = @"app"; // You can add your custom initial props in the dictionary below. // They will be passed down to the ViewController used by React Native. self.initialProps = @{};
// return [super application:application didFinishLaunchingWithOptions:launchOptions] // ^^ removed because of SceneDelegate logic for carplay / Original Code End /
/ CUSTOM_IMPLEMENTATION_START /
/ IMPORTANT NOTE: whole following code replaces return [super application:application didFinishLaunchingWithOptions:launchOptions]; of original version -> neccessary to split app in scenes for carplay -> after every React Native Update you have to copy code Sections of the RCTAppDelegate "application" function except the uncomment "self.window" etc code /
/ Copied code from RCTAppDelegate application function (start) / BOOL enableTM = NO;
if RCT_NEW_ARCH_ENABLED
enableTM = self.turboModuleEnabled;
endif
RCTAppSetupPrepareApp(application, enableTM);
if (!self.bridge) { self.bridge = [self createBridgeWithDelegate:self launchOptions:launchOptions]; }
if RCT_NEW_ARCH_ENABLED
self.bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:self.bridge contextContainer:_contextContainer]; self.bridge.surfacePresenter = self.bridgeAdapter.surfacePresenter;
[self unstable_registerLegacyComponents];
endif
NSDictionary initProps = [self prepareInitialProps]; UIView rootView = [self createRootViewWithBridge:self.bridge moduleName:self.moduleName initProps:initProps];
// self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; // UIViewController *rootViewController = [self createRootViewController]; // rootViewController.view = rootView; // self.window.rootViewController = rootViewController; // [self.window makeKeyAndVisible]; // ^^ // ^^ Ignored Code from RCTAppDelegate because of SceneDelegate logic for carplay // ^^ Code is moved into SceneDelegate class
/ Copied code from RCTAppDelegate application function (end) /
// make bridge and rootview shareable to enable acces from SceneDelegate [[[RNBridgeInstanceHolderObjC alloc]init] setBridgeAndRootView:(self.bridge) rootView:(rootView)]; // custom_carplay
[RNSplashScreen show]; // custom_splashscreen
/ Copied code from RCTAppDelegate application function (start) / return YES; / Copied code from RCTAppDelegate application function (end) /
/ CUSTOM_IMPLEMENTATION_END / }
/ CUSTOM_IMPLEMENTATION_START / // Copied code from RCTAppDelegate (start)
ifdef RCT_NEW_ARCH_ENABLED
// Hardcoding the Concurrent Root as it it not recommended to // have the concurrentRoot turned off when Fabric is enabled. initProps[kRNConcurrentRoot] = @([self fabricEnabled]);
endif
return initProps; } // Copied code from RCTAppDelegate (end) / CUSTOM_IMPLEMENTATION_END / `
No the Problem is: Music Apps like Spotify, Tidal etc show carplay content if the phone apps are terminated and i click on the App Icon in Carplay Simulator. My App doesnt so, it shows an empty screen. If the phone app is in background or active, everything is working well. I have no idea what i am doing wrong. Please help! Thanks in advance!
Some code of the CarPlay SceneDelegate:
import Foundation import CarPlay class SceneDelegateCarPlay: UIResponder, CPTemplateApplicationSceneDelegate { ... func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, didConnect interfaceController: CPInterfaceController) { print("SceneDelegateCarPlay.templateApplicationScene(), didConnect") // start render content ... }
"templateApplicationScene" is called and render content when:
I wonder how i can render content outside of a case like these
React Native Version
0.72.7
Output of
npx react-native info
System: OS: macOS 14.1.1 CPU: (10) arm64 Apple M1 Pro Memory: 98.50 MB / 16.00 GB Shell: version: "5.9" path: /bin/zsh Binaries: Node: version: 16.17.1 path: ~/.nvm/versions/node/v16.17.1/bin/node Yarn: version: 1.22.19 path: ~/Projekte/Mediapioneer/app/node_modules/.bin/yarn npm: version: 8.15.0 path: ~/.nvm/versions/node/v16.17.1/bin/npm Watchman: version: 2023.10.23.00 path: /opt/homebrew/bin/watchman Managers: CocoaPods: version: 1.14.3 path: /opt/homebrew/bin/pod SDKs: iOS SDK: Platforms:
Steps to reproduce
See description
Snack, screenshot, or link to a repository