iOS扩展Target(如Widget)中Swift调用React Native JS代码方案咨询
因为Widget属于App Extension,和主App是独立进程,RCTEventEmitter依赖主App的React Native运行环境,所以在扩展里无法直接使用。下面是两种可行的跨进程通信方案:
方案一:App Groups + UserDefaults 共享状态
通过App Groups实现主App和Widget的数据共享,结合主App激活时的回调触发JS逻辑:
配置App Groups
在Xcode中给主App和Widget扩展添加同一个App Group(Signing & Capabilities → 添加App Groups,输入统一的标识如group.com.yourapp.widget)。Widget端写入交互事件
在Widget的交互回调中,把事件信息写入共享的UserDefaults,并标记未处理状态:func handleWidgetTap(actionType: String, params: [String: Any]? = nil) { guard let sharedDefaults = UserDefaults(suiteName: "group.com.yourapp.widget") else { return } // 存储事件数据 if let params = params { sharedDefaults.set(try? JSONSerialization.data(withJSONObject: params), forKey: "WidgetEventParams") } sharedDefaults.set(actionType, forKey: "WidgetEventType") sharedDefaults.set(true, forKey: "HasUnprocessedWidgetEvent") sharedDefaults.synchronize() // 唤醒主App(可选,若需立即触发) if let url = URL(string: "yourapp://widget-trigger") { UIApplication.shared.open(url) } }主App端读取并触发JS逻辑
在AppDelegate的applicationDidBecomeActive或SceneDelegate的sceneDidBecomeActive中检查共享数据,然后通过RN的事件机制发送到JS:func applicationDidBecomeActive(_ application: UIApplication) { guard let sharedDefaults = UserDefaults(suiteName: "group.com.yourapp.widget"), sharedDefaults.bool(forKey: "HasUnprocessedWidgetEvent") else { return } let actionType = sharedDefaults.string(forKey: "WidgetEventType") ?? "" var eventBody: [String: Any] = ["type": actionType] // 解析参数 if let paramsData = sharedDefaults.data(forKey: "WidgetEventParams"), let params = try? JSONSerialization.jsonObject(with: paramsData) as? [String: Any] { eventBody.merge(params) { $1 } } // 发送事件到React Native RCTBridge.current()?.eventDispatcher().sendAppEvent(withName: "WidgetInteraction", body: eventBody) // 清除已处理标记 sharedDefaults.set(false, forKey: "HasUnprocessedWidgetEvent") sharedDefaults.removeObject(forKey: "WidgetEventType") sharedDefaults.removeObject(forKey: "WidgetEventParams") sharedDefaults.synchronize() }RN端监听事件
在JS代码中监听Native事件,执行对应逻辑:import { NativeEventEmitter, NativeModules } from 'react-native'; import { useEffect } from 'react'; const widgetEventEmitter = new NativeEventEmitter(NativeModules.RCTDeviceEventEmitter); const widgetSubscription = widgetEventEmitter.addListener( 'WidgetInteraction', (event) => { // 这里执行你的业务逻辑 console.log('收到Widget交互:', event.type, event); } ); // 组件卸载时取消订阅 useEffect(() => { return () => widgetSubscription.remove(); }, []);
方案二:自定义URL Scheme 跳转传参
通过自定义URL唤醒主App并携带交互参数,主App解析参数后触发JS逻辑:
配置自定义URL Scheme
在Xcode的主AppInfo → URL Types中添加自定义Scheme(如yourapp)。Widget端触发跳转
在交互回调中打开带参数的自定义URL:func handleWidgetLike(itemId: String) { guard let encodedId = itemId.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: "yourapp://widget-action?type=like&id=\(encodedId)") else { return } UIApplication.shared.open(url, options: [:], completionHandler: nil) }主App端解析URL并触发JS
在AppDelegate中处理URL跳转:func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false), let queryItems = components.queryItems else { return false } // 解析参数 let eventData = queryItems.reduce(into: [String: String]()) { result, item in if let value = item.value { result[item.name] = value } } // 发送事件到RN RCTBridge.current()?.eventDispatcher().sendAppEvent(withName: "WidgetInteraction", body: eventData) return true }RN端监听逻辑同方案一
注意事项
- 若主App处于后台,
openURL会将其唤醒到前台,确保事件能及时处理。 - App Groups的UserDefaults同步存在微小延迟,结合URL唤醒可提升实时性。
- 对于iOS17的Widget交互(如按钮、滑块等),需在
WidgetConfiguration或Intent的回调中执行上述跨进程逻辑。
内容的提问来源于stack exchange,提问作者cuttlas

