birkir / react-native-carplay

CarPlay with React Native
https://birkir.dev/react-native-carplay/
MIT License
637 stars 107 forks source link

Cannot Build CarPlay App On IOS #199

Closed EliG-TA closed 3 weeks ago

EliG-TA commented 1 month ago

Hey! I cannot understand why I am having so much difficulty getting this library setup for IOS. I got it working with minimal effort on my part for Android. This issue is happening after I followed meticulously what the documentations say on setting up for IOS, but to no avail. What could I possibly be doing wrong. FYI, I'm trying very hard to use this library with my Expo project even though this is a bare bones React Native library and it was not built for this use case. However, I feel like I am getting very close. I am fully aware that this library has been transitioning from Objective-C in favor of Swift for much of the code base, however I since I am trying to keep my App stable being that it uses a lot of Expo, I would like to keep it in Objective-C if possible.

Here is the error output:

⚠️ ld: Could not find or use auto-linked framework 'CoreAudioTypes': framework 'CoreAudioTypes' not found ❌ clang: error: linker command failed with exit code 1 (use -v to see invocation)

Here is my relevant project folder layout and code:

. └── ./ios/ ├── ./ios/build/ ├── ./ios/Pods/ ├── ./ios/MyProject/ │ ├── ./ios/MyProject/Images.xcassets/ │ ├── ./ios/MyProject/Supporting/ │ ├── ./ios/MyProject/AppDelegate.h │ ├── ./ios/MyProject/AppDelegate.mm │ ├── ./ios/MyProject/CarSceneDelegate.h │ ├── ./ios/MyProject/CarSceneDelegate.m │ ├── ./ios/MyProject/Info.plist │ ├── ./ios/MyProject/main.m │ ├── ./ios/MyProject/noop-file.swift │ ├── ./ios/MyProject/PhoneSceneDelegate.h │ ├── ./ios/MyProject/PhoneSceneDelegate.m │ ├── ./ios/MyProject/SplashScreen.storyboard │ ├── ./ios/MyProject/MyProject-Bridging-Header.h │ └── ./ios/MyProject/MyProject.entitlements ├── ./ios/MyProject.xcodeproj/ ├── ./ios/MyProject.xcworkspace/ └── ./ios/Other Files

AppDelegate.h

#import <Foundation/Foundation.h>
#import <React/RCTBridgeDelegate.h>
#import <RCTAppDelegate.h>
#import <UIKit/UIKit.h>
#import <Expo/Expo.h>
#import <CarPlay/CarPlay.h>

@interface AppDelegate : EXAppDelegateWrapper <RCTBridgeDelegate, UIApplicationDelegate, CPApplicationDelegate>

@property (nonatomic, strong) UIWindow *window;
@property (nonatomic, strong) UIView *rootView;
@property (nonatomic, strong) RCTBridge *bridge;

- (void)initAppFromSceneWithConnectionOptions:(UISceneConnectionOptions *)connectionOptions;

@end

AppDelegate.mm

#import <RNCarPlay.h>
#import <React/RCTBridge.h>
#import <React/RCTRootView.h>
#import <Firebase/Firebase.h>

#import "CarSceneDelegate.h"
#import "PhoneSceneDelegate.h"

#import "AppDelegate.h"

#import <React/RCTBundleURLProvider.h>
#import <React/RCTLinkingManager.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// @generated begin @react-native-firebase/app-didFinishLaunchingWithOptions - expo prebuild (DO NOT MODIFY) sync-ecd111c37e49fdd1ed6354203cd6b1e2a38cccda
[FIRApp configure];
// @generated end @react-native-firebase/app-didFinishLaunchingWithOptions
  self.moduleName = @"main";

  // 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];
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
  return [self getBundleURL];
}

- (NSURL *)getBundleURL
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"];
#else
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

// Linking API
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
  return [super application:application openURL:url options:options] || [RCTLinkingManager application:application openURL:url options:options];
}

// Universal Links
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
  BOOL result = [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
  return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || result;
}

// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
  return [super application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}

// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
  return [super application:application didFailToRegisterForRemoteNotificationsWithError:error];
}

// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
  return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}

- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
    if (@available(iOS 13.0, *)) {
        if (connectingSceneSession.role == CPTemplateApplicationSceneSessionRoleApplication) {
            UISceneConfiguration *scene = [[UISceneConfiguration alloc] initWithName:@"CarPlay" sessionRole:connectingSceneSession.role];
            scene.delegateClass = [CarSceneDelegate class];
            return scene;
        } else {
            UISceneConfiguration *scene = [[UISceneConfiguration alloc] initWithName:@"Phone" sessionRole:connectingSceneSession.role];
            scene.delegateClass = [PhoneSceneDelegate class];
            return scene;
        }
    } else {
        return nil;
    }
}

