iOS Screen Time API:从DeviceActivityMonitorExtension获取FamilyActivitySelection
解决方案:解决DeviceActivityMonitorExtension无法获取FamilyActivitySelection的问题
核心问题分析
你遇到的问题本质是主App与Extension的UserDefaults容器不共享,尽管配置了App Groups,但代码中仍使用UserDefaults.standard(各自独立的沙盒容器),导致Extension无法读取主App保存的选择数据。此外,忽略编码解码错误也会掩盖序列化失败的问题。
具体修复步骤
1. 切换到App Groups共享的UserDefaults
将所有UserDefaults.standard替换为App Groups对应的共享容器,确保主App和Extension访问同一存储区域:
- 替换为你项目中实际的App Groups标识符(示例为
group.com.yourcompany.yourapp)
2. 完善编码解码的错误处理
移除try?的静默失败逻辑,添加显式错误捕获,便于排查序列化过程中的问题。
3. 初始化时加载共享数据
修改MyModel的初始化逻辑,启动时自动从共享UserDefaults加载已保存的选择,避免初始化为空值。
修改后的代码示例
MyModel.swift 修改部分
class MyModel: ObservableObject { let store = ManagedSettingsStore(named: .mySettingStore) static let shared = MyModel() @Published var familyActivitySelection: FamilyActivitySelection private let encoder = PropertyListEncoder() private let decoder = PropertyListDecoder() private let userDefaultsKey = "ScreenTimeSelection" // 替换为你的App Groups标识符 private let appGroupSuiteName = "group.com.yourcompany.yourapp" // 获取共享UserDefaults private var sharedDefaults: UserDefaults? { UserDefaults(suiteName: appGroupSuiteName) } func saveFamilyActivitySelection(selection: FamilyActivitySelection) { print("selected app updated: ", selection.applicationTokens.count," category: ", selection.categoryTokens.count) guard let defaults = sharedDefaults else { print("Failed to access App Groups UserDefaults") return } do { let data = try encoder.encode(selection) defaults.set(data, forKey: userDefaultsKey) defaults.synchronize() // 确保数据立即写入 // 验证保存结果 if let saved = getSavedFamilyActivitySelection() { print("Saved successfully: apps=\(saved.applicationTokens.count), categories=\(saved.categoryTokens.count)") } } catch { print("Failed to encode selection: \(error.localizedDescription)") } } func getSavedFamilyActivitySelection() -> FamilyActivitySelection? { guard let defaults = sharedDefaults else { print("Could not access App Groups UserDefaults") return nil } guard let data = defaults.data(forKey: userDefaultsKey) else { print("No saved selection data found") return nil } do { let selectedApp = try decoder.decode(FamilyActivitySelection.self, from: data) print("saved selected app updated: \(selectedApp.categoryTokens.count)") return selectedApp } catch { print("Failed to decode selection: \(error.localizedDescription)") return nil } } init() { // 初始化时加载已保存的选择 if let savedSelection = getSavedFamilyActivitySelection() { familyActivitySelection = savedSelection print("Loaded saved selection on init") } else { familyActivitySelection = FamilyActivitySelection() print("Initialized with empty selection") } } // ... 其余方法保持不变 }
DeviceActivityMonitorExtension.swift 优化部分
在intervalDidStart中先验证数据是否加载成功,再执行限制逻辑:
override func intervalDidStart(for activity: DeviceActivityName) { super.intervalDidStart(for: activity) // 先验证数据是否获取成功 if let selection = model.getSavedFamilyActivitySelection() { print("Extension loaded selection: apps=\(selection.applicationTokens.count), categories=\(selection.categoryTokens.count)") model.startAppRestrictions() showLocalNotification(title: "My Start Restrict App", desc: "Restriction App started successfully") } else { print("Extension failed to load selection") showLocalNotification(title: "Restriction Failed", desc: "No apps selected") } let socialStore = ManagedSettingsStore(named: .mySettingStore) socialStore.clearAllSettings() }
额外验证步骤
- 确认App Groups配置
- 主App和Extension的
Signing & Capabilities中均添加App Groups,并勾选同一组 - 检查两者Info.plist的
com.apple.security.application-groups数组,确保包含相同的组标识符
- 主App和Extension的
- 测试权限
- 确保App已获得Family Controls的完整权限(在系统设置中确认)
- 日志排查
- 查看Xcode控制台的打印信息,确认编码解码是否成功,以及共享UserDefaults是否能正常访问
内容的提问来源于stack exchange,提问作者Yusuf Fachroni
相关产品推荐
相关产品推荐

