在 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。