Skip to content

Deep linking#

In order to make sharing work, you need to setup your app to be able to handle Storyteller deep links.

iOS apps support two types of deep links, each serving different purposes:

  • When to use: When it's unknown whether the user has your app installed (e.g., sharing on social media, email links)
  • Format: https://[tenant_name].shar.estori.es/...
  • Behavior: iOS will open your app if installed, otherwise opens the web browser
  • Setup: Requires Associated Domains configuration
  • When to use: When you're certain the user has your app installed (e.g., push notifications, in-app navigation)
  • Format: [tenant_name]stories://...
  • Behavior: Directly opens your app; shows an error if not installed
  • Setup: Requires custom URL scheme registration

Important: Push notifications on iOS do not support Universal Links for directly opening apps. You must use URL Scheme Links in push notification payloads to ensure your app opens correctly.

Add Associated Domain to Your Project Settings in Xcode#

At first you need to add associated domain to your projects.

1. Go to your project settings in Xcode -> Signing & Capabilities

Xcode project settings

2. Press +Capability

3. Choose Associated Domains

Adding capability

4. Add the following domains:

  • applinks:[tenant_name].ope.nstori.es
  • applinks:[tenant_name].shar.estori.es

Adding associated domain

Add Bundle Identifier to Storyteller CMS#

After setting up an associated domain you need to add a bundle identifier to Storyteller CMS.

1. Log into Storyteller CMS

2. Go to Apps

Apps in Storyteller CMS

3. Create a new iOS app or edit existing one

Create or edit iOS app

4. Fill out App ID

App ID has the form <Application Identifier Prefix>.<Bundle Identifier> e.g. ABCDE12345.com.example.app

Fill out App ID with Bundle Identifier

5. Press Save

Register a Custom URL Scheme for your app#

Our SDK supports deeplinking through custom URL schemes. Custom URL schemes allow your application to be launched in a specific context from a custom URL. This is essential for push notifications, as iOS does not support Universal Links from push notifications.

In order to use the custom URL scheme supported by our SDK, you need to register it with the following format: [TENANT_NAME]stories://, E.g. gosportsstories://.

You can follow the next steps to do so:

1. Go to Info tab in your Xcode project settings

Xcode Info Tab

2. Expand URL Types section and add a new URL Type entry. For the Identifier field, you should use a unique identifier, like your app's bundle identifier for example. In URL Schemes, enter [TENANT_NAME]stories, replacing [TENANT_NAME] with your respective Storyteller tenant name. The Role field is only used for macOS applications, and can be ignored on iOS and other platforms.

Xcode URL Types

After following these steps, your app should be able to directly launch our SDK in a specific context from a URL with a custom scheme.

Push Notifications: URL scheme links are essential for opening your app from push notifications. See the Handling URL Scheme Links from Push Notifications section below for implementation details.

This feature can be used for example, to directly open a story or a clip with a deeplink url. To directly open a story, the SDK will handle deeplinks with the following format:

  • [TENANT_ID]stories://open/[STORYID]/[PAGEID]

Or to open a clip:

  • [TENANT_ID]stories://open/clip/[CLIPID]?collectionId=[COLLECTIONID]

As a last step you need to add code handling deep linking inside your AppDelegate's or your UISceneDelegate's method:

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

StorytellerSDK provides two methods that can be helpful:

Storyteller.shared.isStorytellerDeepLink(url: URL) -> Bool

This method takes in a URL and returns true if the URL is Storyteller deep link.

Storyteller.shared.openDeepLink(url: URL) async throws

This method opens the Story/Clip that was specified in the URL.

Examples#

AppDelegate:

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL else { return false }

    if Storyteller.shared.isStorytellerDeepLink(url: url) {
        Task {
            do {
                try await Storyteller.shared.openDeepLink(url: url)
            } catch {
                // handle error
            }
        }
        return true
    }

    // handle app's deeplinks
}

UISceneDelegate:

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

    Task {
        do {
            try await Storyteller.openDeepLink(url: url)
        } catch {
            // handle error
        }
    }
}

After all these steps you should be able to share a story and open the deep link inside your app.

When using push notifications to deep link into Storyteller content, you must use URL scheme links (not Universal Links) in your notification payload. Here's how to implement this:

Push Notification Payload#

Include a custom URL scheme link in your push notification payload:

{
    "aps": {
        "alert": {
            "title": "Check out this story!",
            "body": "Tap to view the latest content"
        }
    },
    "deeplink_url": "[tenant_name]stories://open/STORY_ID/PAGE_ID"
}

