From 581b334bc61205d8edefbbd4ff28a5f3988b5929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20B=C3=B6hnke?= Date: Mon, 23 Jan 2023 17:59:26 +0100 Subject: [PATCH] Add native macOS destination --- Pow Example.xcodeproj/project.pbxproj | 12 ++++ Pow Example/ExampleList.swift | 65 +++++++++---------- Pow Example/Examples/AboutView.swift | 58 +++++++++++++++++ .../Change Effects/SoundEffectsExample.swift | 2 + Pow Example/Examples/Example.swift | 5 ++ .../Examples/Screens/CheckoutExample.swift | 12 ++++ .../Examples/Screens/SocialFeedExample.swift | 20 ++++-- Pow Example/GithubButton.swift | 20 +++++- Pow Example/PowExampleApp.swift | 45 ++++++++----- 9 files changed, 182 insertions(+), 57 deletions(-) create mode 100644 Pow Example/Examples/AboutView.swift diff --git a/Pow Example.xcodeproj/project.pbxproj b/Pow Example.xcodeproj/project.pbxproj index 3885b91..85a19ea 100644 --- a/Pow Example.xcodeproj/project.pbxproj +++ b/Pow Example.xcodeproj/project.pbxproj @@ -98,6 +98,7 @@ 546A29D4292A6B1200A80DE2 /* SprayExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 546A29D3292A6B1200A80DE2 /* SprayExample.swift */; }; 549BF5F5293642BC00B2DCCF /* SocialFeedExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 549BF5F4293642BC00B2DCCF /* SocialFeedExample.swift */; }; 54A962302934CC4400BBD5FE /* GithubButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54A9622F2934CC4400BBD5FE /* GithubButton.swift */; }; + 54CFF812297EEE220033F113 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CFF811297EEE220033F113 /* AboutView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -192,6 +193,7 @@ 546A29D3292A6B1200A80DE2 /* SprayExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SprayExample.swift; sourceTree = ""; }; 549BF5F4293642BC00B2DCCF /* SocialFeedExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialFeedExample.swift; sourceTree = ""; }; 54A9622F2934CC4400BBD5FE /* GithubButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubButton.swift; sourceTree = ""; }; + 54CFF811297EEE220033F113 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; 54D37CAD292F9C4A00788E8A /* Pow-Example-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Pow-Example-Info.plist"; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ @@ -316,6 +318,7 @@ 546A29C3292A4B9000A80DE2 /* Transitions */, 546A29C4292A4B9F00A80DE2 /* Change Effects */, 549BF5F6293642BE00B2DCCF /* Screens */, + 54CFF811297EEE220033F113 /* AboutView.swift */, ); path = Examples; sourceTree = ""; @@ -502,6 +505,7 @@ files = ( 546A298C292A31BB00A80DE2 /* ExampleList.swift in Sources */, 546A29CE292A591F00A80DE2 /* ShineExample.swift in Sources */, + 54CFF812297EEE220033F113 /* AboutView.swift in Sources */, 546A29C0292A420F00A80DE2 /* SwooshExample.swift in Sources */, 546A29BA292A40D800A80DE2 /* PoofExample.swift in Sources */, 546A29B2292A3ED800A80DE2 /* GlareExample.swift in Sources */, @@ -677,9 +681,13 @@ "$(inherited)", "@executable_path/Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 13; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "io.movingparts.Pow-Example"; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -710,9 +718,13 @@ "$(inherited)", "@executable_path/Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 13; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "io.movingparts.Pow-Example"; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/Pow Example/ExampleList.swift b/Pow Example/ExampleList.swift index 31e56f6..7b768ab 100644 --- a/Pow Example/ExampleList.swift +++ b/Pow Example/ExampleList.swift @@ -1,45 +1,20 @@ import Pow -import MessageUI import SwiftUI struct ExampleList: View { var body: some View { List { + #if os(iOS) Section { - VStack(alignment: .leading, spacing: 12) { - Text("This is the official example app for Pow, the Surprise and Delight framework for SwiftUI.") - - Text("Tap the individual examples to see the effects and transitions in action.") - - Text("**Note:** While this app requires iOS 16, Pow itself supports iOS 15 and above.") - } - .font(.subheadline.leading(.loose)) - .foregroundColor(.primary) - - Link(destination: URL(string: "https://movingparts.io/pow")!) { - ViewThatFits { - Label("Pow Website and Licensing Options", systemImage: "safari") - Label("Pow Website & Licensing Options", systemImage: "safari") - Label("Pow Website & Licensing", systemImage: "safari") - Label("Pow Website", systemImage: "safari") - } - } - - Link(destination: URL(string: "https://github.com/movingparts-io/Pow-Examples")!) { - ViewThatFits { - Label("GitHub Repository for this App", systemImage: "terminal") - Label("GitHub Repo for this App", systemImage: "terminal") - Label("Repo for this App", systemImage: "terminal") - } - } - - if MFMailComposeViewController.canSendMail() { - Link(destination: URL(string: "mailto:hello@movingparts.io")!) { - Label("Support", systemImage: "envelope") - } - } + AboutView() + } + #else + NavigationLink("About Pow") { + DecoratedAboutView() } + #endif + #if os(iOS) Section { SocialFeedExample.navigationLink CheckoutExample.navigationLink @@ -48,6 +23,7 @@ struct ExampleList: View { } footer: { Text("Pre-composed screens that show how to use Pow in context. Use them as inspiration for your app.") } + #endif Section { JumpExample.navigationLink @@ -55,7 +31,9 @@ struct ExampleList: View { RiseExample.navigationLink ShakeExample.navigationLink ShineExample.navigationLink + #if os(iOS) SoundEffectExample.navigationLink + #endif SpinExample.navigationLink SprayExample.navigationLink } header: { @@ -126,7 +104,11 @@ struct InfoButton: View { @Environment(\.presentInfoAction) var presentInfoAction + @State + var isPopoverPresented = false + var body: some View { + #if os(iOS) if let presentInfoAction { Button { presentInfoAction(type) @@ -134,6 +116,23 @@ struct InfoButton: View { Label("About", systemImage: "info.circle") } } + #else + Button { + isPopoverPresented.toggle() + } label: { + Label("About", systemImage: "info.circle") + } + .popover(isPresented: $isPopoverPresented, arrowEdge: .bottom) { + ScrollView { + VStack { + type.erasedDescription + } + .padding() + } + .frame(maxWidth: 500) + } + .help("About") + #endif } } diff --git a/Pow Example/Examples/AboutView.swift b/Pow Example/Examples/AboutView.swift new file mode 100644 index 0000000..0f1a794 --- /dev/null +++ b/Pow Example/Examples/AboutView.swift @@ -0,0 +1,58 @@ +import SwiftUI + +#if os(iOS) +import MessageUI +#endif + +struct AboutView: View { + var body: some View { + VStack(alignment: .leading, spacing: 12) { + Text("This is the official example app for Pow, the Surprise and Delight framework for SwiftUI.") + + Text("Tap the individual examples to see the effects and transitions in action.") + + Text("**Note:** While this app requires iOS 16, Pow itself supports iOS 15 and above.") + } + #if os(iOS) + .font(.subheadline.leading(.loose)) + #endif + .foregroundColor(.primary) + + Link(destination: URL(string: "https://movingparts.io/pow")!) { + ViewThatFits { + Label("Pow Website and Licensing Options", systemImage: "safari") + Label("Pow Website & Licensing Options", systemImage: "safari") + Label("Pow Website & Licensing", systemImage: "safari") + Label("Pow Website", systemImage: "safari") + } + } + + Link(destination: URL(string: "https://github.com/movingparts-io/Pow-Examples")!) { + ViewThatFits { + Label("GitHub Repository for this App", systemImage: "terminal") + Label("GitHub Repo for this App", systemImage: "terminal") + Label("Repo for this App", systemImage: "terminal") + } + } + + #if os(iOS) + if MFMailComposeViewController.canSendMail() { + Link(destination: URL(string: "mailto:hello@movingparts.io")!) { + Label("Support", systemImage: "envelope") + } + } + #endif + } +} + +struct DecoratedAboutView: View { + var body: some View { + GroupBox { + VStack(alignment: .leading) { + AboutView() + .fixedSize() + } + .padding() + } + } +} diff --git a/Pow Example/Examples/Change Effects/SoundEffectsExample.swift b/Pow Example/Examples/Change Effects/SoundEffectsExample.swift index d55f250..d87f34e 100644 --- a/Pow Example/Examples/Change Effects/SoundEffectsExample.swift +++ b/Pow Example/Examples/Change Effects/SoundEffectsExample.swift @@ -1,3 +1,4 @@ +#if os(iOS) import Pow import SwiftUI @@ -180,3 +181,4 @@ struct SoundEffectExample_Previews: PreviewProvider { SoundEffectExample() } } +#endif diff --git a/Pow Example/Examples/Example.swift b/Pow Example/Examples/Example.swift index 02fdb59..f865664 100644 --- a/Pow Example/Examples/Example.swift +++ b/Pow Example/Examples/Example.swift @@ -127,6 +127,7 @@ struct IconView: View { var colorScheme var body: some View { + #if os(iOS) ZStack { Rectangle() .fill(.primary) @@ -147,6 +148,10 @@ struct IconView: View { .strokeBorder(.white.opacity(0.1), lineWidth: 0.5) .blendMode(.plusLighter) } + #else + content + .symbolRenderingMode(.monochrome) + #endif } } diff --git a/Pow Example/Examples/Screens/CheckoutExample.swift b/Pow Example/Examples/Screens/CheckoutExample.swift index 278b695..afb4e23 100644 --- a/Pow Example/Examples/Screens/CheckoutExample.swift +++ b/Pow Example/Examples/Screens/CheckoutExample.swift @@ -59,19 +59,25 @@ struct CheckoutExample: View, Example { } .listStyle(.plain) .navigationTitle("") + #if os(iOS) .navigationBarTitleDisplayMode(.inline) + #endif .animation(.default, value: quantity != 0) .toolbar { if quantity == 0 { + #if os(iOS) ToolbarItem(placement: .navigationBarTrailing) { Button("Undo") { quantity = 1 } } + #endif } } + #if os(iOS) .changeEffect(.feedback(SoundEffect("whop")), value: quantity == 0, isEnabled: quantity == 0) .changeEffect(.feedback(SoundEffect("wip")), value: quantity != 0, isEnabled: quantity != 0) + #endif .safeAreaInset(edge: .bottom, spacing: 0) { VStack(spacing: 16) { VStack(alignment: .leading, spacing: 0) { @@ -128,8 +134,10 @@ private struct CartItem: View { .alignmentGuide(.listRowSeparatorLeading) { dimensions in dimensions[.leading] } + #if os(iOS) .changeEffect(.feedback(SoundEffect("beep")), value: quantity, isEnabled: quantity > lastQuantity && quantity > 1) .changeEffect(.feedback(SoundEffect("boop")), value: quantity, isEnabled: quantity < lastQuantity && quantity > 0) + #endif .onChange(of: quantity) { newValue in lastQuantity = quantity } @@ -222,9 +230,11 @@ struct PayButton: View { .tint(status == .failed ? .red : status == .succeeded ? .green : nil) .allowsHitTesting(status == .initial) .changeEffect(.shake(rate: .fast), value: status == .failed, isEnabled: status == .failed) + #if os(iOS) .changeEffect(.feedback(SoundEffect("plop")), value: status == .inProgress, isEnabled: status == .inProgress) .changeEffect(.feedback(SoundEffect("sparkle")), value: status == .succeeded, isEnabled: status == .succeeded) .changeEffect(.feedback(SoundEffect("notfound")), value: status == .failed, isEnabled: status == .failed) + #endif } } @@ -269,7 +279,9 @@ struct CheckoutExample_Previews: PreviewProvider { static var previews: some View { NavigationStack { CheckoutExample() + #if os(iOS) .toolbar(.visible, for: .navigationBar) + #endif } } } diff --git a/Pow Example/Examples/Screens/SocialFeedExample.swift b/Pow Example/Examples/Screens/SocialFeedExample.swift index 14fea23..fc11909 100644 --- a/Pow Example/Examples/Screens/SocialFeedExample.swift +++ b/Pow Example/Examples/Screens/SocialFeedExample.swift @@ -73,13 +73,21 @@ struct SocialFeedExample: View, Example { .safeAreaInset(edge: .bottom, spacing: 0) { tabBar } + #if os(iOS) .navigationBarTitleDisplayMode(.inline) + #endif } var buttonBar: some View { HStack(alignment: .firstTextBaseline, spacing: 12) { + #if os(iOS) let pop = SoundEffect("pop1", "pop2", "pop3", "pop4", "pop5") + let sparkle = SoundEffect(isBoosted ? "sparkle.rising" : "sparkle.falling") + + let pick = SoundEffect(isBookmarked ? "pick.rising" : "pick.falling") + #endif + Button { isLiked.toggle() } label: { @@ -99,9 +107,9 @@ struct SocialFeedExample: View, Example { } } .tint(isLiked ? .red : .gray) + #if os(iOS) .changeEffect(.feedback(pop), value: isLiked, isEnabled: isLiked) - - let sparkle = SoundEffect(isBoosted ? "sparkle.rising" : "sparkle.falling") + #endif Button { isBoosted.toggle() @@ -125,7 +133,9 @@ struct SocialFeedExample: View, Example { } } .tint(isBoosted ? .green : .gray) + #if os(iOS) .changeEffect(.feedback(sparkle), value: isBoosted) + #endif Button { clapCount += 1 @@ -150,11 +160,11 @@ struct SocialFeedExample: View, Example { ) } } + #if os(iOS) .changeEffect(.feedback(pop), value: clapCount) + #endif .tint(clapCount > 202 ? .blue : .gray) - let pick = SoundEffect(isBookmarked ? "pick.rising" : "pick.falling") - Button { isBookmarked.toggle() } label: { @@ -169,7 +179,9 @@ struct SocialFeedExample: View, Example { } .tint(isBookmarked ? .orange : .gray) .animation(.spring(response: 0.4, dampingFraction: 1), value: isBookmarked) + #if os(iOS) .changeEffect(.feedback(pick), value: isBookmarked) + #endif } } diff --git a/Pow Example/GithubButton.swift b/Pow Example/GithubButton.swift index 7e46c00..b24a7da 100644 --- a/Pow Example/GithubButton.swift +++ b/Pow Example/GithubButton.swift @@ -5,6 +5,9 @@ struct GithubButton: View { let baseURL = URL(string: "https://github.com/movingparts-io/Pow-Examples/blob/main/")! + @Environment(\.openURL) + var openURL + init(_ localPath: LocalPath) { self.localPath = localPath } @@ -17,12 +20,23 @@ struct GithubButton: View { let url = baseURL.appendingPathComponent(relative) - Link(destination: url) { - ViewThatFits { + Group { + #if os(iOS) + Link(destination: url) { + ViewThatFits { + Label("Show Example on GitHub", systemImage: "terminal") + Label("Show on GitHub", systemImage: "terminal") + } + } + #else + Button { + openURL(url) + } label: { Label("Show Example on GitHub", systemImage: "terminal") - Label("Show on GitHub", systemImage: "terminal") } + #endif } + .help("Show Example on GitHub") } } } diff --git a/Pow Example/PowExampleApp.swift b/Pow Example/PowExampleApp.swift index 5ccf099..c831628 100644 --- a/Pow Example/PowExampleApp.swift +++ b/Pow Example/PowExampleApp.swift @@ -13,28 +13,39 @@ struct PowExampleApp: App { var body: some Scene { WindowGroup { - NavigationStack { - ExampleList() - } - .environment(\.presentInfoAction, PresentInfoAction { - presentedType = Presentation(type: $0) - }) - .sheet(item: $presentedType) { t in - ScrollView { - VStack(alignment: .leading, spacing: 12) { - Text(t.type.title).font(.title.bold()) + Group { + #if os(iOS) + NavigationView { + ExampleList() + } + .sheet(item: $presentedType) { t in + ScrollView { + VStack(alignment: .leading, spacing: 12) { + Text(t.type.title).font(.title.bold()) - GithubButton(t.type.localPath) - .controlSize(.small) - .buttonStyle(.bordered) + GithubButton(t.type.localPath) + .controlSize(.small) + .buttonStyle(.bordered) - t.type.erasedDescription + t.type.erasedDescription + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding() } - .frame(maxWidth: .infinity, alignment: .leading) - .padding() + .presentationDetents([.medium]) } - .presentationDetents([.medium]) + #else + NavigationSplitView { + ExampleList() + .frame(minWidth: 250) + } detail: { + DecoratedAboutView() + } + #endif } + .environment(\.presentInfoAction, PresentInfoAction { + presentedType = Presentation(type: $0) + }) } } }