2

What is the recommended way to integrate CarPlay into an app which uses a SwiftUI lifecycle ?

@main
struct MyApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

How do I use the CPTemplateApplicationSceneDelegate here ?

  • Depending on what you ask : CarPlay has its own delegate and templates (aka User Interface view, buttons, list ) . You define initial delegate from the info file. – Ptit Xav Jul 08 '22 at 19:13
  • Check for [this](https://developer.apple.com/documentation/carplay/displaying_content_in_carplay) – Ptit Xav Jul 08 '22 at 19:20
  • @PtitXav your link describes the process for the Scene Delegate lifecycle which is a typical UIKIt one. If you start a project with SwiftUI you have the struct which inherits from App, there is no Scene Delegate – hbr4u7vb2sljksdf2 Jul 11 '22 at 08:00
  • Check for [thsi}(https://stackoverflow.com/questions/71524945/ios-15-4-swiftui-carplay-state-not-updating/72658942?noredirect=1#comment128824523_72658942) : it uses UIHostingController – Ptit Xav Jul 12 '22 at 13:58
  • @PtitXav but this is not an App lifecycle. He uses AppDelegate. – hbr4u7vb2sljksdf2 Jul 14 '22 at 11:52

2 Answers2

2

If you don't have a custom app delegate and/or scene delegate (you might need it eventually for stuff like push notifications) it should be enough to let your app know via the info.plist. You need a scene delegate for the CarPlay scene and add the following to your info.plist:

<dict>
    <key>UIApplicationSupportsMultipleScenes</key>
    <true/>
    <key>UISceneConfigurations</key>
    <dict>
        <key>CPTemplateApplicationSceneSessionRoleApplication</key>
        <array>
            <dict>
                <key>UISceneClassName</key>
                <string>CPTemplateApplicationScene</string>
                <key>UISceneConfigurationName</key>
                <string>TemplateSceneConfiguration</string>
                <key>UISceneDelegateClassName</key>
                <string>AppFeature.CarPlaySceneDelegate</string>
            </dict>
        </array>
    </dict>
</dict>

The value for UISceneConfigurationName is handed to you in the scene delegate's scene(_:willConnectTo:options:) session.configuration.name. The value for UISceneDelegateClassName has to match your CarPlay scene delegate's Type name. Note that if you encapsulate your CarPlay code in a package/framework you need to prefix the delegate's name with the module name (in this case AppFeature). If the delegate is in your app target just use CarPlaySceneDelegate.

An excerpt of the scene delegate might look like this:

class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate {
  let templateManager = TemplateManager() // see Apple's sample code

  func templateApplicationScene(_: CPTemplateApplicationScene, didConnect interfaceController: CPInterfaceController) {
    templateManager.connect(interfaceController)
  }

  func templateApplicationScene(_: CPTemplateApplicationScene, didDisconnectInterfaceController _: CPInterfaceController) {
    templateManager.disconnect()
  }
}
fruitcoder
  • 1,073
  • 8
  • 24
  • This doesn't work for me - I still get "Application does not implement CarPlay template application lifecycle methods in its scene delegate" – Halpo Jul 16 '23 at 19:23
2

I had a hell of a time with this, but finally got it working. First, add a UIApplicationSceneManifest to your plist. Note that UIApplicationSupportsMultipleScenes need not be true. It's really intended for macOS/Catalyst apps that support multiple windows. But a CarPlay scene isn't another window in this sense.

<plist>
<dict>
    <key>UIApplicationSceneManifest</key>
    <dict>
        <key>UIApplicationSupportsMultipleScenes</key>
        <false/>
        <key>UISceneConfigurations</key>
        <dict>
            <key>CPTemplateApplicationSceneSessionRoleApplication</key>
            <array>
                <dict>
                    <key>UISceneClassName</key>
                    <string>CPTemplateApplicationScene</string>
                    <key>UISceneDelegateClassName</key>
                    <string>$(PRODUCT_MODULE_NAME).CarPlaySceneDelegate</string>
                    <key>UISceneConfigurationName</key>
                    <string>CarPlay Configuration</string>
                </dict>
            </array>
        </dict>
    </dict>
</dict>
</plist>

Then your CarPlay scene delegate looks like this (note the class name matches the value of UISceneDelegateClassName from your plist)

import CarPlay

class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate {
    var interfaceController: CPInterfaceController?

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

        let carPlayUI = ...  // CPTabBarTemplate, CPListTemplate, CPGridTemplate, etc

        interfaceController.setRootTemplate(carPlayUI, animated: true) { success, error in
            // optional completion handler once CarPlay UI is displayed
        }
    }

    func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene,
                                  didDisconnect interfaceController: CPInterfaceController,
                                  from window: CPWindow) {
        self.interfaceController = nil
    }
}
Christopher Pickslay
  • 17,523
  • 6
  • 79
  • 92