I am working on a SwiftUI app using the Screen Time API and the DeviceActivityReport view to display app usage data. My current implementation successfully shows daily app usage using a DeviceActivityFilter with the .daily(during:) segment. However, I need to filter this data to show app usage only for a specific time period during the day, e.g., 4:00 PM to 5:00 PM.
I created a DeviceActivityFilter with a .daily(during:) segment and passed a DateInterval for the desired time range:
let now = Date()
let startTime = calendar.date(bySettingHour: 16, minute: 0, second: 0, of: now)!
let endTime = calendar.date(bySettingHour: 17, minute: 0, second: 0, of: now)!
let timeInterval = DateInterval(start: startTime, end: endTime)
let filter = DeviceActivityFilter(
segment: .daily(during: timeInterval),
users: .all,
devices: .init([.iPhone])
)
I applied this filter to the DeviceActivityReport view:
DeviceActivityReport(context, filter: filter)
Even with the DateInterval set for the specific time range, the report still shows the total daily usage for each app, instead of restricting the results to the specified 1:00 PM to 5:00 PM range.
Device Activity
RSS for tagMonitor web and app usage through custom time windows and events.
Posts under Device Activity tag
95 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Hello
I am wondering how and if it even is possible to grab the amount of times a user has opened a specific app. Of course these apps will be selected for tracking by the user through the FamilyControls API, but is it possible to then list those selected apps and their amount of openings?
I know Screen Time API is very strict with giving developers control of this information outside of just displaying a view so I don't know if this is possible.
I saw that DeviceActivityData.ApplicationActivity has a value called "numberOfPickups" but I'm not sure how to access that value and display it in my app.
Thank you
Hi everyone,
I've been going through the forums and documentation, but I'm still unsure about how the Screen Time API works or whether it can achieve what I have in mind.
Specifically, I want to display the current screen time value on a widget. Additionally, I’d like to use that screen time value for some calculations and represent the results in a different way within the widget.
I’d really appreciate any guidance or insights. Thanks in advance! 😊
let schedule = DeviceActivitySchedule(
intervalStart: startInterval,
intervalEnd: endInterval,
repeats: false,
warningTime : DateComponents(hour : 0 , minute: 0 , second: 30)
)
```
I hope it will only be executed on the same day, but the monitoring is still there the next day. My system version is 18.2
Bellow I created Manager to be easier for me to handle app limits, but for some reason It never reached callbacks function, I have permission for screen time, I added the capabilities for it also, I'm sure, I send correctly the appTokens, categoriesTokens ... and the time limit and it also reach ✅ Monitoring started for..., I don't know what to do anymore:
import SwiftUI
import DeviceActivity
import FamilyControls
import ManagedSettings
@MainActor
class AppUsageManager: DeviceActivityMonitor, ObservableObject {
static let shared = AppUsageManager()
private let deviceActivityCenter = DeviceActivityCenter()
private var monitoringSelections: [DeviceActivityName: (selection: FamilyActivitySelection, timeLimit: DateComponents)] = [:]
private var resetTimer: Timer?
private override init() {
super.init()
print("🟢 AppUsageManager initialized.")
}
// MARK: - Public Methods
/// Configures monitoring for a selection with a specific event name and time limit.
func configureMonitoring(
for selection: FamilyActivitySelection,
timeLimitInMinutes: Int,
activityName: String,
eventName: String
) {
let activityName = DeviceActivityName(activityName)
let eventName = DeviceActivityEvent.Name(eventName)
monitoringSelections[activityName] = (selection, DateComponents(minute: timeLimitInMinutes))
setupMonitoring(for: activityName, with: eventName)
}
/// Stops monitoring for a specific event.
func stopMonitoring(for activityName: String) {
let activityName = DeviceActivityName(activityName)
Task {
print("🛑 Stopping monitoring for \(activityName.rawValue).")
deviceActivityCenter.stopMonitoring([activityName])
monitoringSelections.removeValue(forKey: activityName)
}
}
/// Stops all monitoring.
func stopAllMonitoring() {
print("🛑 Stopping monitoring")
deviceActivityCenter.stopMonitoring()
}
// MARK: - Private Methods
/// Sets up monitoring for a specific event.
private func setupMonitoring(
for activityName: DeviceActivityName,
with eventName: DeviceActivityEvent.Name
) {
stopAllMonitoring()
guard let (selection, timeLimit) = monitoringSelections[activityName] else {
print("⚠️ No selection configured for \(activityName.rawValue).")
return
}
print("🛠 Setting up monitoring for \(activityName.rawValue).")
print("📋 Monitoring Details:")
print("- Time Limit: \(timeLimit.minute ?? 0) minutes.")
let warningThreshold = DateComponents(minute: 3)
let timeZone = TimeZone.current
let schedule = DeviceActivitySchedule(
intervalStart: DateComponents(timeZone: timeZone, hour: 0, minute: 0, second: 0),
intervalEnd: DateComponents(timeZone: timeZone, hour: 23, minute: 59, second: 59),
repeats: true,
warningTime: warningThreshold
)
let events: [DeviceActivityEvent.Name: DeviceActivityEvent] = [
eventName: DeviceActivityEvent(
applications: selection.applicationTokens,
categories: selection.categoryTokens,
webDomains: selection.webDomainTokens,
threshold: timeLimit
)
]
do {
try deviceActivityCenter.startMonitoring(
activityName,
during: schedule,
events: events
)
print("✅ Monitoring started for \(activityName.rawValue) with time limit \(timeLimit.minute ?? 0) minutes.")
} catch {
print("❌ Failed to start monitoring \(activityName.rawValue): \(error.localizedDescription)")
}
}
// MARK: - DeviceActivityMonitor Overrides
override func intervalDidStart(for activity: DeviceActivityName) {
print("🟢 Interval for \(activity.rawValue) started.")
}
override func intervalWillStartWarning(for activity: DeviceActivityName) {
print("⚠️ Warning: \(activity.rawValue) is about to start.")
}
/// Handles warnings for approaching the time limit.
override func eventWillReachThresholdWarning(
_ event: DeviceActivityEvent.Name,
activity: DeviceActivityName
) {
super.eventWillReachThresholdWarning(event, activity: activity)
print("⚠️ Warning: \(activity.rawValue) is about to reach its time limit.")
print("⚠️ Event: \(event.rawValue)")
}
/// Handles when the time limit is reached.
override func eventDidReachThreshold(
_ event: DeviceActivityEvent.Name,
activity: DeviceActivityName
) {
super.eventDidReachThreshold(event, activity: activity)
print("🟢 Limit reached.")
Task { @MainActor in
print("🕒 \(activity.rawValue) has reached its time limit.")
print("🕒 Event: \(event.rawValue)")
guard let (selection, _) = monitoringSelections[activity] else {
print("⚠️ No selection configured for \(activity.rawValue).")
return
}
blockApps(for: selection)
}
}
// MARK: - Blocking Logic
/// Blocks the selected apps/categories.
private func blockApps(for selection: FamilyActivitySelection) {
print("🔒 Blocking apps/categories for selection.")
print("- Applications: \(selection.applicationTokens)")
print("- Categories: \(selection.categoryTokens)")
let store = ManagedSettingsStore()
store.shield.applications = selection.applicationTokens
store.shield.applicationCategories = .specific(selection.categoryTokens)
print("🔒 Apps/categories blocked successfully.")
}
}
I am getting this error when I try to show device activity report view by this
DeviceActivityReport(appsContext, filter: filter)
Attempt to map database failed: permission was denied. This attempt will not be retried.
I have taken access by this way.
AuthorizationCenter.shared.requestAuthorization(for: .individual)
Hello, we are building an app to limit children's screen time and using Screen Time API. We found bug on the screen where the user selects app category and apps, if the user selects Other category, the screen will crash (he will be empty, but if you reload screen, it will show apps again as usual). We hope that Apple will fix it someday, but we are trying to notify our users about this problem, and the problem is that we don't know what the user selects on Apple screen with apps till the user clicks Save or Done. But we need to notify him either when he clicks on the Other category or when he faces crash of this screen. We want to show a pop-up to user with explanation why screen crashed.
When I use the screen time API, the app occasionally crashes in the morning. I mean the UI freeze lasts for more than ten seconds.
But the weird thing is that I work normally during the day, that is, in the morning, when I just woke up. (There is no Do Not Disturb mode). This problem has been bothering me for several days, please help.
The specific crash log is as follows, and the specific code is as follows.
Model: iPhone 15 Pro, iOS: 18.1.1
Thanks for your help!
Code:
private func startAppMonitoring(application: ApplicationToken, seconds: Int, isFromNow: Bool) {
let schedule = DeviceActivitySchedule(
intervalStart: isFromNow ? Calendar.current.dateComponents([.hour, .minute, .second], from: Date()) : DateComponents(timeZone: TimeZone(identifier:TimeZone.current.identifier), hour: 0, minute: 0, second: 0),
intervalEnd: DateComponents(timeZone: TimeZone(identifier:TimeZone.current.identifier), hour: 23, minute: 58, second: 59),
repeats: true,
warningTime: DateComponents(minute: 1)
)
let event = DeviceActivityEvent(
applications: Set([application]),
threshold: DateComponents(second: seconds)
)
let center = DeviceActivityCenter()
do {
try center.startMonitoring(DeviceActivityName("\(application.hashValue)Usage"), during: schedule, events: [DeviceActivityEvent.Name("\(application.hashValue)Event"): event])
} catch {
print("Error starting monitoring schedule: \(error)")
}
}
Crash report:
Hi everyone, I need to display a Graph based on Screen-time of apps per hour, from 12Am to 11PM. Am able to get the screen-time data for whole day with each app's total screen-time value.
Am kind of confused how can I get the per hour screen-time of apps. I have applied filter of day
DeviceActivityFilter(
segment: .daily(
during: Calendar.current.dateInterval(
of: .day, for: .now
)!
),
users: .all,
devices: .init([.iPhone, .iPad])
)
Am also using this data to display apps with their usage just like Apple's Screen_time in settings.
I need to display exact same graph just like Apple's screen in phone settings for a Day.
I have a app with two targets: a main DeviceActivityApp target and a DeviceReport target. In the DeviceReport target, I have a TotalActivityReport struct conforming to DeviceActivityReportScene. Inside its makeConfiguration method, I update a dynamically generated list of AppReport items. The list updates correctly in the DeviceReport target.
// Define which context your scene will represent.
let context: DeviceActivityReport.Context = .totalActivity
// Define the custom configuration and the resulting view for this report.
let content: (MonitorDeviceReport) -> TotalActivityViewFirst
@ObservedObject var activityData:ActivityData
func makeConfiguration(representing data: DeviceActivityResults<DeviceActivityData>) async -> MonitorDeviceReport {
// Reformat the data into a configuration that can be used to create
// the report's view.
var appList:[AppsReport]=[]
let totalActivityDuration = await data.flatMap { $0.activitySegments }.reduce(0, {
$0 + $1.totalActivityDuration
})
for await _data in data{
for await activity in _data.activitySegments{
for await category in activity.categories{
for await app in category.applications{
let name=app.application.localizedDisplayName ?? "No Name"
let bundleId=app.application.bundleIdentifier ?? "nil"
let duration=app.totalActivityDuration
let appIcon=app.application.token
let app=AppsReport(id:bundleId,duration:duration, name:name, icon:appIcon)
appList.append(app)
}
}
}
}
DispatchQueue.main.async {
activityData.list=appList
}
return MonitorDeviceReport(duration:totalActivityDuration, apps:appList)
}
}
public class ActivityData:ObservableObject{
@Published var list:[AppsReport]=[]
public static let shared = ActivityData()
}. // This is in MonitorReport target
However, I need to access this dynamic list in my MyApp target, specifically in ContentView.swift. I tried using an ObservableObject (ActivityData) to share the data between targets, but the list always appears empty in the MyApp target.
Here’s what I’ve tried so far:
Created a shared ActivityData instance using @Published
Passed the ActivityData instance to TotalActivityReport
Used dependency injection and a singleton pattern for ActivityData
Verified that makeConfiguration updates the list correctly in DeviceReport
What could I be missing? How can I correctly share and access this data across targets?
I am working on the device activity report. and fetched data is loading on the chart. I am developing app using TabbarController. when I go to another tab and come back to the chart screen, it disappears.
Here, I am working on a storyboard using Swift language, and device activity reports can be fetched only with SwiftUI. So, the problem is with it? Following the current code.
@State private var context: DeviceActivityReport.Context = .init(rawValue: "Daily Activity")
@State private var report: DeviceActivityReport?
@State private var filter = DeviceActivityFilter(
segment: .daily(
during: Calendar.current.dateInterval(
of: .day, for: .now
)!
)
// users: .all
// devices: .init([.iPhone, .iPad])
)
@State var isReload: Bool = false
var body: some View {
ZStack {
if isReload {
LoadingView(title: "Data is loading...")
} else if let report = report {
report
} else {
DeviceActivityReport(context, filter: filter)
}
}
.onAppear {
DispatchQueue.main.async {
report = DeviceActivityReport(context, filter: filter)
}
}
}
struct LoadingView: View {
var title: String = "Please wait..."
var body: some View {
HStack {
ProgressView(title)
.font(.system(size: 14, weight: .medium))
.progressViewStyle(.horizontal)
.tint(Color(.darkGray))
.padding(8)
}
.background(Color(.white))
.cornerRadius(8)
.clipped()
}
}
When I try to load the device activity report, it takes too long to load data. Is this because of development time? Will the problem be solved when the app is live on the App Store?
If it does not depend on the development profile, then please give me a solution for the fast data loading in the device activity report extension.
Thank you
Our app monitors device usage and applies a shield when the set time limit is reached. Multiple DeviceActivitySchedules can be present, each with different time limits. To display notifications at 50% of the total limit for each DeviceActivitySchedule, we set a warning time at half of the total time. However, we occasionally receive premature event callbacks.
For example, consider a schedule from 13:00 to 13:30 with a single event threshold at 10 minutes and a warning time of 5 minutes. The 'eventDidReachThreshold' callback is delivered prematurely, along with the 'eventWillReachThresholdWarning' callback, at 13:10.
Additionally, in some cases, when one DeviceActivitySchedule ends and the next begins immediately, DeviceActivityEvents registered for the new DeviceActivitySchedule are delivered prematurely along with the schedule start callback.
For example, consider there are two DeviceActivitySchedules from 12:00 to 13:00 and from 13:00 to 14:00, each with a limit of 10 minutes and a warning time of 5 minutes. When the first schedule ends and the next begins at 13:00, the 'eventDidReachThreshold' callbacks for the events registered in the second schedule are delivered prematurely, along with the 'intervalDidStart' callback.
I'm working with the Screen Time API in iOS and have successfully implemented the following:
Granted Screen Time Permission: The app asks for and obtains Screen Time permissions without any issues.
Blocked Specific Apps: Using FamilyActivitySelection, I can block access to certain apps.
Monitoring Device Activity: With DeviceActivityCenter().startMonitoring(), I’m able to successfully start monitoring.
DeviceActivityCenter().startMonitoring(.myActivity, during: schedule)
Now, I’m wondering if there’s a way to detect exactly which app the user opens, so I can fire an API from my own app based on that event.
Is this kind of real-time app usage detection possible with the Screen Time API? If so, how might it be implemented?
The data displayed about a child’s apps can be outdated (DeviceActivityReport), leading to misinformation for the user. When I access the “Screen Time” section (for child in the parent device) in the iPhone settings, I see there is an update functionality to force load the actual data.
I have tried various workarounds, such as attempting to force an update on the child’s device to call DeviceActivityReport and opening system settings, but none of these have been successful :(
How can I implement something similar? Is there a way to force update this data ?
I'd like to block the apps selected in FamilyActivityPicker individually when a certain threshold is met.
For example, let's say the threshold is 15 minutes, and I want to block both Photos and Freeform. If I spend 15 minutes on Photos, Photos should be blocked. Then, if I spend 15 minutes on Freeform, Freeform should also be blocked.
Currently, only Photos gets blocked after 15 minutes, but Freeform does not. How can I fix this problem so that each app is blocked individually when its respective 15-minute threshold is met?
Thank you in advance
File 1 :
class GlobalSelection {
static let shared = GlobalSelection()
var selection = FamilyActivitySelection()
private init() {}
}
extension DeviceActivityName{
static let daily = Self("daily")
}
@objc(DeviceActivityMonitorModule)
class DeviceActivityMonitorModule: NSObject {
private let store = ManagedSettingsStore()
@objc
func startMonitoring(_ limitInMinutes: Int) {
let schedule = DeviceActivitySchedule(
intervalStart: DateComponents(hour: 0, minute: 0),
intervalEnd: DateComponents(hour: 23, minute: 59),
repeats: true
)
let threshold = DateComponents(minute: limitInMinutes)
var events: [DeviceActivityEvent.Name: DeviceActivityEvent] = [:]
// Iterate over each selected application's token
for token in GlobalSelection.shared.selection.applicationTokens {
// Create a unique event name for each application
let eventName = DeviceActivityEvent.Name("dailyLimitEvent_\(token)")
// Create an event for this specific application
let event = DeviceActivityEvent(
applications: [token], // Single app token
threshold: threshold
)
// Add the event to the dictionary
events[eventName] = event
}
// Register the monitor with the activity name and schedule
do {
try DeviceActivityCenter().startMonitoring(.daily, during: schedule, events: events)
print("24/7 Monitoring started with time limit : \(limitInMinutes) m")
} catch {
print("Failed to start monitoring: \(error)")
}
}
@objc
static func requiresMainQueueSetup() -> Bool {
return true
}
}
FIle 2 :
class DeviceActivityMonitorExtension: DeviceActivityMonitor {
let store = ManagedSettingsStore()
var blockedApps: Set<ApplicationToken> = []
func scheduleNotification(with title: String) {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if granted {
let content = UNMutableNotificationContent()
content.title = "Notification" // Using the custom title here
content.body = title
content.sound = UNNotificationSound.default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false) // 5 seconds from now
let request = UNNotificationRequest(identifier: "MyNotification", content: content, trigger: trigger)
center.add(request) { error in
if let error = error {
print("Error scheduling notification: \(error)")
}
}
} else {
print("Permission denied. \(error?.localizedDescription ?? "")")
}
}
}
// Function to retrieve selected apps
func retrieveSelectedApps() -> FamilyActivitySelection? {
if let sharedDefaults = UserDefaults(suiteName: "group.timelimit.com.zerodistract") {
// Retrieve the encoded data
if let data = sharedDefaults.data(forKey: "selectedAppsTimeLimit") {
// Decode the data back into FamilyActivitySelection
let decoder = JSONDecoder()
if let selection = try? decoder.decode(FamilyActivitySelection.self, from: data) {
return selection
}
}
}
return nil // Return nil if there was an error
}
override func intervalDidStart(for activity: DeviceActivityName){
super.intervalDidStart(for: activity)
scheduleNotification(with: "Interval did start")
scheduleNotification(with: "\(retrieveSelectedApps())")
}
override func intervalDidEnd(for activity: DeviceActivityName) {
super.intervalDidEnd(for: activity)
}
override func eventDidReachThreshold(_ event: DeviceActivityEvent.Name, activity: DeviceActivityName) {
super.eventDidReachThreshold(event, activity: activity)
// Notify that the threshold is met
scheduleNotification(with: "Threshold met")
// Retrieve the selected apps
if let selectedApps = retrieveSelectedApps() {
// Extract the app token identifier from the event name
let appTokenIdentifier = event.rawValue.replacingOccurrences(of: "dailyLimitEvent_", with: "")
// Iterate over the selected application tokens
for appToken in selectedApps.applicationTokens {
// Convert the app token to a string representation (or use its debugDescription)
let tokenString = "\(appToken)"
// Check if the app token matches the token identifier in the event name
if tokenString == appTokenIdentifier {
blockedApps.insert(appToken)
// Block only the app associated with this event
store.shield.applications = blockedApps
scheduleNotification(with: "store.shield.applications = blockedApps is reached")
break
}
}
} else {
scheduleNotification(with: "No stored data for selectedAppsTimeLimit")
}
}
override func intervalWillStartWarning(for activity: DeviceActivityName) {
super.intervalWillStartWarning(for: activity)
// Handle the warning before the interval starts.
}
override func intervalWillEndWarning(for activity: DeviceActivityName) {
super.intervalWillEndWarning(for: activity)
// Handle the warning before the interval ends.
}
override func eventWillReachThresholdWarning(_ event: DeviceActivityEvent.Name, activity: DeviceActivityName) {
super.eventWillReachThresholdWarning(event, activity: activity)
// Handle the warning before the event reaches its threshold.
}
}
Hello, I'm currently facing some technical difficulties in implementing features related to application restrictions using the ScreenTime API.
In our app, we allow users to set up restrictions for specific apps and app categories, with scheduled times and days (for example, Mondays and Thursdays, from 2pm to 5pm). The blocking sessions must run independently and simultaneously, allowing different sets of applications to be restricted at different times. However, I ran into two main problems:
1. Applying restrictions in the DeviceActivityMonitor extension:
Although I can enable and disable restrictions, I haven't found an effective way to apply multiple FamilyActivitySelections directly in the DeviceActivityMonitor extension. The extension has to manage different blocking sessions independently, restricting different sets of applications and categories simultaneously or separately.
I would like to know if it is possible to transmit this list of selected applications via UserDefaults or CoreData to the extension in order to facilitate this integra
To better illustrate, here is a snippet of the code I am using:
import Foundation
import FamilyControls
import ManagedSettings
import DeviceActivity
class AppBlockManager: ObservableObject {
private let store = ManagedSettingsStore()
private let center = DeviceActivityCenter()
@Published var activitySelection: FamilyActivitySelection
private var activityName: DeviceActivityName
private var schedule: DeviceActivitySchedule
init(selection: FamilyActivitySelection, activityName: DeviceActivityName, schedule: DeviceActivitySchedule) {
self.activitySelection = selection
self.activityName = activityName
self.schedule = schedule
}
func startBlock() {
do {
try center.startMonitoring(activityName, during: schedule)
if let applications = activitySelection.applications.isEmpty ? nil : activitySelection.applicationTokens {
store.shield.applications = applications
}
if let categories = activitySelection.categories.isEmpty ? nil : activitySelection.categoryTokens {
store.shield.applicationCategories = ShieldSettings
.ActivityCategoryPolicy
.specific(categories)
store.shield.webDomainCategories = ShieldSettings
.ActivityCategoryPolicy
.specific(categories)
}
if let webDomains = activitySelection.webDomains.isEmpty ? nil : activitySelection.webDomainTokens {
store.shield.webDomains = webDomains
}
} catch {
print("Error starting monitoring: \(error)")
}
}
func stopBlock() {
store.shield.applications = nil
store.shield.webDomains = nil
store.shield.applicationCategories = nil
store.shield.webDomainCategories = nil
center.stopMonitoring([activityName])
}
}
Currently, this AppBlockManager is part of the main app target, not within the DeviceActivityMonitor extension, which is currently empty. With this configuration, I can only have one blocking session active at a time, and when it is deactivated, all restrictions are removed. I tried using different ManagedSettingsStore instances, each named individually, but without success.
2. Problems with scheduling restrictions:
Currently, when setting up scheduled monitoring via DeviceActivitySchedule, the restrictions are activated immediately, ignoring the specific times scheduled (e.g. starting at 2pm and ending at 5pm). I need the schedule to work correctly, applying the restrictions only during the defined periods.
Alternatively, I've considered running a background task that checks whether active sessions (up to a maximum of 3) should apply the restrictions at that time, but I'm still looking for a more suitable solution.
In view of these challenges, I would like some guidance on the following points:
What would be the best way to configure the DeviceActivityMonitor extension to receive and apply different FamilyActivitySelections, ensuring that the blocking sessions are independent and can run simultaneously?
Is there a recommended approach to ensure that restrictions scheduled via DeviceActivitySchedule are applied and removed according to the times and days defined by the user, ensuring that applications are restricted only during the scheduled periods?
Core Data not returning results in ShieldConfiguration Extension, but works fine in other extensions
Hi everyone,
I’m using Core Data in several extensions (DeviceActivityMonitor, ShieldAction, and ShieldConfiguration). It works perfectly in DeviceActivityMonitor and ShieldAction. I’m able to successfully fetch data and log the correct count using a fetch request.
However, when I try the same setup in the ShieldConfiguration extension, the fetch request always returns 0 results. The CoreData and App Group setup appears to be correct since the first two extensions fetch the expected data.
I’ve also previously tested storing the CoreData objects separately in a JSON-FIle using FileManager and it worked without issues—though I’d prefer not to handle manual encoding/decoding if possible.
The documentation mentions that the extension runs in a sandbox, restricting network requests or moving sensitive content. But shouldn’t reading data (from a shared App Group, for instance) still be possible within the sandbox, as it is the case with the Files, what is the difference there? In my case, I only need to read the data, as modifications can be handled via ShieldActionExtension.
Any help would be greatly appreciated!
We are currently experiencing the following weird issue with our iPhone app. As the title says, NSUserDefaults is losing our custom keys and values when phone is rebooted but not unlocked, and this is happening on a very specific scenario with ActivityKit.
Context:
We are using the NSUserDefaults in the app to store user data (e.g. username).
Issue: An error occurred with no permission to access cached messages after a restart.
Scenario: When receiving a Dynamic Island notification, if the phone is restarted, after unlocking the phone and tapping on the Dynamic Island to open the App, all cached information results in an error.
Reasons for the error:
After restarting the APP, the files are in a locked state. The Dynamic Island proactively invokes the App method. When executing the startup method and retrieving cached messages, an error occurs due to lack of permission.
All storage, including files, NSUserDefaults, Keychain, and Plist retrieval, results in errors.
The error message is as follows:
{
"errorCode": "-25308",
"errorDesc": "Error Domain=com.samsoffes.sskeychain Code=-25308 "(null)"",
"serviceName": "com.qunar.qunarclient8",
"account": "iid"
}
The data returned at this time is in a protected state, [UIApplication sharedApplication].isProtectedDataAvailable.
Any help or idea will be truly appreciated :)
Tl:dr What are some reasons my bundleIDs aren't showing up and does anyone have good resources to setup the screentime API/DeviceActivityMonitorExtension?
I'm working on an iOS app that uses the FamilyControls and DeviceActivity frameworks to monitor and restrict app usage. The app allows users to select apps and set usage limits. When a limit is reached, a DeviceActivityMonitorExtension should block the selected apps.
My App setup: Have a model that is called when users select apps to manage these app bundle IDs are then serialized and sent to the Device Monitor Extension via App Group so it can be used when the event threshold is reached. Cant use Application Tokens because they are not serielizable and cant be passed to the extension.
Problem: While testing, I’m unable to retrieve the bundleIdentifier and localizedDisplayName from the Application objects after selecting apps. Instead, these properties are nil or empty, preventing me from saving the bundle IDs to share with the extension via App Groups.
Assumptions: I suspect this issue is due to missing the com.apple.developer.screentime.api entitlement, which might be required to access these properties even during development. I've requested for the entitlement but its still under review.
Key Code Snippets:
Authorization Request:
class ScreenTimeManager: ObservableObject {
static let shared = ScreenTimeManager()
@Published var isAuthorized: Bool = false
func requestAuthorization() async {
do {
try await AuthorizationCenter.shared.requestAuthorization(for: .individual)
DispatchQueue.main.async {
self.isAuthorized = AuthorizationCenter.shared.authorizationStatus == .approved
print("Authorization status: \(AuthorizationCenter.shared.authorizationStatus)")
}
} catch {
DispatchQueue.main.async {
print("Authorization failed: \(error.localizedDescription)")
self.isAuthorized = false
}
}
}
}
Accessing bundleIdentifier:
print("addAppGroup() Called")
let managedApps = selection.applications.compactMap { application -> ManagedApp? in
guard let token = application.token else {
print("No token for application: \(application)")
return nil
}
let app = Application(token: token)
print("New Application instance: \(app)")
guard let bundleID = app.bundleIdentifier, !bundleID.isEmpty else {
print("Bundle identifier is empty or nil for application: \(app)")
return nil
}
let displayName = app.localizedName ?? "Unknown App"
print("Processing application with bundleIdentifier: '\(bundleID)' and displayName: '\(displayName)'")
return ManagedApp(
bundleIdentifier: bundleID,
applicationToken: token,
localizedDisplayName: displayName
)
}
if managedApps.isEmpty {
print("No managed apps created. Exiting addAppGroup().")
return
}
// Continue with creating DeviceActivityEvent...
}
Logs - Shows application token but never bundleID or LocalizedDisplayname
Application(bundleIdentifer: nil, token: Optional(128 byte <TOKEN_PRESENT>), localizedDisplayName: nil)
What I've Tried:
Ensured Screen Time is enabled on the device.
Verified App Group configuration in both app and extension.
Checked that authorization is being requested and the status is .approved.
Cleaned and rebuilt the project.
Questions:
Is the com.apple.developer.screentime.api entitlement required to access bundleIdentifier and localizedDisplayName when using .individual authorization?
Is there a way to access these properties without the entitlement, or am I missing a configuration step?
Has anyone faced a similar issue and found a solution?
Lastly, is there a good place for additional resources on the screentime API??