React Native如何通过sharedGroupPreferences向Swift传递图片数据
React Native iOS 图片小组件实现方案
不建议将图片二进制数据直接塞进UserDefaults传输,大体积图片容易触发UserDefaults读取失败、性能损耗问题,优先推荐共享目录存图+传路径的方案,小图场景也可以用base64传输。
第一步:React Native端代码调整
原有传文本的逻辑保留,新增图片写入共享目录的逻辑,把图片路径和文本一起存在共享UserDefaults里即可:
import SharedGroupPreferences from 'react-native-shared-group-preferences'; import RNFS from 'react-native-fs'; // 用于文件读写,可替换为任意支持App Group路径操作的文件库 const appGroupIdentifier = 'group.com.noisolation.ev1-debug.HomePhotos'; const updateWidgetContent = async (imageLocalPath, displayText) => { try { // 获取App Group共享目录路径,在共享目录下专门建个文件夹存小组件图片 const sharedWidgetDir = `${RNFS.pathForGroup(appGroupIdentifier)}/widget_assets`; await RNFS.mkdir(sharedWidgetDir); const targetImagePath = `${sharedWidgetDir}/show_image.jpg`; // 把主App沙盒里的图片复制到共享目录(Widget读不到主App沙盒的文件,必须复制) await RNFS.copyFile(imageLocalPath, targetImagePath); // 把文本、图片路径一起写入共享UserDefaults await SharedGroupPreferences.setItem( 'savedData', { text: displayText, imagePath: targetImagePath }, appGroupIdentifier, ); // 写入完成后主动调用小组件刷新接口,即可立即看到新内容 } catch (error) { console.log(error); } };
如果是体积很小的图标类图片,也可以直接把图片转成base64字符串,存在imageBase64字段里传给Swift端,不建议超过300KB的图用这个方式。
第二步:Swift端代码修改
在你原有代码基础上新增图片读取、展示逻辑即可,修改后的完整代码如下:
import WidgetKit import SwiftUI import Intents // 匹配RN端传的JSON字段 struct WidgetData: Decodable { var text: String var imagePath: String? var imageBase64: String? } struct Provider: IntentTimelineProvider { func placeholder(in context: Context) -> SimpleEntry { SimpleEntry(date: Date(), configuration: ConfigurationIntent(), text: "Placeholder", showImage: UIImage(named: "placeholder")!) } func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) { let entry = SimpleEntry(date: Date(), configuration: configuration, text: "Data goes here", showImage: UIImage(named: "placeholder")!) completion(entry) } func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> Void) { let entryDate = Date() let userDefaults = UserDefaults.init(suiteName: "group.com.noisolation.ev1-debug.HomePhotos") if userDefaults != nil { if let savedData = userDefaults!.value(forKey: "savedData") as? String { let decoder = JSONDecoder() let data = savedData.data(using: .utf8) if let parsedData = try? decoder.decode(WidgetData.self, from: data!) { let nextRefresh = Calendar.current.date(byAdding: .minute, value: 5, to: entryDate)! // 读取展示用的图片,优先读共享路径的图,读不到再尝试base64,都失败就用占位图 var displayImage: UIImage = UIImage(named: "placeholder")! if let imgPath = parsedData.imagePath, let pathImg = UIImage(contentsOfFile: imgPath) { displayImage = pathImg } else if let base64Str = parsedData.imageBase64, let imgData = Data(base64Encoded: base64Str), let base64Img = UIImage(data: imgData) { displayImage = base64Img } let entry = SimpleEntry(date: nextRefresh, configuration: configuration, text: parsedData.text, showImage: displayImage) let timeline = Timeline(entries: [entry], policy: .atEnd) completion(timeline) } else { print("Could not parse data") } } else { let nextRefresh = Calendar.current.date(byAdding: .minute, value: 5, to: entryDate)! let entry = SimpleEntry(date: nextRefresh, configuration: configuration, text: "No data set", showImage: UIImage(named: "placeholder")!) let timeline = Timeline(entries: [entry], policy: .atEnd) completion(timeline) } } } } // 新增图片字段存储Timeline数据 struct SimpleEntry: TimelineEntry { let date: Date let configuration: ConfigurationIntent let text: String let showImage: UIImage } struct HomePhotosEntryView : View { var entry: Provider.Entry var body: some View { ZStack { // 展示图片作为背景 Image(uiImage: entry.showImage) .resizable() .scaledToFill() .edgesIgnoringSafeArea(.all) // 原有文字展示逻辑保留,加个阴影避免和浅色图片混在一起看不清 VStack { Text(entry.text) .bold() .foregroundColor(.white) .shadow(radius: 2) }.padding(20) } } } @main struct HomePhotos: Widget { let kind: String = "HomePhotos" var body: some WidgetConfiguration { IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in HomePhotosEntryView(entry: entry) } .configurationDisplayName("My Widget") .description("This is an example widget.") } } struct HomePhotos_Previews: PreviewProvider { static var previews: some View { HomePhotosEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent(), text: "Widget preview", showImage: UIImage(named: "placeholder")!)) .previewContext(WidgetPreviewContext(family: .systemSmall)) } }
注意事项
- 提前在Xcode中为主App Target和Widget Target勾选配置完全一致的App Group权限,权限ID和代码里的
suiteName保持一致,否则无法读取共享存储内容 - 不要直接传主App沙盒内的图片路径,Widget属于独立进程,没有主App沙盒的读取权限,必须把图片复制到App Group共享目录后再传路径
- 传入小组件的图片建议提前压缩到对应小组件尺寸的大小,不要直接传几MB的原图,否则会拖慢小组件加载速度,严重时会被系统直接终止小组件进程
- RN端写入新的图片数据后,建议主动调用小组件刷新接口,不用等默认的5分钟自动刷新周期就能立即看到新内容
- 需要提前在Widget Target的资源目录(Assets)中导入一张命名为
placeholder的默认占位图,避免图片加载失败时页面空白
内容的提问来源于stack exchange,提问作者brace brace
相关产品推荐
相关产品推荐

