Глубокая ссылка не обеспечивает переход к ожидаемому местоположению, если приложение удалено из памяти в быстрой iOS

Я новичок в ReactNative. В моем текущем приложении диплинкинг был закодирован и работал хорошо, если это было сделано через appDelegate. позже приложение также стало поддерживать Carplay, поэтому для обработки нескольких сцен был введен SceneDelegate.

После этого при каждом вызове диплинкинга

  • если приложение находится в фоновом режиме, оно запускает приложение в определенном месте
  • если приложение удалено из памяти и нажата глубокая ссылка, оно только запускает приложение, но не переходит к нужному экрану.

В чем здесь проблема, какие вещи мне нужно изменить/добавить, чтобы приложение запускалось в нужном месте, даже если приложение удалено из памяти.

Вот мой appDelegate.

func appDelegate() -> AppDelegate {
    return UIApplication.shared.delegate as! AppDelegate
}

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  var window: UIWindow?
  var appCenter: AppCenterReactNative!
  var appCenterAnaltics: AppCenterReactNativeAnalytics!
  var appCenterCrashes: AppCenterReactNativeCrashes!
  let mParticleKey: String = ReactNativeConfig.env(for: "MPARTICLE_IOS_KEY");
  let mParticleSecret: String = ReactNativeConfig.env(for: "MPARTICLE_IOS_SECRET");
  // var mParticleEmail: String = ReactNativeConfig.env(for: "MPARTICLE_EMAIL");
  let mParticleEnv: String = ReactNativeConfig.env(for: "MPARTICLE_ENV");
  let mParticleDataPlanName: String = ReactNativeConfig.env(for: "MPARTICLE_DATAPLAN");
  let mParticleDataPlanVersion: String = ReactNativeConfig.env(for: "MPARTICLE_DATAPLAN_VERSION");
  let moEngageAppID: String = ReactNativeConfig.env(for: "MOENGAGE_APP_ID");
  
  /* CarPlay setup */
  var playableContentManager: MPPlayableContentManager?
  var remoteCommandCenter: MPRemoteCommandCenter?
  let carplayPlaylist = CarPlayPlaylist()
  let carplayArtworkCache = NSCache<AnyObject, UIImage>()
  
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    initializeFlipper(with: application)

    /* Moengage */
    let sdkConfig = MoEngageSDKConfig(appId: moEngageAppID, dataCenter: .data_center_01);
    MoEngageInitializer.sharedInstance().initializeDefaultSDKConfig(sdkConfig, andLaunchOptions: launchOptions ?? [:])
    
    AppCenterReactNative.register()
    AppCenterReactNativeAnalytics.register(withInitiallyEnabled: true);
    AppCenterReactNativeCrashes.registerWithAutomaticProcessing();

    FirebaseApp.configure()
        
    /* ChromeCast activate */
    let receiverAppID:String = "CC1AD845"; // or @"ABCD1234"
    let criteria = GCKDiscoveryCriteria(applicationID: receiverAppID)
    let options = GCKCastOptions(discoveryCriteria: criteria)
    GCKCastContext.setSharedInstanceWith(options)
    
    let bridge = RCTBridge(delegate: self, launchOptions: launchOptions)!
    let rootView = RCTRootView(bridge: bridge, moduleName: "nova", initialProperties: nil)
    
    let rootViewController = UIViewController()
    rootViewController.view = rootView

    self.window = UIWindow(frame: UIScreen.main.bounds)
    self.window?.rootViewController = rootViewController
    self.window?.makeKeyAndVisible()

    /* MPNowPlayingInfoCenter */
    UIApplication.shared.beginReceivingRemoteControlEvents()

    setupCarPlay();

    RNSplashScreen.show()

    /* Setup MParticle */
    var dPlanVersion:NSNumber = 0
    if let versionInt = Int(mParticleDataPlanVersion) {
     dPlanVersion = NSNumber(value:versionInt)
    }

    var mParticleEnvMode: MPEnvironment = MPEnvironment.development
    if (mParticleEnv == "PROD") {
      mParticleEnvMode =  MPEnvironment.production
    }
    let mParticleOptions = MParticleOptions(key: mParticleKey, secret: mParticleSecret)
    mParticleOptions.environment = mParticleEnvMode
    mParticleOptions.dataPlanId = mParticleDataPlanName
    mParticleOptions.dataPlanVersion = dPlanVersion
    mParticleOptions.proxyAppDelegate = false
    if #available(iOS 14, *) {
       mParticleOptions.attStatus = NSNumber.init(value: ATTrackingManager.trackingAuthorizationStatus.rawValue)
    }
    
    // Remove AST Events
    mParticleOptions.onCreateBatch = { (batch: [AnyHashable: Any]) -> [AnyHashable: Any]? in
        var modifiedBatch = batch
        guard var modifiedMessages = batch["msgs"] as? [AnyHashable] else { return batch }
        var index = 0
        for message in modifiedMessages {
            // the following removes Application State Transition (AST) events, except for those uploaded on installs and upgrades
            // Install AST events are used by many server-side integrations and are used by
            // mParticle to ensure there is a user profile created
            guard let messageAsDictionary = message as? [AnyHashable: Any] else { continue }
            guard let type = messageAsDictionary["dt"] as? String else { continue }
            let isFirstRun = messageAsDictionary["ifr"] as? Bool ?? false
            let isUpgrade = messageAsDictionary["iu"] as? Bool ?? false
            if type == "ast" && !isFirstRun && !isUpgrade {
                modifiedMessages.remove(at: index)
                index -= 1
            }
            index += 1
        }
        modifiedBatch["msgs"] = modifiedMessages
        return modifiedBatch
    }
    // Start the SDK
    MParticle.sharedInstance().start(with: mParticleOptions)
    
    return true
  } 

  private func initializeFlipper(with application: UIApplication) {
    #if DEBUG
      let client = FlipperClient.shared()
        
      let layoutDescriptorMapper = SKDescriptorMapper(defaults: ())
      client?.add(FlipperKitLayoutPlugin(rootNode: application, with: layoutDescriptorMapper!))
      client?.add(FKUserDefaultsPlugin(suiteName: "nova"))
      client?.add(FlipperKitReactPlugin())
      client?.add(FlipperKitNetworkPlugin(networkAdapter: SKIOSNetworkAdapter()))
      client?.start()
    #endif
  }

  /* Allow for orientation change */
  func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    return Orientation.getOrientation()
  }

  /* Allow Link back URLs ('nova://') */
  public func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    return RCTLinkingManager.application(app, open: url, options: options)
  }
  
  public func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    return RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
  }
}

