Skip to main content

Deep Linking

Deep linking allows users to open specific StorifyMe stories directly from shared links, providing a seamless experience when content is shared across platforms.

Before you begin

Make sure you have configured your sharing domain in the StorifyMe Dashboard Settings. Set either Custom Domain OR Proxy - this domain will be used when links are copied/shared from your stories.

iOS Configuration

1. Add Associated Domain

In Xcode, add your proxy domain as an associated domain:

  1. Select your app target
  2. Go to Signing & Capabilities
  3. Add Associated Domains capability
  4. Add: applinks:YOUR_PROXY_DOMAIN

Replace YOUR_PROXY_DOMAIN with the domain you configured in the StorifyMe Dashboard.

2. Backend Configuration

Create an apple-app-site-association file in your backend at https://YOUR_PROXY_DOMAIN/.well-known/apple-app-site-association:

apple-app-site-association
{
"applinks": {
"details": [
{
"appIDs": ["TEAMID.bundleId"],
"paths": ["*"]
}
]
}
}

Replace:

  • TEAMID with your Apple Developer Team ID
  • bundleId with your app's bundle identifier
Finding your Team ID

You can find your Team ID in your Apple Developer account or in Xcode under Signing & Capabilities.

Implementation

Method 1: Direct Story Launch (Recommended)

For shared story links, use openStoryByHandle to open stories directly:

SceneDelegate.swift
import StorifyMe

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else {
return
}

handleDeepLink(url: url)
}

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else { return }
handleDeepLink(url: url)
}

private func handleDeepLink(url: URL) {
guard url.scheme == "https",
let handle = extractStoryHandle(from: url) else {
return
}

// Launch story directly
StorifyMeInstance.shared.openStoryByHandle(handle: handle)
}

private func extractStoryHandle(from url: URL) -> String? {
let pathComponents = url.pathComponents.filter { $0 != "/" }

switch pathComponents.count {
case 1:
// For URLs like: https://domain.com/story-handle
return pathComponents[0]

case 2 where pathComponents[0] == "story":
// For URLs like: https://domain.com/story/story-handle
return pathComponents[1]

case 2 where pathComponents[0] == "stories":
// For URLs like: https://domain.com/stories/story-handle
return pathComponents[1]

default:
return nil
}
}
}

Method 2: Widget-Based Launch

If you need to open a story within a loaded widget context:

ViewController.swift
import StorifyMe

class ViewController: UIViewController {
@IBOutlet weak var storifyMeWidget: StorifyMeWidget!
private var pendingHandle: String?

override func viewDidLoad() {
super.viewDidLoad()
setupStorifyMeWidget()
}

private func setupStorifyMeWidget() {
storifyMeWidget.eventHandler = self
storifyMeWidget.setWidgetId(widgetId: YOUR_WIDGET_ID)
storifyMeWidget.load()
}

func handleDeepLink(handle: String) {
if storifyMeWidget.isLoaded {
// Widget is already loaded, open immediately
openStoryInWidget(handle: handle)
} else {
// Store handle to open after widget loads
pendingHandle = handle
}
}

private func openStoryInWidget(handle: String) {
storifyMeWidget.openWidgetStoryByHandle(handle) { result in
switch result {
case .success:
// Story opened successfully
break
case .storyNotFound:
// Handle not found in this widget
// Fall back to direct launch
StorifyMeInstance.shared.openStoryByHandle(handle: handle)
case .widgetNotLoaded:
// Widget not ready yet
break
}
}
}
}

extension ViewController: StorifyMeStoryEventProtocol {
func onLoad(widgetId: Int, storyList: [StorifyMeStory]) {
// Widget loaded, check if we have a pending handle
if let handle = pendingHandle {
openStoryInWidget(handle: handle)
pendingHandle = nil
}
}

func onFail(widgetId: Int, error: String) {
// If widget fails to load but we have a handle, try direct launch
if let handle = pendingHandle {
StorifyMeInstance.shared.openStoryByHandle(handle: handle)
pendingHandle = nil
}
}
}

