callstack / react-native-visionos

A framework for building native visionOS applications using React
https://callstack.github.io/react-native-visionos-docs/
MIT License
977 stars 28 forks source link

fix: deep and universal links #139

Closed thiagobrez closed 5 months ago

thiagobrez commented 5 months ago

Summary:

Fixes #138

Deep and Universal Links currently do not work on RN visionOS. Since iOS 13, if the project has opted into Scenes, lifecycle events are now controlled by SceneDelegate instead of AppDelegate.

After this PR is merged, we also need docs showing how to implement them, since the React Native docs only apply to non-Scene apps.

This PR is intended to be worked on. Currently, this solution works for Deep and Universal links when the app is already running or in background. When launching the app (previously killed) via Deep or Universal Links, Linking.getInitialUrl() will always return null, due to launchOptions being initialized as an empty object in RCTReactViewController.

The Apple Documentation indicates scene(_ :continue:) for handling Universal Links when app is already running, but that only applies to UIKit-based apps. For SwiftUI, the onOpenURL(_:perform:) view modifier must be used.

Final usage example for apps:

SceneDelegate.swift

class SceneDelegate: NSObject, UIWindowSceneDelegate {
  ...

  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // Other logic already present here
    ...

    // Deep Links and Universal Links - called when the app was previously NOT running
    RCTLinkingManager.scene(scene, willConnectTo: session, options: connectionOptions)
  }

  func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
      // Deep Links - called when the app was previously running or suspended
      RCTLinkingManager.scene(scene, openURLContexts: URLContexts)
  }
}

App.swift

struct visionaryApp: App {
  @UIApplicationDelegateAdaptor var delegate: AppDelegate

  var body: some Scene {
    WindowGroup {
      TabView {
        RCTRootViewRepresentable(moduleName: "myApp")
          .tabItem {
            Label("Home", systemImage: "house.fill")
          }
        RCTRootViewRepresentable(moduleName: "SettingsScreen")
          .tabItem {
            Label("Settings", systemImage: "gear")
          }
      }
      // Add the onOpenURL view modifier
      // Calling AppDelegate directly here but this should be improved to call SceneDelegate, probably
      .onOpenURL(perform: { (universalLink: URL) in
        delegate.onOpenURL(url: universalLink)
      })
    }

  }
}

AppDelegate.swift

class AppDelegate: RCTAppDelegate {
  ...

  // Universal Links - called when the app was previously running or suspended
  func onOpenURL(url: URL) {
    RCTLinkingManager.onOpen(url)
  }
}

Changelog:

[IOS] [ADDED] - Added equivalent scene method scene:willConnectTo:options: to handle Deep and Universal Links when app was previously NOT running [IOS] [ADDED] - Added equivalent scene method scene:openURLContexts: to handle Deep Links when app was previously in background or suspended [IOS] [ADDED] - Added equivalent scene method onOpenURL to handle Universal Links when app was previously in background or suspended

Test Plan:

okwasniewski commented 5 months ago

Closed in favour of #140