- (UIView *)rootView {
    if (!_rootView) {
        if (!self.bridge) {
            self.bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:nil];
        }
        _rootView = [[RCTRootView alloc] initWithBridge:self.bridge
                                             moduleName:@"main"
                                      initialProperties:nil];
    }
    return _rootView;
}

- (void)initAppFromSceneWithConnectionOptions:(UISceneConnectionOptions *)connectionOptions
{
    if (!self.bridge) {
        self.bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:[self connectionOptionsToLaunchOptions:connectionOptions]];
    }
    self.rootView = [self rootView];
}

- (NSDictionary *)connectionOptionsToLaunchOptions:(UISceneConnectionOptions *)connectionOptions
{
    NSMutableDictionary *launchOptions = [NSMutableDictionary dictionary];

    if (connectionOptions.notificationResponse) {
        launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] = connectionOptions.notificationResponse.notification.request.content.userInfo;
    }

    if (connectionOptions.userActivities.count > 0) {
        NSUserActivity *userActivity = connectionOptions.userActivities.allObjects.firstObject;
        NSDictionary *userActivityDictionary = @{
            @"UIApplicationLaunchOptionsUserActivityTypeKey": userActivity.activityType,
            @"UIApplicationLaunchOptionsUserActivityKey": userActivity
        };
        launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey] = userActivityDictionary;
    }

    return launchOptions;
}

- (void)application:(UIApplication *)application didConnectCarInterfaceController:(CPInterfaceController *)interfaceController toWindow:(CPWindow *)window {
  [RNCarPlay connectWithInterfaceController:interfaceController window:window];
}

- (void)application:(nonnull UIApplication *)application didDisconnectCarInterfaceController:(nonnull CPInterfaceController *)interfaceController fromWindow:(nonnull CPWindow *)window {
  [RNCarPlay disconnect];
}

@end

CarSceneDelegate.h

#import <UIKit/UIKit.h>
#import <CarPlay/CarPlay.h>

@interface CarSceneDelegate : UIResponder <CPTemplateApplicationSceneDelegate>

@end

CarSceneDelegate

#import "CarSceneDelegate.h"
#import "AppDelegate.h"
#import <RNCarPlay.h>

@implementation CarSceneDelegate

- (void)templateApplicationScene:(CPTemplateApplicationScene *)templateApplicationScene
       didConnectInterfaceController:(CPInterfaceController *)interfaceController {
    AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
    [appDelegate initAppFromSceneWithConnectionOptions:templateApplicationScene.connectionOptions];
    [RNCarPlay connectWithInterfaceController:interfaceController window:templateApplicationScene.carWindow];
}

- (void)templateApplicationScene:(CPTemplateApplicationScene *)templateApplicationScene
       didDisconnectInterfaceController:(CPInterfaceController *)interfaceController {
    [RNCarPlay disconnect];
}

@end

main.m

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
  @autoreleasepool {
    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
  }
}

PhoneSceneDelegate.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface PhoneSceneDelegate : UIResponder <UIWindowSceneDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

PhoneSceneDelegate.m

#import "PhoneSceneDelegate.h"
#import "AppDelegate.h"

@implementation PhoneSceneDelegate

- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
    AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
    UIWindowScene *windowScene = (UIWindowScene *)scene;

    [appDelegate initAppFromSceneWithConnectionOptions:connectionOptions];

    UIViewController *rootViewController = [[UIViewController alloc] init];
    rootViewController.view = appDelegate.rootView;
    UIWindow *window = [[UIWindow alloc] initWithWindowScene:windowScene];
    window.rootViewController = rootViewController;
    self.window = window;
    [window makeKeyAndVisible];
}

@end

To Reproduce Try building the project with my configuration for IOS.

Expected behavior To build successfully for IOS.

Screenshots/Videos If applicable, add screenshots to help explain your problem.

CarPlay (please complete the following information):

Additional context I have the following Apple entitlements:
"com.apple.developer.carplay-audio": true, "com.apple.developer.playable-content": true

EliG-TA commented 1 month ago

Here is an update. It seems like this is a step forward in the right direction as this patch seems to allow my app to compile except I have not tested it working in runtime.

https://github.com/birkir/react-native-carplay/issues/101#issuecomment-2186383765

kudos to @janwiebe-jump and @casperolesen

UPDATE: 08.02.2024

Got basic Apple CarPlay working fully with much tweaking and help from above thread.

janwiebe-jump commented 2 weeks ago

@EliG-TA could you please share your code? I am running into issues with Expo SDK 51, I keep getting build errors and a black screen.