hbr4u7vb2sljksdf2
hbr4u7vb2sljksdf2

Reputation: 318

Adding CarPlay to a SwiftUI lifecycle app

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 ?

Upvotes: 2

Views: 2199

Answers (2)

Christopher Pickslay
Christopher Pickslay

Reputation: 17762

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
    }
}

Upvotes: 3

fruitcoder
fruitcoder

Reputation: 1218

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()
  }
}

Upvotes: 2

Related Questions