extension AppDelegate: RCTBridgeDelegate {
    func sourceURL(for bridge: RCTBridge!) -> URL! {
        #if DEBUG
        return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
        #else
        return CodePush.bundleURL()
        #endif
    }
}

Это моя сценаДелегат

@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate, RCTBridgeDelegate {

  let mParticleKey: String = ReactNativeConfig.env(for: "MPARTICLE_IOS_KEY");
  let mParticleSecret: String = ReactNativeConfig.env(for: "MPARTICLE_IOS_SECRET");
  // var mParticleEmail: String = ReactNativeConfig.env(for: "MPARTICLE_EMAIL");
  let mParticleEnv: String = ReactNativeConfig.env(for: "MPARTICLE_ENV");
  let mParticleDataPlanName: String = ReactNativeConfig.env(for: "MPARTICLE_DATAPLAN");
  let mParticleDataPlanVersion: String = ReactNativeConfig.env(for: "MPARTICLE_DATAPLAN_VERSION");


  func sourceURL(for bridge: RCTBridge!) -> URL! {
    let jsCodeLocation: URL
    jsCodeLocation = RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
    return jsCodeLocation
  }
  
  var window: UIWindow?
  
  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    var deeplink: URL?
    if let userActivity = connectionOptions.userActivities.first(where: { $0.activityType == NSUserActivityTypeBrowsingWeb }),
        let webpageURL = userActivity.webpageURL {
        // get universal link
        deeplink = webpageURL
    } else if let urlContext = connectionOptions.urlContexts.first {
        // get app scheme deep link
        deeplink = urlContext.url
    }
    handleDeepLink(deeplink)
    
    let bridge = RCTBridge.init(delegate: self, launchOptions: nil)
    let rootView = RCTRootView.init(bridge: bridge!, moduleName: "nova", initialProperties: nil)
      
    let rootViewController = UIViewController()
    rootViewController.view = rootView

    

    AppCenterReactNative.register()
    AppCenterReactNativeAnalytics.register(withInitiallyEnabled: true);
    AppCenterReactNativeCrashes.registerWithAutomaticProcessing();

    FirebaseApp.configure()

    /* ChromeCast activate */
    let receiverAppID:String = "CC1AD845"; // or @"ABCD1234"
    let criteria = GCKDiscoveryCriteria(applicationID: receiverAppID)
    let options = GCKCastOptions(discoveryCriteria: criteria)
    GCKCastContext.setSharedInstanceWith(options)
    
    /* MPNowPlayingInfoCenter */
    UIApplication.shared.beginReceivingRemoteControlEvents()

    RNSplashScreen.show()

    // Instantiate root view here instead of scene to start the bundler on app launch
    RNBridgeInstanceHolder.sharedInstance.bridge = bridge
    RNBridgeInstanceHolder.sharedInstance.rctRootView = rootView
    
    if let windowScene = scene as? UIWindowScene {
       let window = UIWindow(windowScene: windowScene)
      window.rootViewController = rootViewController
       self.window = window
       window.makeKeyAndVisible()
    }

    if #unavailable(iOS 14.0) {
      appDelegate().setupCarPlay()
    }

    /* Setup MParticle */
    var dPlanVersion:NSNumber = 0
    if let versionInt = Int(mParticleDataPlanVersion) {
     dPlanVersion = NSNumber(value:versionInt)
    }

    var mParticleEnvMode: MPEnvironment = MPEnvironment.development
    if (mParticleEnv == "PROD") {
      mParticleEnvMode =  MPEnvironment.production
    }
    let mParticleOptions = MParticleOptions(key: mParticleKey, secret: mParticleSecret)
    mParticleOptions.environment = mParticleEnvMode
    mParticleOptions.dataPlanId = mParticleDataPlanName
    mParticleOptions.dataPlanVersion = dPlanVersion
    mParticleOptions.proxyAppDelegate = false
    if #available(iOS 14, *) {
       mParticleOptions.attStatus = NSNumber.init(value: ATTrackingManager.trackingAuthorizationStatus.rawValue)
    }
    
    // Remove AST Events
    mParticleOptions.onCreateBatch = { (batch: [AnyHashable: Any]) -> [AnyHashable: Any]? in
        var modifiedBatch = batch
        guard var modifiedMessages = batch["msgs"] as? [AnyHashable] else { return batch }
        var index = 0
        for message in modifiedMessages {
            // the following removes Application State Transition (AST) events, except for those uploaded on installs and upgrades
            // Install AST events are used by many server-side integrations and are used by
            // mParticle to ensure there is a user profile created
            guard let messageAsDictionary = message as? [AnyHashable: Any] else { continue }
            guard let type = messageAsDictionary["dt"] as? String else { continue }
            let isFirstRun = messageAsDictionary["ifr"] as? Bool ?? false
            let isUpgrade = messageAsDictionary["iu"] as? Bool ?? false
            if type == "ast" && !isFirstRun && !isUpgrade {
                modifiedMessages.remove(at: index)
                index -= 1
            }
            index += 1
        }
        modifiedBatch["msgs"] = modifiedMessages
        return modifiedBatch
    }
    // Start the SDK
    MParticle.sharedInstance().start(with: mParticleOptions)
      
  }

  //handels app scheme novaplayer:// in active and inactive foreground mode
  func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
      if let url = URLContexts.first?.url {
        handleDeepLink(url)
      }
  }

  //handels universal links https://novaplayer in active and inactive foreground mode
  func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
    if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
    if let url = userActivity.webpageURL {               
        handleDeepLink(url)
      }
    }
  }
    
  //function to pass deelink value to react native
  func handleDeepLink(_ deeplink: URL?) {
      guard let deeplink = deeplink else {
          os_log("No deeplink found", log: OSLog.default, type: .debug)
          return
      }
      os_log("Deeplink URL FOUND: %@", log: OSLog.default, type: .debug, deeplink.absoluteString)
      RCTLinkingManager.application(UIApplication.shared, open: deeplink, options: [:])
  }

}

