From 92933ef46bcb75313567dda61653df6bc9680448 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Thu, 11 Jun 2026 14:12:04 +0200 Subject: [PATCH] fix(apple/tvOS): system-style slide for in-stack pushes (swiftui-navigation-transitions) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SwiftUI's NavigationStack on tvOS animates pushes as a bare crossfade with no public customization — the system Settings app slides. The home stack now applies .customNavigationTransition(.slide) on tvOS via davdroman/swiftui-navigation-transitions (MIT, tvOS 13+), covering the top-level routes AND the settings pickers' drill-ins. The dependency is referenced by the Xcode PROJECT only and linked solely by the Punktfunk-tvOS target: its manifest (no macOS platform declared vs 10.15 deps) breaks SwiftPM whole-graph validation for plain `swift build`, and the #if os(tvOS) import never compiles in the macOS-only SwiftPM dev shell anyway. Headless builds need xcodebuild -skipMacroValidation (the lib pulls Swift macro packages; in the Xcode UI it's a one-time Trust & Enable prompt). iOS/macOS keep their untouched system navigation animations. Co-Authored-By: Claude Fable 5 --- clients/apple/Package.swift | 3 + .../apple/Punktfunk.xcodeproj/project.pbxproj | 20 +++++++ .../xcshareddata/swiftpm/Package.resolved | 60 +++++++++++++++++++ .../Sources/PunktfunkClient/ContentView.swift | 8 +++ 4 files changed, 91 insertions(+) create mode 100644 clients/apple/Punktfunk.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/clients/apple/Package.swift b/clients/apple/Package.swift index aa4a389..06c126a 100644 --- a/clients/apple/Package.swift +++ b/clients/apple/Package.swift @@ -24,6 +24,9 @@ let package = Package( ] ), // Development app shell (swift run PunktfunkClient): connect form → stream + input. + // (The tvOS slide-transition package is referenced by the Xcode PROJECT only — + // its manifest breaks SwiftPM whole-graph validation on macOS, and only the + // Punktfunk-tvOS target links it; the #if os(tvOS) import never compiles here.) .executableTarget(name: "PunktfunkClient", dependencies: ["PunktfunkKit"]), .testTarget(name: "PunktfunkKitTests", dependencies: ["PunktfunkKit"]), ] diff --git a/clients/apple/Punktfunk.xcodeproj/project.pbxproj b/clients/apple/Punktfunk.xcodeproj/project.pbxproj index b4fd44b..4598429 100644 --- a/clients/apple/Punktfunk.xcodeproj/project.pbxproj +++ b/clients/apple/Punktfunk.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ AA0000000000000000000005 /* PunktfunkKit in Frameworks */ = {isa = PBXBuildFile; productRef = AA0000000000000000000006 /* PunktfunkKit */; }; BB0000000000000000000005 /* PunktfunkKit in Frameworks */ = {isa = PBXBuildFile; productRef = BB0000000000000000000006 /* PunktfunkKit */; }; CC0000000000000000000005 /* PunktfunkKit in Frameworks */ = {isa = PBXBuildFile; productRef = CC0000000000000000000006 /* PunktfunkKit */; }; + DD0000000000000000000003 /* SwiftUINavigationTransitions in Frameworks */ = {isa = PBXBuildFile; productRef = DD0000000000000000000002 /* SwiftUINavigationTransitions */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -53,6 +54,7 @@ buildActionMask = 2147483647; files = ( CC0000000000000000000005 /* PunktfunkKit in Frameworks */, + DD0000000000000000000003 /* SwiftUINavigationTransitions in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -148,6 +150,7 @@ name = "Punktfunk-tvOS"; packageProductDependencies = ( CC0000000000000000000006 /* PunktfunkKit */, + DD0000000000000000000002 /* SwiftUINavigationTransitions */, ); productName = "Punktfunk-tvOS"; productReference = CC0000000000000000000001 /* Punktfunk-tvOS.app */; @@ -177,6 +180,7 @@ mainGroup = AA0000000000000000000007; packageReferences = ( AA000000000000000000000F /* XCLocalSwiftPackageReference "." */, + DD0000000000000000000001 /* XCRemoteSwiftPackageReference "swiftui-navigation-transitions" */, ); preferredProjectObjectVersion = 77; productRefGroup = AA0000000000000000000008 /* Products */; @@ -588,6 +592,17 @@ }; /* End XCLocalSwiftPackageReference section */ +/* Begin XCRemoteSwiftPackageReference section */ + DD0000000000000000000001 /* XCRemoteSwiftPackageReference "swiftui-navigation-transitions" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/davdroman/swiftui-navigation-transitions"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.18.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + /* Begin XCSwiftPackageProductDependency section */ AA0000000000000000000006 /* PunktfunkKit */ = { isa = XCSwiftPackageProductDependency; @@ -601,6 +616,11 @@ isa = XCSwiftPackageProductDependency; productName = PunktfunkKit; }; + DD0000000000000000000002 /* SwiftUINavigationTransitions */ = { + isa = XCSwiftPackageProductDependency; + package = DD0000000000000000000001 /* XCRemoteSwiftPackageReference "swiftui-navigation-transitions" */; + productName = SwiftUINavigationTransitions; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = AA000000000000000000000D /* Project object */; diff --git a/clients/apple/Punktfunk.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/clients/apple/Punktfunk.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..be619c5 --- /dev/null +++ b/clients/apple/Punktfunk.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,60 @@ +{ + "originHash" : "5d17a752eb57d190a90cbd663718ff44034b24fe0ae1baafea7677db2d49da6f", + "pins" : [ + { + "identity" : "objc-runtime-tools", + "kind" : "remoteSourceControl", + "location" : "https://github.com/davdroman/objc-runtime-tools", + "state" : { + "revision" : "04715d0c98d366d7000be32c0c81b4ba87001910", + "version" : "0.5.1" + } + }, + { + "identity" : "swift-once-macro", + "kind" : "remoteSourceControl", + "location" : "https://github.com/davdroman/swift-once-macro", + "state" : { + "revision" : "5f9d4e77cd95335fe14b44064fcf7f96e8ed56a0", + "version" : "1.1.1" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-syntax", + "state" : { + "revision" : "79e4b74a295b6eb74a8b585e3a39d29e70c1dbd1", + "version" : "603.0.2" + } + }, + { + "identity" : "swiftui-introspect", + "kind" : "remoteSourceControl", + "location" : "https://github.com/siteline/swiftui-introspect", + "state" : { + "revision" : "aead9358a55f635d62d885aeb9105752c0213aec", + "version" : "27.0.0-beta.1" + } + }, + { + "identity" : "swiftui-navigation-transitions", + "kind" : "remoteSourceControl", + "location" : "https://github.com/davdroman/swiftui-navigation-transitions", + "state" : { + "revision" : "78287a0adf2ed35c40dc6445d0c7fc6fba236076", + "version" : "0.18.0" + } + }, + { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state" : { + "revision" : "cb281f343fd953280336dcbd3822cdf47c182f5b", + "version" : "1.10.0" + } + } + ], + "version" : 3 +} diff --git a/clients/apple/Sources/PunktfunkClient/ContentView.swift b/clients/apple/Sources/PunktfunkClient/ContentView.swift index 9d3846f..ce8d968 100644 --- a/clients/apple/Sources/PunktfunkClient/ContentView.swift +++ b/clients/apple/Sources/PunktfunkClient/ContentView.swift @@ -13,6 +13,9 @@ import AppKit #endif import PunktfunkKit import SwiftUI +#if os(tvOS) +import SwiftUINavigationTransitions +#endif struct ContentView: View { @StateObject private var model = SessionModel() @@ -181,6 +184,11 @@ struct ContentView: View { #if os(macOS) .frame(minWidth: 480, minHeight: 360) #endif + #if os(tvOS) + // The Settings-app slide for every push in this stack (top-level routes AND + // the pickers' drill-ins) — SwiftUI's default on tvOS is a bare crossfade. + .customNavigationTransition(.slide) + #endif #if !os(tvOS) .sheet(isPresented: $showAddHost) { AddHostSheet { store.add($0) }