Skip to content

Commit ebf6ce9

Browse files
committed
Add widget extension methods for click tracking
Add extension method `onesignalWidgetURL` to View and DynamicIsland that is modeled after the corresponding `widgetURL` methods in WidgetKit.
1 parent 67d7d9d commit ebf6ce9

File tree

3 files changed

+160
-0
lines changed

3 files changed

+160
-0
lines changed

iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@
7676
3C2D8A5928B4C4E300BE41F6 /* OSDelta.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2D8A5828B4C4E300BE41F6 /* OSDelta.swift */; };
7777
3C2DB2F12DE6CB5E0006B905 /* OneSignalBadgeHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C2DB2EF2DE6CB5E0006B905 /* OneSignalBadgeHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; };
7878
3C2DB2F22DE6CB5E0006B905 /* OneSignalBadgeHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C2DB2F02DE6CB5E0006B905 /* OneSignalBadgeHelpers.m */; };
79+
3C3D34E92E95EAA5006A2924 /* LiveActivityConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3D34E82E95EAA5006A2924 /* LiveActivityConstants.swift */; };
80+
3C3D8D782E92DB7500C3E977 /* OSLiveActivityViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3D8D772E92DB7500C3E977 /* OSLiveActivityViewExtensions.swift */; };
7981
3C44673E296D099D0039A49E /* OneSignalMobileProvision.m in Sources */ = {isa = PBXBuildFile; fileRef = 912411FD1E73342200E41FD7 /* OneSignalMobileProvision.m */; };
8082
3C44673F296D09CC0039A49E /* OneSignalMobileProvision.h in Headers */ = {isa = PBXBuildFile; fileRef = 912411FC1E73342200E41FD7 /* OneSignalMobileProvision.h */; settings = {ATTRIBUTES = (Public, ); }; };
8183
3C448B9D2936ADFD002F96BC /* OSBackgroundTaskHandlerImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C448B9B2936ADFD002F96BC /* OSBackgroundTaskHandlerImpl.h */; };
@@ -1261,6 +1263,8 @@
12611263
3C2D8A5828B4C4E300BE41F6 /* OSDelta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSDelta.swift; sourceTree = "<group>"; };
12621264
3C2DB2EF2DE6CB5E0006B905 /* OneSignalBadgeHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OneSignalBadgeHelpers.h; sourceTree = "<group>"; };
12631265
3C2DB2F02DE6CB5E0006B905 /* OneSignalBadgeHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OneSignalBadgeHelpers.m; sourceTree = "<group>"; };
1266+
3C3D34E82E95EAA5006A2924 /* LiveActivityConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityConstants.swift; sourceTree = "<group>"; };
1267+
3C3D8D772E92DB7500C3E977 /* OSLiveActivityViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLiveActivityViewExtensions.swift; sourceTree = "<group>"; };
12641268
3C448B9B2936ADFD002F96BC /* OSBackgroundTaskHandlerImpl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OSBackgroundTaskHandlerImpl.h; sourceTree = "<group>"; };
12651269
3C448B9C2936ADFD002F96BC /* OSBackgroundTaskHandlerImpl.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OSBackgroundTaskHandlerImpl.m; sourceTree = "<group>"; };
12661270
3C448BA12936B474002F96BC /* OSBackgroundTaskManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSBackgroundTaskManager.swift; sourceTree = "<group>"; };
@@ -2272,8 +2276,10 @@
22722276
3CFA8F482E9087DB00201FE5 /* Requests */,
22732277
3CFA8F4B2E9087DB00201FE5 /* OneSignalLiveActivitiesManagerImpl.swift */,
22742278
3CFA8F4C2E9087DB00201FE5 /* OneSignalLiveActivityAttributes.swift */,
2279+
3C3D8D772E92DB7500C3E977 /* OSLiveActivityViewExtensions.swift */,
22752280
3CFA8F4D2E9087DB00201FE5 /* OSLiveActivitiesExtension.swift */,
22762281
3CFA8F492E9087DB00201FE5 /* AnyCodable.swift */,
2282+
3C3D34E82E95EAA5006A2924 /* LiveActivityConstants.swift */,
22772283
3CFA8F4A2E9087DB00201FE5 /* DefaultLiveActivityAttributes.swift */,
22782284
);
22792285
path = Source;
@@ -4307,6 +4313,8 @@
43074313
buildActionMask = 2147483647;
43084314
files = (
43094315
3CFA8F4F2E9087DB00201FE5 /* AnyCodable.swift in Sources */,
4316+
3C3D8D782E92DB7500C3E977 /* OSLiveActivityViewExtensions.swift in Sources */,
4317+
3C3D34E92E95EAA5006A2924 /* LiveActivityConstants.swift in Sources */,
43104318
3CFA8F502E9087DB00201FE5 /* OSLiveActivitiesExecutor.swift in Sources */,
43114319
3CFA8F512E9087DB00201FE5 /* DefaultLiveActivityAttributes.swift in Sources */,
43124320
3CFA8F522E9087DB00201FE5 /* OSRequestSetStartToken.swift in Sources */,
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
Modified MIT License
3+
4+
Copyright 2025 OneSignal
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
1. The above copyright notice and this permission notice shall be included in
14+
all copies or substantial portions of the Software.
15+
16+
2. All copies of substantial portions of the Software may only be used in connection
17+
with services provided by OneSignal.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
THE SOFTWARE.
26+
*/
27+
28+
/// Constants used throughout the OneSignalLiveActivities module
29+
enum LiveActivityConstants {
30+
/// URL components for OneSignal click tracking
31+
enum Tracking {
32+
static let scheme = "onesignal-liveactivity"
33+
static let host = "track"
34+
static let clickPath = "/click"
35+
static let clickId = "clickId"
36+
static let activityId = "activityId"
37+
static let activityType = "activityType"
38+
static let redirect = "redirect"
39+
}
40+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
Modified MIT License
3+
4+
Copyright 2025 OneSignal
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
1. The above copyright notice and this permission notice shall be included in
14+
all copies or substantial portions of the Software.
15+
16+
2. All copies of substantial portions of the Software may only be used in connection
17+
with services provided by OneSignal.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
THE SOFTWARE.
26+
*/
27+
28+
import WidgetKit
29+
import ActivityKit
30+
import SwiftUI
31+
32+
@available(iOS 16.1, *)
33+
extension DynamicIsland {
34+
/// Sets the URL that opens the corresponding app of a Live Activity when a user taps on the Live Activity.
35+
/// Sets OneSignal activity metadata. See Important callout below on usage.
36+
///
37+
/// By setting the URL with this function, it becomes the default URL for deep linking into the app
38+
/// for each view of the Live Activity. However, if you include a
39+
/// <doc://com.apple.documentation/documentation/swiftui/link> in the Live Activity,
40+
/// the link takes priority over the default URL. When a person taps on the `Link`, it takes them to the
41+
/// place in the app that corresponds to the URL of the `Link`.
42+
///
43+
/// - Parameters:
44+
/// - url: The URL that opens the app.
45+
/// - context: The activity view context.
46+
///
47+
/// - Returns: The configuration object for the Dynamic Island with the specified URL.
48+
///
49+
/// > Important: Use instead of`.widgetURL`. Requires handling from your app's URL handling code
50+
/// (e.g., `application(_:open:options:)` in AppDelegate or `onOpenURL` in SwiftUI) using the
51+
/// `OneSignal.LiveActivities.trackClickAndReturnOriginal(url)` method.
52+
public func onesignalWidgetURL<T: OneSignalLiveActivityAttributes>(
53+
_ url: URL?,
54+
context: ActivityViewContext<T>
55+
) -> DynamicIsland {
56+
return self.widgetURL(generateTrackingDeepLink(originalURL: url, context: context))
57+
}
58+
}
59+
60+
@available(iOS 16.1, *)
61+
extension View {
62+
/// Sets the URL to open in the containing app when the user clicks the widget.
63+
/// Sets OneSignal activity metadata. See Important callout below on usage.
64+
///
65+
/// - Parameters:
66+
/// - url: The URL to open in the containing app.
67+
/// - context: The activity view context.
68+
/// - Returns: A view that opens the specified URL when the user clicks
69+
/// the widget.
70+
///
71+
/// Widgets support one `onesignalWidgetURL` modifier in their view hierarchy.
72+
/// If multiple views have `onesignalWidgetURL` modifiers, the behavior is undefined.
73+
///
74+
/// > Important: Use instead of`.widgetURL`. Requires handling from your app's URL handling code
75+
/// (e.g., `application(_:open:options:)` in AppDelegate or `onOpenURL` in SwiftUI) using the
76+
/// `OneSignal.LiveActivities.trackClickAndReturnOriginal(url)` method.
77+
@MainActor @preconcurrency public func onesignalWidgetURL<T: OneSignalLiveActivityAttributes>(_ url: URL?, context: ActivityViewContext<T>) -> some View {
78+
return self.widgetURL(generateTrackingDeepLink(originalURL: url, context: context))
79+
}
80+
}
81+
82+
// MARK: - Helper Function
83+
84+
@available(iOS 16.1, *)
85+
private func generateTrackingDeepLink<T: OneSignalLiveActivityAttributes>(originalURL: URL?, context: ActivityViewContext<T>) -> URL? {
86+
// Generate a unique click ID
87+
let clickId = UUID().uuidString
88+
89+
// Get activity metadata
90+
let activityId = context.attributes.onesignal.activityId
91+
let activityType = String(describing: T.self)
92+
93+
// Build OneSignal tracking URL
94+
var components = URLComponents()
95+
components.scheme = LiveActivityConstants.Tracking.scheme
96+
components.host = LiveActivityConstants.Tracking.host
97+
components.path = LiveActivityConstants.Tracking.clickPath
98+
99+
var queryItems = [
100+
URLQueryItem(name: LiveActivityConstants.Tracking.clickId, value: clickId),
101+
URLQueryItem(name: LiveActivityConstants.Tracking.activityId, value: activityId),
102+
URLQueryItem(name: LiveActivityConstants.Tracking.activityType, value: activityType)
103+
]
104+
105+
if let originalURL = originalURL {
106+
queryItems.append(URLQueryItem(name: LiveActivityConstants.Tracking.redirect, value: originalURL.absoluteString))
107+
}
108+
109+
components.queryItems = queryItems
110+
111+
return components.url
112+
}

0 commit comments

Comments
 (0)