skip to Main Content

I am working on showing the React-native-View in the swift project. I download the bunlde file through asynchronous operation, save the bundle file in the app directory, and use it to create the RCTROOTVIEW. My current problem is that when I try to show the view, I see a blank screen because the process of downloading and saving the file is asynchronous. How can I solve this problem?

my source

import UIKit
import React
import SwiftUI
import Foundation

struct ReactNativeView: UIViewRepresentable {
  
    let mockData: NSDictionary = ["scores": [["name": "Alex", "value": "42"], ["name": "Joel", "value": "10"]]]
    let jsCodeLocation = URL(string: "https://CDN.com/main.jsbundle")!
  
    func getDocumentsDirectory() -> URL {
        return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    }

    func downloadBundle(url: URL, completion: @escaping (URL?, Error?) -> Void) {
      let documentsUrl = getDocumentsDirectory()
      let destinationUrl = documentsUrl.appendingPathComponent(url.lastPathComponent)
         URLSession.shared.downloadTask(with: url) { (tempLocalUrl, response, error) in
             guard let tempLocalUrl = tempLocalUrl, error == nil else {
                 print("Error downloading bundle: (String(describing: error))")
                 completion(nil, error)
                 return
             }

             do {
                 try FileManager.default.moveItem(at: tempLocalUrl, to: destinationUrl)
                 print("File moved to destination [(destinationUrl.path)]")
                 completion(destinationUrl, nil)
             } catch {
                 print("Error moving file to destination: (error)")
                 completion(nil, error)
             }
         }.resume()
    }
 
    func makeUIView(context: Context) -> UIView {
      var rootView : RCTRootView?
 
      downloadBundle(url: jsCodeLocation) { _url, error in
        guard let localURL = _url, error == nil else {
            print("error saving file")
            return
        }
        rootView = RCTRootView(
            bundleURL: localURL,
            moduleName: "RNHighScores",
            initialProperties: mockData as [NSObject: AnyObject],
            launchOptions: nil
        )
      }
      

      
      return rootView ?? UIView()
    }

    func updateUIView(_ uiView: UIView, context: Context) {}
  
}

use code

@objc private func showReactNativeView() {
         let reactNativeViewController = UIHostingController(rootView: ReactNativeView())
         present(reactNativeViewController, animated: true, completion: nil)
  }
...
containerButton.rx.tap
            .asDriverOnErrorJustComplete()
            .drive(onNext: { [weak self] in
        self?.showReactNativeView()
            })
            .disposed(by: disposeBag)

Finally, if you already have a downloaded file, I want to erase or overwrite it.

2

Answers


  1. your func makeUIView(context: Context) -> UIView returns nil due to the async operation. This is fundamentally bad as you should never assign a returned variable in an async operation in a sync context to return the result.

    There are many ways of solving this:

    • Perform the async operation inside RCTRootView so it can update itself without requiring SwiftUI to do anything
    • Return only a container view in func makeUIView(context: Context) -> UIView, then once the operation is done, create RCTRootView and add it to the container view
    • Having a @State property to track the progress (loading/finished) and update the view in func updateUIView(_ uiView: UIView, context: Context) using that State property.
    Login or Signup to reply.
  2. It appears that there is a problem with the asynchronous download of the bundle and the RCTRootView creation in your ReactNativeView. Because downloading is asynchronous, it’s possible that the makeUIView function will return before the download is finished, leaving you with a blank screen.

    struct ReactNativeView: UIViewRepresentable {
      
    //Your existing code..........................
    
    @State private var isDownloadComplete = false
    
    func makeUIView(context: Context) -> UIView {
        var rootView: RCTRootView?
    
        if !isDownloadComplete {
            downloadBundle(url: jsCodeLocation) { _url, error in
                guard let localURL = _url, error == nil else {
                    print("error saving file")
                    return
                }
                rootView = RCTRootView(
                    bundleURL: localURL,
                    moduleName: "RNHighScores",
                    initialProperties: mockData as [NSObject: AnyObject],
                    launchOptions: nil
                )
    
                // Set the flag to indicate download completion
                isDownloadComplete = true
            }
        } else {
            // If download is complete, create RCTRootView immediately
            rootView = RCTRootView(
                bundleURL: getDocumentsDirectory().appendingPathComponent(jsCodeLocation.lastPathComponent),
                moduleName: "RNHighScores",
                initialProperties: mockData as [NSObject: AnyObject],
                launchOptions: nil
            )
        }
    
        return rootView ?? UIView()
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {}
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search