Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions MacMagazine/MacMagazine/MainApp/MainViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ class MainViewModel {
}
}

// MARK: - Deep Link

extension MainViewModel {
func openDeepLink(_ url: URL, source: String = "widget") {
deepLinkPostURL = url.absoluteString
analytics.track(.buttonTap(
buttonId: AnalyticsConstants.ButtonID.deepLinkOpened(source).id,
screen: AnalyticsConstants.Screen.deepLinkDetail.name
))
}
}

// MARK: - Onboarding

extension MainViewModel {
Expand Down
24 changes: 24 additions & 0 deletions MacMagazine/MacMagazine/MainApp/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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))
Expand All @@ -24,6 +26,28 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
if let shortcutItem = connectionOptions.shortcutItem {
ShortcutManager.shared.pendingShortcut = shortcutItem
}
openDeepLink(from: connectionOptions.urlContexts)
openUniversalLink(from: connectionOptions.userActivities)
}

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
openDeepLink(from: URLContexts)
}

func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
openUniversalLink(from: [userActivity])
}

private func openDeepLink(from contexts: Set<UIOpenURLContext>) {
guard let url = contexts.first?.url else { return }
viewModel?.openDeepLink(url)
}

private func openUniversalLink(from activities: Set<NSUserActivity>) {
guard let url = activities
.first(where: { $0.activityType == NSUserActivityTypeBrowsingWeb })?
.webpageURL else { return }
viewModel?.openDeepLink(url, source: "universal_link")
}

func windowScene(_ windowScene: UIWindowScene,
Expand Down
5 changes: 5 additions & 0 deletions MacMagazine/MacMagazine/Resources/MacMagazine.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
<string>Development</string>
<key>com.apple.developer.aps-environment</key>
<string>Development</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:macmagazine.com.br</string>
<string>applinks:www.macmagazine.com.br</string>
</array>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.brit.macmagazine.cloudkit</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
<string>Production</string>
<key>com.apple.developer.aps-environment</key>
<string>Production</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:macmagazine.com.br</string>
<string>applinks:www.macmagazine.com.br</string>
</array>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.brit.macmagazine.cloudkit</string>
Expand Down
39 changes: 39 additions & 0 deletions Support/AssociatedDomains/README.md
Original file line number Diff line number Diff line change
@@ -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/<slug>/
```
15 changes: 15 additions & 0 deletions Support/AssociatedDomains/apple-app-site-association
Original file line number Diff line number Diff line change
@@ -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."
}
]
}
]
}
}
Loading