diff --git a/examples/demo/App.xcodeproj/project.pbxproj b/examples/demo/App.xcodeproj/project.pbxproj index 7de7b5a01..ae17cd23e 100644 --- a/examples/demo/App.xcodeproj/project.pbxproj +++ b/examples/demo/App.xcodeproj/project.pbxproj @@ -364,7 +364,7 @@ E194A3F19072CB17A8F1A12E /* SmsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmsSection.swift; sourceTree = ""; }; ECAC7EF0B67920F9FEC4F129 /* TagsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsSection.swift; sourceTree = ""; }; F46DFACB9F304B9374F3C570 /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; - "TEMP_ABAB8D6F-E57E-4D74-98B6-E29157EB8D71" /* Build.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Build.xcconfig; sourceTree = ""; }; + "TEMP_DEC528CA-1E47-4B0F-AAAF-071417BC60C5" /* Build.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Build.xcconfig; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -582,10 +582,10 @@ path = Support; sourceTree = ""; }; - "TEMP_50803CDB-250B-41C8-93E2-9DF8EB2C4802" /* demo */ = { + "TEMP_17E43425-D1F7-4A88-B0A0-C0368CB0E171" /* demo */ = { isa = PBXGroup; children = ( - "TEMP_ABAB8D6F-E57E-4D74-98B6-E29157EB8D71" /* Build.xcconfig */, + "TEMP_DEC528CA-1E47-4B0F-AAAF-071417BC60C5" /* Build.xcconfig */, ); path = demo; sourceTree = ""; @@ -956,7 +956,7 @@ /* Begin XCBuildConfiguration section */ 0D2EF3911CA89837C30DB0D1 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = "TEMP_ABAB8D6F-E57E-4D74-98B6-E29157EB8D71" /* Build.xcconfig */; + baseConfigurationReference = "TEMP_DEC528CA-1E47-4B0F-AAAF-071417BC60C5" /* Build.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; @@ -974,7 +974,7 @@ }; 4A0C935808978B5A7673E412 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = "TEMP_ABAB8D6F-E57E-4D74-98B6-E29157EB8D71" /* Build.xcconfig */; + baseConfigurationReference = "TEMP_DEC528CA-1E47-4B0F-AAAF-071417BC60C5" /* Build.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = App.entitlements; @@ -1029,6 +1029,7 @@ COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_TEAM = ""; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1047,6 +1048,7 @@ MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; + SUPPORTS_MACCATALYST = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.9; @@ -1055,7 +1057,7 @@ }; D0E56A85F1C385808720F94B /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = "TEMP_ABAB8D6F-E57E-4D74-98B6-E29157EB8D71" /* Build.xcconfig */; + baseConfigurationReference = "TEMP_DEC528CA-1E47-4B0F-AAAF-071417BC60C5" /* Build.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = OneSignalNotificationServiceExtension/OneSignalNotificationServiceExtension.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -1077,7 +1079,7 @@ }; EB1CC3A930E09FEBECF9195D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = "TEMP_ABAB8D6F-E57E-4D74-98B6-E29157EB8D71" /* Build.xcconfig */; + baseConfigurationReference = "TEMP_DEC528CA-1E47-4B0F-AAAF-071417BC60C5" /* Build.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = App.entitlements; @@ -1101,7 +1103,7 @@ }; F305A3E63851EE49DA2D190E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = "TEMP_ABAB8D6F-E57E-4D74-98B6-E29157EB8D71" /* Build.xcconfig */; + baseConfigurationReference = "TEMP_DEC528CA-1E47-4B0F-AAAF-071417BC60C5" /* Build.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; @@ -1124,7 +1126,7 @@ }; F5FD25168D9B32A08A468069 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = "TEMP_ABAB8D6F-E57E-4D74-98B6-E29157EB8D71" /* Build.xcconfig */; + baseConfigurationReference = "TEMP_DEC528CA-1E47-4B0F-AAAF-071417BC60C5" /* Build.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = OneSignalNotificationServiceExtension/OneSignalNotificationServiceExtension.entitlements; DEVELOPMENT_TEAM = 99SW8E36CT; @@ -1176,6 +1178,7 @@ COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; + DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -1201,6 +1204,7 @@ ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; + SUPPORTS_MACCATALYST = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.9; diff --git a/examples/demo/App/App.swift b/examples/demo/App/App.swift index 58de08ea3..db1d37689 100644 --- a/examples/demo/App/App.swift +++ b/examples/demo/App/App.swift @@ -27,7 +27,6 @@ import SwiftUI import OneSignalFramework -import OneSignalLiveActivities @main struct App: SwiftUI.App { @@ -62,9 +61,11 @@ class AppDelegate: NSObject, UIApplicationDelegate { setupInAppMessageListeners() // Set up Live Activities (iOS 16.1+) + #if !targetEnvironment(macCatalyst) if #available(iOS 16.1, *) { LiveActivityController.setup() } + #endif return true } diff --git a/examples/demo/App/Info.plist b/examples/demo/App/Info.plist index 21a2421ab..5138a7d3f 100644 --- a/examples/demo/App/Info.plist +++ b/examples/demo/App/Info.plist @@ -26,6 +26,8 @@ This app uses your location to personalize notifications and content. NSLocationAlwaysAndWhenInUseUsageDescription This app uses your location to personalize notifications and content even when the app is in the background. + OneSignal_app_groups_key + group.com.onesignal.example.onesignal NSSupportsLiveActivities NSSupportsLiveActivitiesFrequentUpdates diff --git a/examples/demo/App/Services/LiveActivityController.swift b/examples/demo/App/Services/LiveActivityController.swift index 8d00a542d..5d9f3c045 100644 --- a/examples/demo/App/Services/LiveActivityController.swift +++ b/examples/demo/App/Services/LiveActivityController.swift @@ -27,6 +27,9 @@ import Foundation import OneSignalFramework +#if targetEnvironment(macCatalyst) +// Live Activities are unavailable on Mac Catalyst. +#else import OneSignalLiveActivities /// Order tracking phases used by the Live Activity demo @@ -151,3 +154,4 @@ enum LiveActivityController { } } } +#endif diff --git a/examples/demo/App/ViewModels/OneSignalViewModel.swift b/examples/demo/App/ViewModels/OneSignalViewModel.swift index dfa23553a..e158be52d 100644 --- a/examples/demo/App/ViewModels/OneSignalViewModel.swift +++ b/examples/demo/App/ViewModels/OneSignalViewModel.swift @@ -389,6 +389,7 @@ final class OneSignalViewModel: ObservableObject { // MARK: - Live Activities + #if !targetEnvironment(macCatalyst) func startLiveActivity(activityId: String, orderNumber: String, status: LiveActivityStatus) { let trimmedId = activityId.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmedId.isEmpty else { return } @@ -423,6 +424,7 @@ final class OneSignalViewModel: ObservableObject { ) } } + #endif // MARK: - Tooltips diff --git a/examples/demo/App/Views/Components/ToggleRow.swift b/examples/demo/App/Views/Components/ToggleRow.swift index 59d1ceeba..3454247c0 100644 --- a/examples/demo/App/Views/Components/ToggleRow.swift +++ b/examples/demo/App/Views/Components/ToggleRow.swift @@ -66,6 +66,7 @@ struct ToggleRow: View { Toggle("", isOn: isOn) .labelsHidden() .tint(OS.Color.primary) + .background(OS.Color.grey500.opacity(0.35), in: Capsule()) .disabled(isDisabled) .accessibilityIdentifier(accessibilityID) } diff --git a/examples/demo/App/Views/ContentView.swift b/examples/demo/App/Views/ContentView.swift index 9e6578488..fbaf70fd8 100644 --- a/examples/demo/App/Views/ContentView.swift +++ b/examples/demo/App/Views/ContentView.swift @@ -51,7 +51,9 @@ struct ContentView: View { TriggersSection() CustomEventsSection() LocationSection() + #if !targetEnvironment(macCatalyst) LiveActivitySection() + #endif ActionButton( "NEXT SCREEN", accessibilityID: "next_screen_button" diff --git a/examples/demo/App/Views/Sections/LiveActivitySection.swift b/examples/demo/App/Views/Sections/LiveActivitySection.swift index 136c6564d..9086da6a2 100644 --- a/examples/demo/App/Views/Sections/LiveActivitySection.swift +++ b/examples/demo/App/Views/Sections/LiveActivitySection.swift @@ -27,6 +27,9 @@ import SwiftUI +#if targetEnvironment(macCatalyst) +// Live Activities are unavailable on Mac Catalyst. +#else /// Live Activities (iOS 16.1+) section with activity ID + order # inputs and status cycler. /// Mirrors the Capacitor demo's LiveActivitySection. struct LiveActivitySection: View { @@ -142,3 +145,4 @@ struct LiveActivitySection: View { } } } +#endif diff --git a/examples/demo/OneSignalNotificationServiceExtension/Info.plist b/examples/demo/OneSignalNotificationServiceExtension/Info.plist index ca3cd8f43..19587da71 100644 --- a/examples/demo/OneSignalNotificationServiceExtension/Info.plist +++ b/examples/demo/OneSignalNotificationServiceExtension/Info.plist @@ -20,6 +20,8 @@ 1.0 CFBundleVersion 1 + OneSignal_app_groups_key + group.com.onesignal.example.onesignal NSExtension NSExtensionPointIdentifier diff --git a/examples/demo/OneSignalWidget/OneSignalWidgetBundle.swift b/examples/demo/OneSignalWidget/OneSignalWidgetBundle.swift index 063a36e0c..4db76db04 100644 --- a/examples/demo/OneSignalWidget/OneSignalWidgetBundle.swift +++ b/examples/demo/OneSignalWidget/OneSignalWidgetBundle.swift @@ -31,8 +31,12 @@ import SwiftUI @main struct OneSignalWidgetBundle: WidgetBundle { var body: some Widget { + #if targetEnvironment(macCatalyst) + OneSignalWidgetPlaceholder() + #else if #available(iOS 16.2, *) { OneSignalWidgetLiveActivity() } + #endif } } diff --git a/examples/demo/OneSignalWidget/OneSignalWidgetLiveActivity.swift b/examples/demo/OneSignalWidget/OneSignalWidgetLiveActivity.swift index cf4a029d3..181af0ea4 100644 --- a/examples/demo/OneSignalWidget/OneSignalWidgetLiveActivity.swift +++ b/examples/demo/OneSignalWidget/OneSignalWidgetLiveActivity.swift @@ -25,9 +25,48 @@ * THE SOFTWARE. */ -import ActivityKit import WidgetKit import SwiftUI + +#if targetEnvironment(macCatalyst) +private struct CatalystPlaceholderEntry: TimelineEntry { + let date: Date +} + +private struct CatalystPlaceholderProvider: TimelineProvider { + func placeholder(in context: Context) -> CatalystPlaceholderEntry { + CatalystPlaceholderEntry(date: Date()) + } + + func getSnapshot( + in context: Context, + completion: @escaping (CatalystPlaceholderEntry) -> Void + ) { + completion(CatalystPlaceholderEntry(date: Date())) + } + + func getTimeline( + in context: Context, + completion: @escaping (Timeline) -> Void + ) { + completion(Timeline(entries: [CatalystPlaceholderEntry(date: Date())], policy: .never)) + } +} + +struct OneSignalWidgetPlaceholder: Widget { + var body: some WidgetConfiguration { + StaticConfiguration( + kind: "com.onesignal.example.catalyst-placeholder", + provider: CatalystPlaceholderProvider() + ) { _ in + Text("OneSignal") + } + .configurationDisplayName("OneSignal") + .description("Live Activities are unavailable on Mac Catalyst.") + } +} +#else +import ActivityKit import OneSignalLiveActivities /// Live Activity widget that renders the order tracking flow used by the demo. @@ -173,3 +212,4 @@ struct DeliveryProgressBar: View { .frame(height: 6) } } +#endif diff --git a/examples/demo/README.md b/examples/demo/README.md index 42e5d5513..292202b5a 100644 --- a/examples/demo/README.md +++ b/examples/demo/README.md @@ -92,7 +92,7 @@ xcodegen generate # rewrites App.xcodeproj ### 3. Capabilities & App Group -The shipped `App.entitlements` and `OneSignalNotificationServiceExtension/OneSignalNotificationServiceExtension.entitlements` use `group.com.onesignal.example.onesignal`. If you need a different group (for example to install on a real device under your own team), change the value in both files to the same string. The other capabilities (Push Notifications, Remote notifications background mode, `NSSupportsLiveActivities`) are already declared in the entitlements / `App/Info.plist`. +The shipped `App.entitlements` and `OneSignalNotificationServiceExtension/OneSignalNotificationServiceExtension.entitlements` use `group.com.onesignal.example.onesignal`. Both targets also set `OneSignal_app_groups_key` to this value because Catalyst derives a `maccatalyst.*` bundle identifier that would otherwise produce a different default group name. If you need a different group (for example to install on a real device under your own team), change the value in both entitlements and both Info.plists to the same string. The other capabilities (Push Notifications, Remote notifications background mode, `NSSupportsLiveActivities`) are already declared in the entitlements / `App/Info.plist`. ### 4. Configure your OneSignal credentials diff --git a/examples/demo/project.yml b/examples/demo/project.yml index 16736ca48..22f5b3842 100644 --- a/examples/demo/project.yml +++ b/examples/demo/project.yml @@ -18,6 +18,8 @@ settings: CODE_SIGN_STYLE: Automatic DEVELOPMENT_TEAM: "" GENERATE_INFOPLIST_FILE: NO + SUPPORTS_MACCATALYST: YES + DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER: YES projectReferences: OneSignalSDK: path: ../../iOS_SDK/OneSignalSDK/OneSignal.xcodeproj diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSResilientStorage.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSResilientStorage.swift index 21f3b07ae..312cd8d65 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSResilientStorage.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSResilientStorage.swift @@ -33,6 +33,7 @@ import OneSignalCore /// /// Stored in the App Group container, so it's shared across any targets (main app, NSE, etc.) /// configured with the same App Group entitlement. Opaque identifiers only: no PII or credentials. +/// Accessors are no-ops on Mac Catalyst, where iOS data protection and prewarming do not apply. @objc(OSResilientStorage) public final class OSResilientStorage: NSObject { @@ -50,6 +51,14 @@ public final class OSResilientStorage: NSObject { // MARK: - Internal + private static var isSupported: Bool { + #if targetEnvironment(macCatalyst) + return false + #else + return true + #endif + } + private static let fileName = "onesignal_identity.json" /// Serial queue used to serialize all file reads/writes. @@ -59,15 +68,15 @@ public final class OSResilientStorage: NSObject { /// the NSE can read the same file. Falls back to the app's private /// Application Support directory when no App Group is entitled. private static func fileURL() -> URL? { - let fm = FileManager.default + let fileManager = FileManager.default let groupName = OneSignalUserDefaults.appGroupName() - if let container = fm.containerURL(forSecurityApplicationGroupIdentifier: groupName) { + if let container = fileManager.containerURL(forSecurityApplicationGroupIdentifier: groupName) { return container.appendingPathComponent(fileName) } do { - let support = try fm.url( + let support = try fileManager.url( for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, @@ -121,6 +130,7 @@ public final class OSResilientStorage: NSObject { /// Returns the full current contents of the cache. Empty dict if absent. @objc public static func snapshot() -> [String: String] { + guard isSupported else { return [:] } return queue.sync { loadUnsafe() } } @@ -133,6 +143,7 @@ public final class OSResilientStorage: NSObject { /// Atomically updates a single value. Passing nil or an empty string removes the key. @objc public static func setString(_ value: String?, forKey key: String) { + guard isSupported else { return } queue.async { var current = loadUnsafe() if let value = value, !value.isEmpty { @@ -147,7 +158,7 @@ public final class OSResilientStorage: NSObject { /// Atomically updates multiple values, preserving keys not in `values`. /// An empty-string value removes the corresponding key. @objc public static func setStrings(_ values: [String: String]) { - guard !values.isEmpty else { return } + guard isSupported, !values.isEmpty else { return } queue.async { var current = loadUnsafe() for (key, value) in values { diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSStubLiveActivities.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSStubLiveActivities.swift index 761875502..6360b564f 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSStubLiveActivities.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSStubLiveActivities.swift @@ -16,23 +16,29 @@ public class OSStubLiveActivities: NSObject, OSLiveActivities { } public static func enter(_ activityId: String, withToken: String) { - OneSignalLog.onesignalLog(.LL_ERROR, message: "OneSignalLiveActivities not found. In order to use OneSignal's LiveActivities features the OneSignalLiveActivities module must be added.") + logUnavailable() } public static func enter(_ activityId: String, withToken: String, withSuccess: OSResultSuccessBlock?, withFailure: OSFailureBlock?) { - OneSignalLog.onesignalLog(.LL_ERROR, message: "OneSignalLiveActivities not found. In order to use OneSignal's LiveActivities features the OneSignalLiveActivities module must be added.") + logUnavailable() } public static func exit(_ activityId: String) { - OneSignalLog.onesignalLog(.LL_ERROR, message: "OneSignalLiveActivities not found. In order to use OneSignal's LiveActivities features the OneSignalLiveActivities module must be added.") + logUnavailable() } public static func exit(_ activityId: String, withSuccess: OSResultSuccessBlock?, withFailure: OSFailureBlock?) { - OneSignalLog.onesignalLog(.LL_ERROR, message: "OneSignalLiveActivities not found. In order to use OneSignal's LiveActivities features the OneSignalLiveActivities module must be added.") + logUnavailable() } public static func trackClickAndReturnOriginal(_ url: URL) -> URL? { - OneSignalLog.onesignalLog(.LL_ERROR, message: "OneSignalLiveActivities not found. In order to use OneSignal's LiveActivities features the OneSignalLiveActivities module must be added.") + logUnavailable() return url } + + private static func logUnavailable() { + let message = "OneSignalLiveActivities not found. In order to use OneSignal's " + + "LiveActivities features the OneSignalLiveActivities module must be added." + OneSignalLog.onesignalLog(.LL_ERROR, message: message) + } } diff --git a/iOS_SDK/OneSignalSDK/Source/OneSignal.m b/iOS_SDK/OneSignalSDK/Source/OneSignal.m index 0b3313bf8..0a1bd38ec 100755 --- a/iOS_SDK/OneSignalSDK/Source/OneSignal.m +++ b/iOS_SDK/OneSignalSDK/Source/OneSignal.m @@ -26,6 +26,7 @@ */ #import +#import #import "OneSignalFramework.h" #import #import "OneSignalInternal.h" @@ -206,6 +207,9 @@ + (void)logout { } + (Class)LiveActivities { +#if TARGET_OS_MACCATALYST + return [OSStubLiveActivities liveActivities]; +#else let oneSignalLiveActivities = NSClassFromString(ONE_SIGNAL_LIVE_ACTIVITIES_CLASS_NAME); if (oneSignalLiveActivities != nil && [oneSignalLiveActivities respondsToSelector:@selector(liveActivities)]) { return [oneSignalLiveActivities performSelector:@selector(liveActivities)]; @@ -213,6 +217,7 @@ + (void)logout { [OneSignalLog onesignalLog:ONE_S_LL_ERROR message:@"oneSignalLiveActivities not found. In order to use OneSignal's LiveActivities features the OneSignalLiveActivities module must be added."]; return [OSStubLiveActivities liveActivities]; } +#endif } + (Class)Location { @@ -440,12 +445,16 @@ + (void)startUserManager { } + (void)startLiveActivitiesManager { +#if TARGET_OS_MACCATALYST + return; +#else let oneSignalLiveActivities = NSClassFromString(ONE_SIGNAL_LIVE_ACTIVITIES_CLASS_NAME); if (oneSignalLiveActivities != nil && [oneSignalLiveActivities respondsToSelector:@selector(start)]) { [oneSignalLiveActivities performSelector:@selector(start)]; } else { [OneSignalLog onesignalLog:ONE_S_LL_ERROR message:@"oneSignalLiveActivities not found. In order to use OneSignal's LiveActivities features the OneSignalLiveActivities module must be added."]; } +#endif } + (void)delayInitializationForPrivacyConsent {