Вот мой фрагмент кода info.plist.

<key>UIApplicationSceneManifest</key>
    <dict>
        <key>UISceneConfigurations</key>
        <dict>
            <key>CPTemplateApplicationSceneSessionRoleApplication</key>
            <array>
                <dict>
                    <key>UISceneClassName</key>
                    <string>CPTemplateApplicationScene</string>
                    <key>UISceneConfigurationName</key>
                    <string>CarPlay Configuration</string>
                    <key>UISceneDelegateClassName</key>
                    <string>$(PRODUCT_MODULE_NAME).CarPlaySceneDelegate</string>
                </dict>
            </array>
            <key>UIWindowSceneSessionRoleApplication</key>
            <array>
                <dict>
                    <key>UISceneConfigurationName</key>
                    <string>Default Configuration</string>
                    <key>UISceneDelegateClassName</key>
                    <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
                </dict>
            </array>
        </dict>
    </dict>

Он работает, как и ожидалось, в сборке Android, но проблемы возникают только со сборкой iOS. Если я вернусь к appDelegate без SceneDelegate, мое приложение Carplay не запустится.

Любая помощь будет оценена по достоинству.

Спасибо.

Я видела, что вы тоже справились с userActivity, вроде правильно. Можете ли вы поставить точку отладки на willConnectTo session, чтобы проверить userActivity.webpageURL?

sonle 12.04.2024 13:22

да, я сделал это и получаю правильный URL. Я тоже самое распечатал. даже в функции handleDeepLink я также получаю URL-адрес, но RCTLinkingManager.application не переходит в нужное место.

Sagar 12.04.2024 13:31

Тогда я думаю, что класс RCTLinkingManager не был готов к использованию. Пробовали ли вы поставить некоторую задержку после получения URL-адреса? Только для тестирования, например DispatchQueue.main.asyncAfter(deadline: .now() + 2)

sonle 12.04.2024 13:35

Но если приложение находится в фоновом режиме, то выполняется тот же метод и он также попадает в ожидаемое место. Проблема возникает только тогда, когда приложение удаляется из памяти и пытается открыться через диплинк.

Sagar 12.04.2024 13:56

Это вполне понятно. На момент запуска вашего приложения нет гарантии, что оно «готово к использованию». то есть, возможно, вы когда-нибудь захотите представить viewController внутри viewDidLoad, он тоже не будет работать.

sonle 12.04.2024 14:47
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
5
63
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Разбираюсь с помощью @sonle

добавив время для выполнения глубокой ссылки, проблема была решена.

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            handleDeepLink(deeplink)
        }

Спасибо за руководство, Сонле.

Другие вопросы по теме