SwiftUI UserDefaults使用App Group共享数据
SwiftUI UserDefaults使用App Group共享数据

SwiftUI UserDefaults使用App Group共享数据

在 SwiftUI 中使用 App Group(应用组)通常是为了在主 App 与其扩展(如 Widget、App Clip、Share Extension 等)之间共享数据。它的核心是通过使用一个共享的容器(通常是 UserDefaults 或 FileManager 的共享目录)来进行数据读写。

配置App Group

需要注意的是,只有通过 App Group 配置,主应用和其他应用(例如小组件Widget)才能共享数据。

使用 UserDefaults(suiteName:) 来指定一个共享的存储空间(例如:group.com.fangjunyu.piglet),确保主应用和 Widget 都通过这个共享的 UserDefaults 来读取和写入数据。

小组件示例

例如,我们需要在主应用和小组件之间通过App Group共享数据。

在 Xcode 中,为主应用和 Widget Extension 共同启用 App Groups。

打开 Xcode,选择主应用和 Widget Extension。

在 Signing & Capabilities 中,启用 App Groups。

确保为主应用和 Widget Extension 设置相同的 App Group 标识符(例如:group.com.fangjunyu.piglet)。

group.com.fangjunyu.piglet

配置App Group报错

如果在配置App Group后,出现如下报错信息:

Provisioning profile "iOS Team Provisioning Profile: com.fangjunyu.piglet" doesn't support the group.com.fangjunyu.piglet App Group.
Provisioning profile "iOS Team Provisioning Profile: com.fangjunyu.piglet" doesn't match the entitlements file's value for the com.apple.security.application-groups entitlement.

这个报错信息表示Provisioning Profile(描述文件) 并不支持在代码里使用的 App Group 权限。

报错信息为:当前使用的描述文件没有包含 group.com.fangjunyu.piglet这个 App Group 标识符。

.entitlements 文件中配置了 com.apple.security.application-groups 权限,要求使用某个 App Group,但描述文件中并没有包含它。

因此,需要检查App Group 设置:

1、打开 Xcode → 选中主项目 → 目标(Targets)→ Signing & Capabilities。

2、在 Apple Developer 网站更新 App Group 权限。

前往 Apple Developer – Certificates, Identifiers & Profiles,在Identifiers模块中,找到对应的App。

点进去,检查“App Groups” 部分是否勾选,在确认或重新勾选后,点击“Edit”按钮添加自己创建的App Group。

在App Group Assignment中勾选创建的App Group。

注意:如果在App Group Assignment中没有看到你创建的App Group,可能存在以下几种可能。

1、Apple服务器存在部分延迟,当Xcode项目配置App Groups后,需要等待数分钟的时间才会同步更新到App Group Assignment中。

2、检查Xcode项目中配置的App Groups,是否存在自己创建的App Group,如果是和其他应用(例如小组件Widget)一起使用App Group,也检查其他应用中的App Group是否配置。

3、假设在iOS主应用中配置并创建了App Group,但是其他应用中配置App Group后,无法通过刷新以及等待数分钟,显示创建的App Group,那么就在其他应用中创建一个同名的App Group,这个操作可能解决问题。

使用 UserDefaults 共享数据

使用UserDefaults存储App Group,suiteName就是在Xcode中配置并创建的App Group名称。

// 1. 获取共享 UserDefaults
if let userDefaults = UserDefaults(suiteName: "group.com.yourcompany.yourapp") {
    // 2. 设置数据
    userDefaults.set("Hello from App Group!", forKey: "sharedKey")
    userDefaults.synchronize()
}

在其他应用中通过forKey读取数据。

// 读取数据
if let userDefaults = UserDefaults(suiteName: "group.com.yourcompany.yourapp") {
    let value = userDefaults.string(forKey: "sharedValue") ?? "默认值"
    let amount = userDefaults.amount(forKey: "sharedAmount") ?? "默认值"
    print("读取到:\(value) 和 \(amount)")
}

使用共享文件目录(例如用于图片或数据库)

下面是更高级的用法,比如我们在处理小组件的时候,需要使用iOS主应用的图片,我们可以在小组件的资源文件夹中新增图片,或者使用共享文件目录的方法。

在iOS主应用中创建saveImageToAppGroup方法:

