Files
Camper-Monitor/VanControl/CarPlay/CarPlaySceneDelegate.swift
T
fototeddyandClaude Opus 5 ae12d8ede4 CarPlay-Demo: Nivellierung auf dem Fahrzeugdisplay
Eigene Build-Konfiguration "CarPlay" samt Schema, damit der Versuchsbau neben
der TestFlight-App stehen kann: eigener Name, eigene Bundle-ID, persoenliches
Team, manuelles Signing. Debug und Release bleiben unberuehrt.

Wichtig dabei: Die Signing-Einstellungen stehen ausschliesslich in den
Target-Konfigurationen. Steht CODE_SIGN_STYLE auf Projektebene, schreibt der
naechste xcodebuild-Lauf alle uebrigen Konfigurationen auf Manual um und loescht
ihr Team – der TestFlight-Build waere damit hin.

Der CarPlay-Code haengt an `CARPLAY_DEMO` und existiert im regulaeren Build
nicht. Die Szene zeichnet in das gelieferte CPWindow; das CPMapTemplate darueber
bleibt leer, weil es auf dem Stellplatz nichts zu tippen gibt. Die Anzeige
sperrt sich oberhalb von 5 km/h selbst – erzwungen, nicht zugesagt.

Die Groessen sind auf die 400x240 Punkte ausgelegt, die CarPlay liefert, nicht
auf die 800x480 Pixel. In Pixeln gedachte Schriftgroessen sind auf dem
Fahrzeugdisplay doppelt zu gross und schneiden den Text ab.

Bekannt: Unter iOS 26.5 stuerzt Apples CarPlayTemplateUIHost beim Aufbau der
Navigationsleiste ab (`_updateShareButtonVisibility` fragt seinen
destinationSharingDelegate nach `vehicleSupportsDestinationSharing`, den dieser
nicht kennt). Beides ist privates API und von der App aus nicht erreichbar;
unter iOS 18 laeuft es. Szene und Root-Template werden vorher in beiden
Faellen akzeptiert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 18:35:33 +02:00

97 lines
4.2 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#if CARPLAY_DEMO
import CarPlay
import OSLog
import SwiftUI
/// Einstiegspunkt der CarPlay-Szene.
///
/// Mit dem Navigations-Entitlement liefert CarPlay ein eigenes `CPWindow`, in
/// das frei gezeichnet werden darf anders als bei den Template-Kategorien,
/// die nur vorgegebene Bausteine erlauben und höchstens alle zehn Sekunden
/// aktualisieren. Genau deshalb hängt hier die Libelle drin: Beim Rangieren
/// muss sie der Bewegung des Fahrzeugs folgen, nicht ihr hinterherlaufen.
///
/// Das `CPMapTemplate` darüber ist nur die Bedienleiste. Es bleibt bewusst
/// leer: Auf dem Stellplatz gibt es nichts zu tippen, es gibt nur etwas
/// abzulesen.
@objc(CarPlaySceneDelegate)
final class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate, CPMapTemplateDelegate {
private var interfaceController: CPInterfaceController?
/// Ohne eigenes Logging ist beim Debuggen nicht zu unterscheiden, ob die
/// Szene gar nicht verbindet oder ob CarPlay das Template ablehnt.
private static let log = Logger(subsystem: "de.s0.fototeddy.VanControl", category: "CarPlay")
func templateApplicationScene(
_ templateApplicationScene: CPTemplateApplicationScene,
didConnect interfaceController: CPInterfaceController,
to window: CPWindow
) {
Self.log.notice("didConnect: Szene verbunden, Fenster \(NSCoder.string(for: window.bounds.size), privacy: .public)")
self.interfaceController = interfaceController
let root = CarPlayLevelScreen()
.environment(CarPlayLevelSource.shared)
let host = UIHostingController(rootView: root)
host.view.backgroundColor = .black
window.rootViewController = host
window.makeKeyAndVisible()
CarPlayLevelSource.shared.start()
// Bewusst nackt konfiguriert: iOS 26 laesst den CarPlayTemplateUIHost
// beim Aufbau der Navigationsleiste ueber den Share-Button stolpern
// (`_updateShareButtonVisibility` -> unrecognized selector), sobald die
// Leiste erzwungen wird. Ohne Buttons und mit der Standard-Automatik
// gibt es nichts zu konfigurieren und auf dem Stellplatz gibt es
// ohnehin nichts zu tippen.
let map = CPMapTemplate()
map.mapButtons = []
map.leadingNavigationBarButtons = []
map.trailingNavigationBarButtons = []
map.hidesButtonsWithNavigationBar = true
map.mapDelegate = self
interfaceController.setRootTemplate(map, animated: false) { success, error in
if let error {
Self.log.error("setRootTemplate abgelehnt: \(error.localizedDescription, privacy: .public)")
} else {
Self.log.notice("setRootTemplate ok: \(success, privacy: .public)")
}
}
}
func templateApplicationScene(
_ templateApplicationScene: CPTemplateApplicationScene,
didDisconnectInterfaceController interfaceController: CPInterfaceController,
from window: CPWindow
) {
Self.log.notice("didDisconnect: Szene getrennt")
self.interfaceController = nil
window.rootViewController = nil
CarPlayLevelSource.shared.stop()
}
// MARK: - Zielteilung
// CarPlay bietet seit iOS 26 an, ein Navigationsziel an das Fahrzeug zu
// übergeben. Wir haben keine Ziele aber das Protokoll vollständig zu
// bedienen ist Teil davon, eine korrekte Navigations-App zu sein. Der
// Verdacht dabei: Apples Template-Host löst seinen Sharing-Delegate nur
// dann sauber auf, wenn die App diese Methoden anbietet.
@available(iOS 26.4, *)
func mapTemplate(_ mapTemplate: CPMapTemplate, willShareDestinationFor trip: CPTrip) {
Self.log.notice("willShareDestination die App teilt keine Ziele")
}
@available(iOS 26.4, *)
func mapTemplate(_ mapTemplate: CPMapTemplate, didShareDestinationFor trip: CPTrip) {
Self.log.notice("didShareDestination")
}
@available(iOS 26.4, *)
func mapTemplate(_ mapTemplate: CPMapTemplate, didFailToShareDestinationFor trip: CPTrip, error: Error) {
Self.log.error("didFailToShareDestination: \(error.localizedDescription, privacy: .public)")
}
}
#endif