Swift项目中UIViewRepresentable加载RN视图空白及文件覆盖问题求助
解决Swift项目中React Native视图异步加载空白与bundle覆盖问题
核心问题分析
- 空白屏问题:
makeUIView方法同步执行,返回视图时bundle下载尚未完成,因此初始返回空白UIView,后续创建的RCTRootView无法自动更新到界面。 - bundle覆盖问题:下载时若目标路径已存在文件,
moveItem操作会抛出错误,导致无法更新bundle。
解决方案与代码修改
1. 状态管理与视图更新逻辑
通过@State追踪本地bundle的加载状态,在下载完成后触发视图更新,将RCTRootView动态添加到容器中。
2. 预删除已有bundle文件
在下载前检查目标路径是否存在旧bundle,若存在则删除,确保新文件能正常写入。
修改后的完整代码
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")! // 追踪本地bundle的加载状态 @State private var localBundleURL: URL? 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) // 先删除已存在的bundle文件 if FileManager.default.fileExists(atPath: destinationUrl.path) { do { try FileManager.default.removeItem(at: destinationUrl) print("已删除旧bundle文件: \(destinationUrl.path)") } catch { print("删除旧bundle失败: \(error)") completion(nil, error) return } } URLSession.shared.downloadTask(with: url) { (tempLocalUrl, response, error) in guard let tempLocalUrl = tempLocalUrl, error == nil else { print("下载bundle失败: \(String(describing: error))") completion(nil, error) return } do { try FileManager.default.moveItem(at: tempLocalUrl, to: destinationUrl) print("bundle已保存到: \(destinationUrl.path)") completion(destinationUrl, nil) } catch { print("移动bundle文件失败: \(error)") completion(nil, error) } }.resume() } func makeUIView(context: Context) -> UIView { // 返回一个容器视图,后续动态添加RCTRootView let containerView = UIView() // 开始下载bundle downloadBundle(url: jsCodeLocation) { url, error in DispatchQueue.main.async { self.localBundleURL = url } } return containerView } func updateUIView(_ containerView: UIView, context: Context) { // 清空容器中已有视图 containerView.subviews.forEach { $0.removeFromSuperview() } guard let localURL = localBundleURL else { // 可在此添加加载提示视图 let loadingLabel = UILabel() loadingLabel.text = "加载中..." loadingLabel.textAlignment = .center loadingLabel.frame = containerView.bounds containerView.addSubview(loadingLabel) return } // 创建并添加RCTRootView let rootView = RCTRootView( bundleURL: localURL, moduleName: "RNHighScores", initialProperties: mockData as [NSObject: AnyObject], launchOptions: nil ) rootView.frame = containerView.bounds containerView.addSubview(rootView) } }
使用代码(无需修改)
@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)
关键修改说明
- 状态管理:通过
@State private var localBundleURL追踪bundle下载状态,下载完成后在主线程更新状态,触发updateUIView。 - 视图容器:
makeUIView返回空容器,后续通过updateUIView动态替换内容,避免初始空白。 - 文件覆盖:下载前检查并删除旧bundle,确保新文件能正常写入。
- 主线程更新:UI操作必须在主线程执行,因此在download的completion中切换到主线程更新状态。
内容的提问来源于stack exchange,提问作者viteknow
相关产品推荐
相关产品推荐

