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>