From 38f8f69a70e3267c83fdf9fb9ec767e1c67897af Mon Sep 17 00:00:00 2001 From: David Roman <2538074+davdroman@users.noreply.github.com> Date: Thu, 10 Nov 2022 02:46:44 +0000 Subject: [PATCH] macCatalyst, tvOS support (#30) Screenshot 2022-11-10 at 02 26 20 ![image](https://user-images.githubusercontent.com/2538074/200985122-0d317808-171e-4d05-8acb-685818a08174.png) Closes #12. --- .github/workflows/ci.yml | 2 ++ .spi.yml | 4 +++ Demo/Demo.xcodeproj/project.pbxproj | 18 ++++++++++--- Demo/Demo/AppDelegate.swift | 6 +++-- Demo/Demo/Demo.entitlements | 10 ++++++++ Demo/Demo/PageView.swift | 25 +++++++++++++------ Demo/Demo/Pages.swift | 10 ++++++-- Demo/Demo/RootView.swift | 6 +++-- Demo/Demo/SettingsView.swift | 2 ++ Package.swift | 2 ++ .../NavigationTransition+UIKit.swift | 24 ++++++++++++------ 11 files changed, 85 insertions(+), 24 deletions(-) create mode 100644 Demo/Demo/Demo.entitlements diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c88c8f2f..c04f8ca2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,8 @@ jobs: - macos-12 platform: - iOS + - mac-catalyst + - tvOS swift: - 5.5 - 5.6 diff --git a/.spi.yml b/.spi.yml index 1487742d..2e30d857 100644 --- a/.spi.yml +++ b/.spi.yml @@ -4,3 +4,7 @@ builder: - platform: ios scheme: NavigationTransitions documentation_targets: [NavigationTransitions, NavigationTransition, AtomicTransition, Animator, Animation] + - platform: macos-xcodebuild + scheme: NavigationTransitions + - platform: tvos + scheme: NavigationTransitions diff --git a/Demo/Demo.xcodeproj/project.pbxproj b/Demo/Demo.xcodeproj/project.pbxproj index 48c2ce54..29515664 100644 --- a/Demo/Demo.xcodeproj/project.pbxproj +++ b/Demo/Demo.xcodeproj/project.pbxproj @@ -12,7 +12,7 @@ D5535834290E9718009E5D72 /* Swing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D553582C290E9718009E5D72 /* Swing.swift */; }; D5535835290E9718009E5D72 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D553582D290E9718009E5D72 /* RootView.swift */; }; D5535836290E9718009E5D72 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D553582E290E9718009E5D72 /* SceneDelegate.swift */; }; - D5535837290E9718009E5D72 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D553582F290E9718009E5D72 /* LaunchScreen.storyboard */; }; + D5535837290E9718009E5D72 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D553582F290E9718009E5D72 /* LaunchScreen.storyboard */; platformFilter = ios; }; D5535839290E9718009E5D72 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5535831290E9718009E5D72 /* AppDelegate.swift */; }; D553583F290E97C5009E5D72 /* NavigationTransitions in Frameworks */ = {isa = PBXBuildFile; productRef = D553583E290E97C5009E5D72 /* NavigationTransitions */; }; D5535843290F4BEA009E5D72 /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5535842290F4BEA009E5D72 /* AppView.swift */; }; @@ -36,6 +36,7 @@ D5535842290F4BEA009E5D72 /* AppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = ""; }; D5535844290F52F7009E5D72 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; D5535846290F5E6F009E5D72 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; + D571826B291C9426003672F5 /* Demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Demo.entitlements; sourceTree = ""; }; D5755A78291ADC00007F2201 /* Zoom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Zoom.swift; sourceTree = ""; }; D5AAF4042911C59E009743D3 /* PageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageView.swift; sourceTree = ""; }; D5AAF4062911C621009743D3 /* Pages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pages.swift; sourceTree = ""; }; @@ -73,6 +74,7 @@ D553581D290E9691009E5D72 /* Demo */ = { isa = PBXGroup; children = ( + D571826B291C9426003672F5 /* Demo.entitlements */, D553583C290E978C009E5D72 /* Info.plist */, D553582F290E9718009E5D72 /* LaunchScreen.storyboard */, D5535831290E9718009E5D72 /* AppDelegate.swift */, @@ -317,6 +319,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Demo/Demo.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Demo/Preview Content\""; @@ -337,10 +340,14 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = mn.dro.Demo; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3"; + TVOS_DEPLOYMENT_TARGET = 13.0; }; name = Debug; }; @@ -350,6 +357,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Demo/Demo.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Demo/Preview Content\""; @@ -370,9 +378,13 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = mn.dro.Demo; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3"; + TVOS_DEPLOYMENT_TARGET = 13.0; }; name = Release; }; diff --git a/Demo/Demo/AppDelegate.swift b/Demo/Demo/AppDelegate.swift index 85da7d1c..6cd13707 100644 --- a/Demo/Demo/AppDelegate.swift +++ b/Demo/Demo/AppDelegate.swift @@ -2,6 +2,7 @@ import SwiftUI @main final class AppDelegate: UIResponder, UIApplicationDelegate { + #if !os(tvOS) func applicationDidFinishLaunching(_ application: UIApplication) { customizeNavigationBarAppearance() customizeTabBarAppearance() @@ -18,7 +19,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { proxy.scrollEdgeAppearance = customAppearance proxy.compactAppearance = customAppearance proxy.standardAppearance = customAppearance - if #available(iOS 15.0, *) { + if #available(iOS 15.0, tvOS 15, *) { proxy.compactScrollEdgeAppearance = customAppearance } } @@ -31,10 +32,11 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { let proxy = UITabBar.appearance() proxy.standardAppearance = customAppearance - if #available(iOS 15, *) { + if #available(iOS 15, tvOS 15, *) { proxy.scrollEdgeAppearance = customAppearance } } + #endif func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) diff --git a/Demo/Demo/Demo.entitlements b/Demo/Demo/Demo.entitlements new file mode 100644 index 00000000..ee95ab7e --- /dev/null +++ b/Demo/Demo/Demo.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/Demo/Demo/PageView.swift b/Demo/Demo/PageView.swift index 34488634..01617c41 100644 --- a/Demo/Demo/PageView.swift +++ b/Demo/Demo/PageView.swift @@ -14,7 +14,7 @@ struct PageView: View { ZStack { Rectangle() .do { - if #available(iOS 16, *) { + if #available(iOS 16, tvOS 16, *) { $0.fill(color.gradient) } else { $0.fill(color) @@ -33,23 +33,34 @@ struct PageView: View { .shadow(color: .white.opacity(0.25), radius: 1, x: 0, y: 1) .frame(maxWidth: .infinity, maxHeight: .infinity) .foregroundColor(Color(white: 0.14)) - if let link = link, let destination = destination { - if #available(iOS 16, *) { - NavigationLink(value: number + 1) { link } - } else { - NavigationLink(destination: destination) { link } + .frame(maxWidth: 1200) + + Group { + if let link = link, let destination = destination { + if #available(iOS 16, tvOS 16, *) { + NavigationLink(value: number + 1) { link } + } else { + NavigationLink(destination: destination) { link } + } } } + #if os(tvOS) + .frame(maxWidth: 600) + #else + .frame(maxWidth: 300) + #endif } .multilineTextAlignment(.center) .padding(.horizontal) .padding(.bottom, 30) } + #if !os(tvOS) .navigationBarTitle(Text(title), displayMode: .inline) + #endif .navigationBarItems( trailing: Button(action: { appState.isPresentingSettings = true }) { Group { - if #available(iOS 14, *) { + if #available(iOS 14, tvOS 16, *) { Image(systemName: "gearshape") } else { Image(systemName: "gear") diff --git a/Demo/Demo/Pages.swift b/Demo/Demo/Pages.swift index e69dce7a..b76355cc 100644 --- a/Demo/Demo/Pages.swift +++ b/Demo/Demo/Pages.swift @@ -14,7 +14,7 @@ struct PageOne: View { PageTwo() } .do { - if #available(iOS 16, *) { + if #available(iOS 16, tvOS 16, *) { $0.navigationDestination(for: Int.self) { number in switch number { case 1: PageOne() @@ -125,10 +125,14 @@ struct PageLink: View { var body: some View { ZStack { + #if !os(tvOS) RoundedRectangle(cornerRadius: 6, style: .continuous) .fill(Color.blue.opacity(0.8)) + #endif Text(title) + #if !os(tvOS) .foregroundColor(.white) + #endif .font(.system(size: 18, weight: .medium, design: .rounded)) } .frame(maxHeight: 50) @@ -150,7 +154,7 @@ struct Code: View { let shape = RoundedRectangle(cornerRadius: 4, style: .circular) Text(content) - .frame(maxWidth: .infinity, alignment: .leading) + .frame(maxWidth: 500, alignment: .leading) .padding(10) .lineLimit(lineLimit) .multilineTextAlignment(.leading) @@ -158,6 +162,7 @@ struct Code: View { .font(.system(size: 14, design: .monospaced)) .background(shape.stroke(Color(white: 0.1).opacity(0.35), lineWidth: 1)) .background(Color(white: 0.94).opacity(0.6).clipShape(shape)) + #if !os(tvOS) .do { if #available(iOS 15, *) { $0.textSelection(.enabled) @@ -165,5 +170,6 @@ struct Code: View { $0 } } + #endif } } diff --git a/Demo/Demo/RootView.swift b/Demo/Demo/RootView.swift index 9cb47d3b..84e69b8d 100644 --- a/Demo/Demo/RootView.swift +++ b/Demo/Demo/RootView.swift @@ -6,7 +6,7 @@ struct RootView: View { var body: some View { Group { - if #available(iOS 16, *) { + if #available(iOS 16, tvOS 16, *) { NavigationStack { PageOne() } @@ -21,6 +21,8 @@ struct RootView: View { appState.transition().animation(appState.animation()), interactivity: appState.interactivity() ) - .sheet(isPresented: $appState.isPresentingSettings, content: SettingsView.init) + .sheet(isPresented: $appState.isPresentingSettings) { + SettingsView().environmentObject(appState) + } } } diff --git a/Demo/Demo/SettingsView.swift b/Demo/Demo/SettingsView.swift index 3cc21dd1..87deb38a 100644 --- a/Demo/Demo/SettingsView.swift +++ b/Demo/Demo/SettingsView.swift @@ -20,7 +20,9 @@ struct SettingsView: View { picker("Interactivity", $appState.interactivity) } } + #if !os(tvOS) .navigationBarTitle("Settings", displayMode: .inline) + #endif .navigationBarItems( leading: Button("Shuffle", action: shuffle), trailing: Button(action: dismiss) { Text("Done").bold() } diff --git a/Package.swift b/Package.swift index 1f85d2d4..261281ad 100644 --- a/Package.swift +++ b/Package.swift @@ -7,6 +7,8 @@ let package = Package( name: "swiftui-navigation-transitions", platforms: [ .iOS(.v13), + .macCatalyst(.v13), + .tvOS(.v13), ] ) diff --git a/Sources/NavigationTransitions/NavigationTransition+UIKit.swift b/Sources/NavigationTransitions/NavigationTransition+UIKit.swift index 1105999e..4b962559 100644 --- a/Sources/NavigationTransitions/NavigationTransition+UIKit.swift +++ b/Sources/NavigationTransitions/NavigationTransition+UIKit.swift @@ -53,7 +53,7 @@ extension UISplitViewController { extension UISplitViewController { var compactViewController: UIViewController? { - if #available(iOS 14, *) { + if #available(iOS 14, tvOS 14, *) { return viewController(for: .compact) } else { if isCollapsed { @@ -65,7 +65,7 @@ extension UISplitViewController { } var primaryViewController: UIViewController? { - if #available(iOS 14, *) { + if #available(iOS 14, tvOS 14, *) { return viewController(for: .primary) } else { if !isCollapsed { @@ -77,7 +77,7 @@ extension UISplitViewController { } var supplementaryViewController: UIViewController? { - if #available(iOS 14, *) { + if #available(iOS 14, tvOS 14, *) { return viewController(for: .supplementary) } else { if !isCollapsed { @@ -93,7 +93,7 @@ extension UISplitViewController { } var secondaryViewController: UIViewController? { - if #available(iOS 14, *) { + if #available(iOS 14, tvOS 14, *) { return viewController(for: .secondary) } else { if !isCollapsed { @@ -142,6 +142,13 @@ extension UINavigationController { defaultDelegate = delegate } + if transition.type == Default.self { + delegate = defaultDelegate + } else { + customDelegate = NavigationTransitionDelegate(transition: transition, baseDelegate: defaultDelegate) + } + + #if !os(tvOS) if defaultPanRecognizer == nil { defaultPanRecognizer = UIPanGestureRecognizer() defaultPanRecognizer.targets = defaultEdgePanRecognizer?.targets // https://stackoverflow.com/a/60526328/1922543 @@ -165,8 +172,6 @@ extension UINavigationController { } if transition.type == Default.self { - delegate = defaultDelegate - switch interactivity { case .disabled: exclusivelyEnableGestureRecognizer(.none) @@ -176,8 +181,6 @@ extension UINavigationController { exclusivelyEnableGestureRecognizer(defaultPanRecognizer) } } else { - customDelegate = NavigationTransitionDelegate(transition: transition, baseDelegate: defaultDelegate) - switch interactivity { case .disabled: exclusivelyEnableGestureRecognizer(.none) @@ -187,8 +190,10 @@ extension UINavigationController { exclusivelyEnableGestureRecognizer(panRecognizer) } } + #endif } + @available(tvOS, unavailable) private func exclusivelyEnableGestureRecognizer(_ gestureRecognizer: UIPanGestureRecognizer?) { for recognizer in [defaultEdgePanRecognizer!, defaultPanRecognizer!, edgePanRecognizer!, panRecognizer!] { if let gestureRecognizer = gestureRecognizer, recognizer === gestureRecognizer { @@ -200,6 +205,7 @@ extension UINavigationController { } } +@available(tvOS, unavailable) extension UINavigationController { var defaultEdgePanRecognizer: UIScreenEdgePanGestureRecognizer! { interactivePopGestureRecognizer as? UIScreenEdgePanGestureRecognizer @@ -225,6 +231,7 @@ extension UINavigationController { } } +@available(tvOS, unavailable) extension UIGestureRecognizer { private static var strongDelegateKey = "strongDelegateKey" @@ -252,6 +259,7 @@ extension UIGestureRecognizer { } } +@available(tvOS, unavailable) final class NavigationGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate { private unowned let navigationController: UINavigationController