You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 15:06:22