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.
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:
- Select your app target
- Go to Signing & Capabilities
- Add Associated Domains capability
- 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
:
{
"applinks": {
"details": [
{
"appIDs": ["TEAMID.bundleId"],
"paths": ["*"]
}
]
}
}
Replace:
TEAMID
with your Apple Developer Team IDbundleId
with your app's bundle identifier
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:
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:
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
:
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
:
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 Pattern | Handle Extraction |
---|---|
https://domain.com/story-handle | pathComponents[0] |
https://domain.com/story/story-handle | pathComponents[1] |
https://domain.com/stories/story-handle | pathComponents[1] |
https://domain.com/content/story-handle | pathComponents[1] |
Testing Deep Links
Using Simulator
Test your deep link implementation in the iOS Simulator:
- Open Safari in the simulator
- Navigate to your deep link URL
- Tap the link to trigger your app
Using Device
On a physical device:
- Send yourself the deep link via Messages or Mail
- Tap the link to test the integration
- Or use Safari and navigate to the URL directly
Using Xcode
You can also test using Xcode's URL scheme testing:
- In Xcode, go to Product → Scheme → Edit Scheme
- Select Run from the left panel
- Go to Arguments tab
- Add your deep link URL under Arguments Passed On Launch
Best Practices
- Use Direct Launch for Shared Content:
openStoryByHandle
is more reliable for shared links as it doesn't depend on widget loading - Implement Fallback Logic: If a story isn't found in a widget, fall back to direct launch
- Handle Multiple URL Patterns: Make your URL parsing robust to handle different backend configurations
- Validate URLs: Check if the URL belongs to your domain before processing
- User Experience: Show loading states while processing deep links
- Error Handling: Gracefully handle invalid or expired story handles
Common Issues
Deep Link Not Triggering
- 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
- Use Apple's validator: https://search.developer.apple.com/appsearch-validation-tool/
- Check that your
apple-app-site-association
file is accessible without redirects - Ensure the file doesn't have a
.json
extension