UIKit Integration

For UIKit-based apps, handle deep links in your AppDelegate:

AppDelegate.swift
import StorifyMe

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {

guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else {
return false
}

return handleDeepLink(url: url)
}

private func handleDeepLink(url: URL) -> Bool {
guard let handle = extractStoryHandle(from: url) else {
return false
}

// Launch story directly
StorifyMeInstance.shared.openStoryByHandle(handle: handle)
return true
}

private func extractStoryHandle(from url: URL) -> String? {
let pathComponents = url.pathComponents.filter { $0 != "/" }

switch pathComponents.count {
case 1:
return pathComponents[0]
case 2 where pathComponents[0] == "story":
return pathComponents[1]
case 2 where pathComponents[0] == "stories":
return pathComponents[1]
default:
return nil
}
}
}

SwiftUI Integration

For SwiftUI apps, handle deep links using onOpenURL:

ContentView.swift
import SwiftUI
import StorifyMe

struct ContentView: View {
var body: some View {
// Your main content
VStack {
// StorifyMe widget or other content
}
.onOpenURL { url in
handleDeepLink(url: url)
}
}

private func handleDeepLink(url: URL) {
guard let handle = extractStoryHandle(from: url) else {
return
}

StorifyMeInstance.shared.openStoryByHandle(handle: handle)
}

private func extractStoryHandle(from url: URL) -> String? {
let pathComponents = url.pathComponents.filter { $0 != "/" }

switch pathComponents.count {
case 1:
return pathComponents[0]
case 2 where pathComponents[0] == "story":
return pathComponents[1]
case 2 where pathComponents[0] == "stories":
return pathComponents[1]
default:
return nil
}
}
}

URL Pattern Examples

Your implementation should handle various URL patterns depending on your backend setup:

URL PatternHandle Extraction
https://domain.com/story-handlepathComponents[0]
https://domain.com/story/story-handlepathComponents[1]
https://domain.com/stories/story-handlepathComponents[1]
https://domain.com/content/story-handlepathComponents[1]

Using Simulator

Test your deep link implementation in the iOS Simulator:

  1. Open Safari in the simulator
  2. Navigate to your deep link URL
  3. Tap the link to trigger your app

Using Device

On a physical device:

  1. Send yourself the deep link via Messages or Mail
  2. Tap the link to test the integration
  3. Or use Safari and navigate to the URL directly

Using Xcode

You can also test using Xcode's URL scheme testing:

  1. In Xcode, go to ProductSchemeEdit Scheme
  2. Select Run from the left panel
  3. Go to Arguments tab
  4. Add your deep link URL under Arguments Passed On Launch

Best Practices

  1. Use Direct Launch for Shared Content: openStoryByHandle is more reliable for shared links as it doesn't depend on widget loading
  2. Implement Fallback Logic: If a story isn't found in a widget, fall back to direct launch
  3. Handle Multiple URL Patterns: Make your URL parsing robust to handle different backend configurations
  4. Validate URLs: Check if the URL belongs to your domain before processing
  5. User Experience: Show loading states while processing deep links
  6. Error Handling: Gracefully handle invalid or expired story handles

Common Issues

  • Verify your apple-app-site-association file is accessible and properly formatted
  • Check that your Team ID and bundle identifier are correct
  • Ensure the domain in Associated Domains matches your proxy domain exactly
  • Make sure the file is served with Content-Type: application/json

Story Not Opening

  • Verify the story handle exists and is published
  • Check if the story is available in the widget you're trying to open it from
  • Use direct launch (openStoryByHandle) as a fallback

URL Parsing Issues

  • Test with different URL patterns your backend might generate
  • Add logging to debug which path components contain the handle
  • Handle edge cases like URLs with query parameters or fragments

Associated Domain Verification