For URL scheme links to work from push notifications, you need to implement additional handling:

SwiftUI Applications:

import SwiftUI
import StorytellerSDK

@main
struct YourApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { url in
                    Task {
                        do {
                            try await Storyteller.shared.openDeepLink(url: url)
                        } catch {
                            // Handle error
                        }
                    }
                }
        }
    }
}

UIKit Applications:

In addition to the application(_:continue:restorationHandler:) method shown above, implement:

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    if Storyteller.shared.isStorytellerDeepLink(url: url) {
        Task {
            do {
                try await Storyteller.shared.openDeepLink(url: url)
            } catch {
                // Handle error
            }
        }
        return true
    }

    // Handle other custom URL schemes
    return false
}

In your UNUserNotificationCenterDelegate:

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
    let userInfo = response.notification.request.content.userInfo

    guard
        let deepLink = userInfo["deeplink_url"] as? String,
        let url = URL(string: deepLink)
    else {
        return
    }

    // This will trigger onOpenURL in SwiftUI or application(_:open:options:) in UIKit
    await UIApplication.shared.open(url)
}

The Storyteller.shared.openDeepLink function intelligently parses the provided URL (which can be either an HTTPS link via Associated Domains or a custom scheme link) to determine the type of content to open.

Story Category#

  • Identifies links containing /open/category/ or /go/category/ in the path.
  • Extracts the category identifier following /category/.
  • Calls the internal equivalent of Storyteller.shared.openCategory with the extracted category ID.
  • Example HTTPS: https://[tenantname].shar.estori.es/go/category/123456
  • Example Custom Scheme: [tenantname]stories://open/category/123456

Clip Collection#

  • Identifies links containing /open/clip, /go/clip, /open/clips, or /go/clips in the path.
  • Requires a collectionId query parameter.
  • Optionally accepts a categoryId query parameter to specify an initial category.
  • Optionally accepts a clipId path segment to attempt opening a specific clip within the collection.
  • Calls the internal equivalent of Storyteller.shared.openCollection using the extracted information.
  • Example HTTPS: https://[tenantname].shar.estori.es/open/clip/CLIP_UUID?collectionId=COLLECTION_ID&categoryId=CATEGORY_ID
  • Example Custom Scheme: [tenantname]stories://open/clip/CLIP_UUID?collectionId=COLLECTION_ID&categoryId=CATEGORY_ID

Story / Page#

  • Identifies links matching patterns like /story/STORY_ID or /page/PAGE_ID (for HTTPS) or open/STORY_ID/PAGE_ID (for custom scheme).
  • Extracts the storyId and/or pageId from the path segments.
  • Calls the internal equivalent of Storyteller.shared.openStory(storyId:) or Storyteller.shared.openPage(storyId:pageId:).
  • Example HTTPS (Story): https://[tenantname].shar.estori.es/story/STORY_UUID
  • Example HTTPS (Page): https://[tenantname].shar.estori.es/page/PAGE_UUID
  • Example Custom Scheme: [tenantname]stories://open/STORY_UUID/PAGE_UUID

Sheet#

  • Identifies links containing /open/sheet/ or /go/sheet/ in the path.
  • Extracts the sheetId from the path segment following /sheet/.
  • Calls the internal equivalent of Storyteller.shared.openSheet(sheetId:).
  • Example HTTPS: https://[tenantname].ope.nstori.es/open/sheet/SHEET_ID
  • Example Custom Scheme: [tenantname]stories://open/sheet/SHEET_ID

While Storyteller.shared.openDeepLink provides convenience, you might require more control over your app's state or navigation when a deep link is handled. In such cases, it's recommended to parse the URL yourself (after checking it with Storyteller.shared.isStorytellerDeepLink) and then use the specific Storyteller methods like openStory(), openPage(), openCollection(), openCategory(), or openSheet() to present the content. This approach allows for custom transitions, loading states, or error handling specific to your application flow. Refer to the Open Player documentation for details on these methods.

API Reference#

func isStorytellerDeepLink(url: URL) -> Bool

Checks if the given url is Storyteller deep link.

func openDeepLink(url: URL) async throws

This call makes Storyteller open the provided deep link (showing the requested Page / Story / Clip).

Parameters:

  1. url - deep link url.

Throws if there is an issue with opening the Deeplink (e.g. the requested content is not available).