From 230ee9f8224ab5d8f17f2ef622d949515635886f Mon Sep 17 00:00:00 2001 From: Cassio Rossi Date: Tue, 21 Jul 2026 20:09:09 +0100 Subject: [PATCH 1/3] fix(#312): route widget deep links through SceneDelegate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Widget taps opened the app but nothing happened, on every widget size. The app uses the UIKit scene lifecycle (AppDelegate configures a SceneDelegate that hosts SceneView in a UIHostingController), so SwiftUI's .onOpenURL never fires — incoming URLs are delivered to the scene delegate instead. SceneDelegate handled only shortcut items, so the post URL was dropped. Handle URLs in SceneDelegate for both cold launch (connectionOptions .urlContexts) and warm launch (scene(_:openURLContexts:)), routing them into MainViewModel.openDeepLink, which sets deepLinkPostURL and tracks the existing widget deep-link analytics. Co-Authored-By: Claude --- MacMagazine/MacMagazine/MainApp/MainViewModel.swift | 12 ++++++++++++ MacMagazine/MacMagazine/MainApp/SceneDelegate.swift | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/MacMagazine/MacMagazine/MainApp/MainViewModel.swift b/MacMagazine/MacMagazine/MainApp/MainViewModel.swift index cac6a6b0..b630e9a5 100644 --- a/MacMagazine/MacMagazine/MainApp/MainViewModel.swift +++ b/MacMagazine/MacMagazine/MainApp/MainViewModel.swift @@ -85,6 +85,18 @@ class MainViewModel { } } +// MARK: - Deep Link + +extension MainViewModel { + func openDeepLink(_ url: URL) { + deepLinkPostURL = url.absoluteString + analytics.track(.buttonTap( + buttonId: AnalyticsConstants.ButtonID.deepLinkOpened("widget").id, + screen: AnalyticsConstants.Screen.deepLinkDetail.name + )) + } +} + // MARK: - Onboarding extension MainViewModel { diff --git a/MacMagazine/MacMagazine/MainApp/SceneDelegate.swift b/MacMagazine/MacMagazine/MainApp/SceneDelegate.swift index 3bf4a862..59de0df1 100644 --- a/MacMagazine/MacMagazine/MainApp/SceneDelegate.swift +++ b/MacMagazine/MacMagazine/MainApp/SceneDelegate.swift @@ -6,6 +6,7 @@ import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? let pushNotification = PushNotification() + private var viewModel: MainViewModel? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, @@ -16,6 +17,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { (UIApplication.shared.delegate as? AppDelegate)?.pushNotification = pushNotification let viewModel = MainViewModel(pushNotification: pushNotification) + self.viewModel = viewModel window = UIWindow(windowScene: windowScene) window?.rootViewController = UIHostingController(rootView: SceneView(viewModel: viewModel)) @@ -24,6 +26,16 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { if let shortcutItem = connectionOptions.shortcutItem { ShortcutManager.shared.pendingShortcut = shortcutItem } + openDeepLink(from: connectionOptions.urlContexts) + } + + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + openDeepLink(from: URLContexts) + } + + private func openDeepLink(from contexts: Set) { + guard let url = contexts.first?.url else { return } + viewModel?.openDeepLink(url) } func windowScene(_ windowScene: UIWindowScene, From 9343233e59ae944f3dca3fff9c920c60300acb0f Mon Sep 17 00:00:00 2001 From: Cassio Rossi Date: Tue, 21 Jul 2026 20:39:53 +0100 Subject: [PATCH 2/3] fix(#312): support Universal Links for macmagazine.com.br posts Tapping a macmagazine.com.br link in Safari or another app opened the browser instead of the app. The app declared no Associated Domains and handled no browsing-web user activity, so it could not claim its URLs. - Add com.apple.developer.associated-domains (apex + www) to the debug and release entitlements. - Handle Universal Links in SceneDelegate for cold launch (connectionOptions.userActivities) and warm launch (scene(_:continue:)), routing the webpageURL into openDeepLink. - Parameterize MainViewModel.openDeepLink with a source so universal links track distinctly from widget opens. Which URLs open the app (/post/*) is gated server-side by the AASA file; the app trusts that classification and reuses the existing DeepLinkNewsDetailView path to render the post. Co-Authored-By: Claude --- MacMagazine/MacMagazine/MainApp/MainViewModel.swift | 4 ++-- MacMagazine/MacMagazine/MainApp/SceneDelegate.swift | 12 ++++++++++++ .../MacMagazine/Resources/MacMagazine.entitlements | 5 +++++ .../Resources/MacMagazineRelease.entitlements | 5 +++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/MacMagazine/MacMagazine/MainApp/MainViewModel.swift b/MacMagazine/MacMagazine/MainApp/MainViewModel.swift index b630e9a5..d233e8b4 100644 --- a/MacMagazine/MacMagazine/MainApp/MainViewModel.swift +++ b/MacMagazine/MacMagazine/MainApp/MainViewModel.swift @@ -88,10 +88,10 @@ class MainViewModel { // MARK: - Deep Link extension MainViewModel { - func openDeepLink(_ url: URL) { + func openDeepLink(_ url: URL, source: String = "widget") { deepLinkPostURL = url.absoluteString analytics.track(.buttonTap( - buttonId: AnalyticsConstants.ButtonID.deepLinkOpened("widget").id, + buttonId: AnalyticsConstants.ButtonID.deepLinkOpened(source).id, screen: AnalyticsConstants.Screen.deepLinkDetail.name )) } diff --git a/MacMagazine/MacMagazine/MainApp/SceneDelegate.swift b/MacMagazine/MacMagazine/MainApp/SceneDelegate.swift index 59de0df1..a173827d 100644 --- a/MacMagazine/MacMagazine/MainApp/SceneDelegate.swift +++ b/MacMagazine/MacMagazine/MainApp/SceneDelegate.swift @@ -27,17 +27,29 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { ShortcutManager.shared.pendingShortcut = shortcutItem } openDeepLink(from: connectionOptions.urlContexts) + openUniversalLink(from: connectionOptions.userActivities) } func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { openDeepLink(from: URLContexts) } + func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + openUniversalLink(from: [userActivity]) + } + private func openDeepLink(from contexts: Set) { guard let url = contexts.first?.url else { return } viewModel?.openDeepLink(url) } + private func openUniversalLink(from activities: Set) { + guard let url = activities + .first(where: { $0.activityType == NSUserActivityTypeBrowsingWeb })? + .webpageURL else { return } + viewModel?.openDeepLink(url, source: "universal_link") + } + func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { diff --git a/MacMagazine/MacMagazine/Resources/MacMagazine.entitlements b/MacMagazine/MacMagazine/Resources/MacMagazine.entitlements index 916acf14..634fd191 100644 --- a/MacMagazine/MacMagazine/Resources/MacMagazine.entitlements +++ b/MacMagazine/MacMagazine/Resources/MacMagazine.entitlements @@ -6,6 +6,11 @@ Development com.apple.developer.aps-environment Development + com.apple.developer.associated-domains + + applinks:macmagazine.com.br + applinks:www.macmagazine.com.br + com.apple.developer.icloud-container-identifiers iCloud.com.brit.macmagazine.cloudkit diff --git a/MacMagazine/MacMagazine/Resources/MacMagazineRelease.entitlements b/MacMagazine/MacMagazine/Resources/MacMagazineRelease.entitlements index cdcc7da8..c059d039 100644 --- a/MacMagazine/MacMagazine/Resources/MacMagazineRelease.entitlements +++ b/MacMagazine/MacMagazine/Resources/MacMagazineRelease.entitlements @@ -6,6 +6,11 @@ Production com.apple.developer.aps-environment Production + com.apple.developer.associated-domains + + applinks:macmagazine.com.br + applinks:www.macmagazine.com.br + com.apple.developer.icloud-container-identifiers iCloud.com.brit.macmagazine.cloudkit From 422097864444cd0a70c14af5f0d6e28d869729c6 Mon Sep 17 00:00:00 2001 From: Cassio Rossi Date: Tue, 21 Jul 2026 20:46:19 +0100 Subject: [PATCH 3/3] fix(#312): add reference AASA file for Universal Links Ready-to-host apple-app-site-association (Team ID A5VW9QUF9L, appID A5VW9QUF9L.com.brit.macmagazine) scoped to /post/* only, plus a README documenting the hosting requirements for macmagazine.com.br and www. Co-Authored-By: Claude --- Support/AssociatedDomains/README.md | 39 +++++++++++++++++++ .../apple-app-site-association | 15 +++++++ 2 files changed, 54 insertions(+) create mode 100644 Support/AssociatedDomains/README.md create mode 100644 Support/AssociatedDomains/apple-app-site-association diff --git a/Support/AssociatedDomains/README.md b/Support/AssociatedDomains/README.md new file mode 100644 index 00000000..425dd6b5 --- /dev/null +++ b/Support/AssociatedDomains/README.md @@ -0,0 +1,39 @@ +# Associated Domains (Universal Links) + +Reference copy of the `apple-app-site-association` (AASA) file that must be hosted for +Universal Links to open `macmagazine.com.br` post URLs in the app instead of Safari. + +## Hosting requirements + +Serve the `apple-app-site-association` file (this exact content, **no `.json` extension**) at: + +``` +https://macmagazine.com.br/.well-known/apple-app-site-association +https://www.macmagazine.com.br/.well-known/apple-app-site-association +``` + +- HTTPS only, valid certificate. +- `Content-Type: application/json`. +- **No redirects** — must return `200` directly. +- Both apex and `www` must serve it (the app declares `applinks:` for both). + +## Scope + +`components` allows only `/post/*`, so only article URLs open the app; every other path +(homepage, categories, `/wp-admin`, etc.) stays in the browser. + +## App side (already in the repo) + +- `com.apple.developer.associated-domains` in `MacMagazine/Resources/MacMagazine.entitlements` + and `MacMagazineRelease.entitlements`. +- `SceneDelegate` handles the browsing-web `NSUserActivity` (cold + warm launch). + +Enable the **Associated Domains** capability on the `com.brit.macmagazine` App ID in the +Developer portal and regenerate provisioning profiles before device/TestFlight builds. + +## Verify after hosting + +```bash +curl -sI https://macmagazine.com.br/.well-known/apple-app-site-association +xcrun simctl openurl booted https://macmagazine.com.br/post/2025/12/03// +``` diff --git a/Support/AssociatedDomains/apple-app-site-association b/Support/AssociatedDomains/apple-app-site-association new file mode 100644 index 00000000..5b9c6633 --- /dev/null +++ b/Support/AssociatedDomains/apple-app-site-association @@ -0,0 +1,15 @@ +{ + "applinks": { + "details": [ + { + "appIDs": ["A5VW9QUF9L.com.brit.macmagazine"], + "components": [ + { + "/": "/post/*", + "comment": "Only post URLs open the app; everything else stays in the browser." + } + ] + } + ] + } +}