func saveImageToAppGroup(_ image: UIImage, name: String) {
    guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.yourcompany.yourapp") else {
        return
    }
    let imagesFolder = containerURL.appendingPathComponent("Images", isDirectory: true)
    
    // 创建文件夹(如果不存在)
    try? FileManager.default.createDirectory(at: imagesFolder, withIntermediateDirectories: true)

    let fileURL = imagesFolder.appendingPathComponent("\(name).png")
    
    if let data = image.pngData() {
        try? data.write(to: fileURL)
    }
}

调用方式:

saveImageToAppGroup(yourUIImage, name: "icon_1")

读取图片(Widget 或 App):

func loadImageFromAppGroup(name: String) -> UIImage? {
    guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.yourcompany.yourapp") else {
        return nil
    }
    let fileURL = containerURL.appendingPathComponent("Images/\(name).png")
    return UIImage(contentsOfFile: fileURL.path)
}

显示照片:

if let image = loadImageFromAppGroup(name: "icon_1") {
    Image(uiImage: image)
}

注意事项

1、App 与 Widget 都必须加入相同的 App Group。

2、图片尺寸尽量小,否则 Widget 加载时性能可能受影响。

3、Widget 访问共享容器时不能太频繁,Apple 有限制。

使用场景

1、隔离测试环境

在 Xcode 预览或开发阶段,隔离的 UserDefaults 可用于安全测试:

修改数据只会影响隔离存储,不会影响真实的用户数据。

提供了一个虚拟存储环境,在预览中模拟 App 的行为。

2、多用户或多实例支持

在支持多用户多账户切换的场景中,每个用户可以拥有独立的 UserDefaults 存储。比如:

为每个用户分配一个独立的存储实例。

不同的应用模块或组件可以使用自己的存储实例,避免数据冲突。

3、应用间共享(App Group)

如果有多个应用(或应用和扩展),希望共享某些数据,就可以通过设置相同的 suiteName 来实现跨进程共享。

如何清除App Group的内容?

可以通过以下方法清除指定 suiteName 的隔离存储内容:

1、直接调用 removePersistentDomain

使用 UserDefaults 的 removePersistentDomain(forName:) 方法,清理特定 suiteName 的数据:

if let userDefaults = UserDefaults(suiteName: " group.com.yourcompany.yourapp") {
    userDefaults.removePersistentDomain(forName: " group.com.yourcompany.yourapp")
    userDefaults.synchronize() // 确保立即生效
}

解释

removePersistentDomain(forName:) 会清除指定 suiteName 下的所有内容。

synchronize() 确保数据更改立即保存到磁盘。

2、在 Xcode 预览中自动清理

如果只是在预览环境中使用隔离存储,可以在 #Preview 或视图加载时自动清除:

@AppStorage("previewKey", store: UserDefaults(suiteName: " group.com.yourcompany.yourapp")) var previewValue: String = "Default"

#Preview {
    // 在每次预览加载时清理存储
    if let previewDefaults = UserDefaults(suiteName: " group.com.yourcompany.yourapp") {
        previewDefaults.removePersistentDomain(forName: " group.com.yourcompany.yourapp")
    }
    return ContentView()
}

3、手动清理特定的 key

如果只想清理某个 key,而不是整个存储,可以单独移除该 key:

if let userDefaults = UserDefaults(suiteName: " group.com.yourcompany.yourapp") {
    userDefaults.removeObject(forKey: "yourKey")
    userDefaults.synchronize()
}

总结

1、隔离存储的意义:通过指定 suiteName,隔离存储数据,避免干扰默认的 UserDefaults.standard。

2、全局影响:只要指定了 suiteName,就不会影响默认存储,作用范围仅限于指定的隔离存储空间。

3、如何清理:通过 removePersistentDomain(forName:) 清除指定存储,或移除特定 key。

扩展知识

@AppStorage语法糖

在SwiftUI中,可以搭配@AppStorage将数据存储到App Group中:

@AppStorage("yourKey", store: UserDefaults(suiteName: " group.com.yourcompany.yourapp")) var yourValue: String = ""

这段代码创建了一个 UserDefaults 的自定义实例,使用指定的 App Group 或 Suite 名称 PreviewUserDefaults。

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注