CarPlay + SwiftUI App Lifecycle => not working

Hi

I identified an issue that I cannot resolve with CarPlay. I am now rewriting an existing app that I initially created with ObjectiveC/Swift/UIKit. The new app should be super elegant, and of course SwiftUI based.

The thing is, if I use the old App Lifecycle (using App Delegates or Scene Delegates) without SwiftUI, everything works fine and smooth. However, I don't really want that, but rather have a single code base and not mess with ****** workarounds.

The funny thing is, if I place the @main item to the SwiftUI part, it tells me the life cycle would not be implemented, but it is.

Anybody has an idea? Or this is a bug in SwiftUI / CarPlay?

I was trying to find some original code reference from Apple related to SwiftUI base apps and having a CarPlay 'extension' but couldn't find any - the latest WWDC code example was still based on the old framework

Thanks Marco

Here are the files


import UIKit

import SwiftUI



@main // < == if @main is here, it DOES NOT WORK fine -- using non Swift UI app cycle

/*

 2022-11-11 12:24:34.516461+0100 CarPlayTutorial[49059:1094508] *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Application does not implement CarPlay template application lifecycle methods in its scene delegate.'

 ...

 CoreSimulator 857.13 - Device: iPhone 14 Pro (A32C27BF-48D7-48EA-A32B-26A6CC562201) - Runtime: iOS 16.1 (20B72) - DeviceType: iPhone 14 Pro

 *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Application does not implement CarPlay template application lifecycle methods in its scene delegate.'

 (lldb)

 */

struct testappApp: App {

 @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

  var body: some Scene {

        WindowGroup {

            ContentView()

        }

    }

}

struct ContentView: View {

    var body: some View {

        Text("Hallo")

    }

}



//@main // < == if @main is here, it works fine -- using non Swift UI app cycle

class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        return true

    }

    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {

        if (connectingSceneSession.role == UISceneSession.Role.carTemplateApplication) {

            let scene =  UISceneConfiguration(name: "CarPlay", sessionRole: connectingSceneSession.role)

            scene.delegateClass = CarPlaySceneDelegate.self

            return scene

        } else {

            return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)

        }

    }

    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {

    }

}

the Scene Delegate that I used, but is largely ignored is

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        print(scene.debugDescription)

        print(session.debugDescription)

        print(connectionOptions.debugDescription)

        guard let _ = (scene as? UIWindowScene) else { return }

    }

    func sceneDidDisconnect(_ scene: UIScene) {

        print(scene.debugDescription)

    }

    func sceneDidBecomeActive(_ scene: UIScene) {

        print(scene.debugDescription)

    }

    //...

}

The CarPlay delegate is this




class CarPlaySceneDelegate: UIResponder  {

    var interfaceController: CPInterfaceController?

}

extension CarPlaySceneDelegate: CPTemplateApplicationSceneDelegate {

    func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, didConnect interfaceController: CPInterfaceController) {

        self.interfaceController = interfaceController

        self.interfaceController?.delegate = self

    }

    private func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, didDisconnect interfaceController: CPInterfaceController) {

        self.interfaceController = nil

    }

}

extension CarPlaySceneDelegate: CPTabBarTemplateDelegate {

    func tabBarTemplate(_ tabBarTemplate: CPTabBarTemplate, didSelect selectedTemplate: CPTemplate) {



    }

}

extension CarPlaySceneDelegate: CPInterfaceControllerDelegate {

    func templateWillAppear(_ aTemplate: CPTemplate, animated: Bool) {

        print("templateWillAppear", aTemplate)

    }



    func templateDidAppear(_ aTemplate: CPTemplate, animated: Bool) {

        print("templateDidAppear", aTemplate)

    }



    func templateWillDisappear(_ aTemplate: CPTemplate, animated: Bool) {

        print("templateWillDisappear", aTemplate)

    }



    func templateDidDisappear(_ aTemplate: CPTemplate, animated: Bool) {

        print("templateDidDisappear", aTemplate)

    }

}

the info plist section:


	<dict>

		<key>UIApplicationSupportsMultipleScenes</key>

		<false/>

		<key>UISceneConfigurations</key>

		<dict>

			<key>UIWindowSceneSessionRoleApplication</key>

			<array>

				<dict>

					<key>UISceneConfigurationName</key>

					<string>Default Configuration</string>

					<key>UISceneDelegateClassName</key>

					<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>

				</dict>

			</array>

			<key>UIWindowSceneSessionRoleExternalDisplay</key>

			<array>

				<dict>

					<key>UISceneClassName</key>

					<string>CPTemplateApplicationScene</string>

					<key>UISceneConfigurationName</key>

					<string>CarPlay</string>

					<key>UISceneDelegateClassName</key>

					<string>$(PRODUCT_MODULE_NAME).CarPlaySceneDelegate</string>

				</dict>

			</array>

		</dict>

	</dict>

Replies

Here are the console logs for the failure case


standard	13:07:40.422508+0100	CarPlay	connected session <CARSession: 0x600003404500> configuration: [<CARSessionConfiguration: 0x12da05010> name: (null), modelName: (null), manufacturer: (null), serialNumber: (null), transport: USB, ETC supported: NO, right hand drive: NO, limitableUserInterfaces: (), manufacturerIconLabel: OEM, manufacturerIconVisible: YES, night mode supported: NO, supports AC_BACK: YES, screens: (

    "<CARScreenInfo: 0x600001640370>, identifier: PurpleTVOut, availableInteractionModels: Touch, Knob, Touchpad, primaryInteractionModel: Touchpad, isLimited: NO, isNightMode: NO, supportsHiFi: NO, maxFPS: 0, physicalSize: {w: 0.000000, h: 0.000000}, pixelSize: {w: 0.000000, h: 0.000000}, wantsCornerMasks: NO, initialFocusOwner: YES, viewAreas: (\n    \"<CARScreenViewArea: 0x600001044500> {visibleFrame: {x: 0.000000, y: 0.000000, w: 1024.000000, h: 1024.000000} safeFrame: {x: 0.000000, y: 0.000000, w: 1024.000000, h: 1024.000000} displaysTransitionControl: 0 statusBarEdge: 0 supportsFocusTransfer: no}\"\n)"

)], [CC] Now Playing Album Art Mode: Unknown, [CC] User Interface Style: Automatic, [CC] Additional Safe Area Insets {top = 0.000000, left = 0.000000, bottom = 0.000000, right = 0.000000}, [CC] Dashboard rounded corners: {top = 0.000000, left = 0.000000, bottom = 0.000000, right = 0.000000}, night mode: unset, limit UI: unset, ETC available: unset

standard	13:07:40.427308+0100	CarPlay	Template scene content style updated to dark

standard	13:07:40.427956+0100	CarPlay	[0x600000a7dc70] Initialized with scene: <CPTemplateApplicationScene: 0x12e705510>; behavior: <_UIEventDeferringBehavior_CarPlay: 0x6000021e8c00>

standard	13:07:40.428045+0100	CarPlay	0x600002fd1590 setDelegate:<0x6000021e8fc0 _UIBacklightEnvironment> hasDelegate:YES for environment:Car[2-7]:ch.CarPlay.CarPlay

standard	13:07:40.428298+0100	CarPlay	Application declares audio entitlement.

standard	13:07:40.428313+0100	CarPlay	Connecting to listener endpoint for scene identifier: Car[2-7]:ch.CarPlay.CarPlay

standard	13:07:40.429475+0100	CarPlay	Scene will connect observer fired

standard	13:07:40.429487+0100	CarPlay	Attempting to deliver interface controller...

standard	13:07:40.429497+0100	CarPlay	App ready to receive interface controller

standard	13:07:40.429507+0100	CarPlay	App does not support any CarPlay template connection

 

Hi! It looks like there is a typo in your scene manifest in the Info.plist. Your CarPlay scene role is under the UIWindowSceneSessionRoleExternalDisplay key, but that's incorrect; it should be CPTemplateApplicationSceneSessionRoleApplication for a CarPlay template app.

Additionally, I believe it is expected that if you specify a scene manifest in the Info.plist, the app delegate method that returns a UISceneConfiguration will not be called.

For more details, be sure to see the CarPlay programming guide: https://developer.apple.com/carplay/documentation/CarPlay-App-Programming-Guide.pdf

  • Thanks @Frameworks Engineer, this finally got me up and running. I have to say, the CarPlay programming guide is not very helpful here. I started with a SwiftUI app then tried to add CarPlay integration and started down the scene configuration path, but apparently had my CPTemplateApplicationSceneSessionRoleApplication nested as another role under my UIWindowSceneSessionRoleApplication.

  • FWIW, you also don't need a UIWindowSceneSessionRoleApplication to use a scene configuration for your CarPlay scene. You can just leave the @main annotation on your SwuiftUI App Scene.

Add a Comment

Hiya, I am facing the same issue as you with adding CarPlay to a SwiftUI project. I tried your example but am getting a blank screen on the device. CarPlay works on the simulator. Did you manage to come up with a solution? Thanks!