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.
Family Controls
RSS for tagPrevent access to the Screen Time API without guardian approval and provide opaque tokens that represent apps and websites.
Posts under Family Controls tag
181 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Hi all,
For context, the Family Controls entitlement request (for the Personal Device Management category/individual use case) includes the question:
Will your app share device or usage data beyond the individual for the individual use case, or Family Sharing for the parent/guardian use case, including through means such as screenshots, screen recordings, or server logging?
I'm looking for clarification on how to interpret this. I originally answered Yes and was rejected, then later answered No and was accepted.
Ideally, I would like my screen time management app to allow users to opt-in to social features. One simple example is opting into a leaderboard with your friends for who has the lowest screen time.
If the user installed this app for themself and chooses to share this basic data with their friends, it sounds like an ethical and unproblematic feature but I suppose storing that data would fall under "server logging"?
If anyone has any experience with this, I would appreciate a more explicit description of the requirement above. Is what I described allowed?
Thanks for reading!
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?
/Users/varunashokbhaisidpara/Desktop/Screenshot 2024-11-15 at 5.43.44 PM.png
This error occurs When I try to upload an app on the test flight or App Store. I checked "Automatically manage signing" in the main target. and i have added extensions DeviceActivityMonitorExtension, ShieldConfigurationExtension, ShieldActionExtension, DeviceActivityReport. and all have selected "Automatically manage signing" in the target > signing and capability.
pls provide me with an exact solution set by step.
Thank you
Hello community:
Is there any way to change the button color of the screen time permission to match app style?
When I upgraded to iOS 18 beta, I don’t recall setting a screen time passcode, however one is set. I am locked out from changing the passcode. I have clicked “forgot passcode” down at bottom but it doesn’t do anything. I am the only user on this phone. I am currently running iOS iOS 18.2 (22C5125e). Please advise, as screen time is locked and cannot be viewed nor change passcode
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 ?
Hi, so, we grabbed a couple of nice new watches the other week (Ultra for dad, SE for teenage son). Mostly cool and working together (calls, messages, maps, walkie talkies, etc, etc). All good.
But then son said, "Dad, why can't I see my sleep stuff like you can..?". He was right, it wasn't working. Looking around a bit, it turns out that there are a bunch of things that are turned off or not available when pairing a kids watch with dad's phone.
From the Apple page: "The following features and apps are not available: Medications, respiratory rate, irregular heart rhythm notifications, ECG, AF History, Cycle Tracking, Sleep, Wrist Temperature, Blood Oxygen, Walking Steadiness, Audiobooks, Remote, News, Shortcuts and the double-tap gesture".
Now dev-me reacts to this situation with: "Ok, so let's just build a little standalone sleep app for son's watch. There must be lots of parents out there who would like the same thing". And there are also a bunch of other "family sharing" enabled apps that when you try and use them on kid's phone, say "iPhone requireed", i.e, they don't apparently work with just a watch hooked up to mum or dad's phone.
So before I dive into that kind of project, which seems like an obvious fix path from a dev and a parents' point of view: does anybody know if this from Apple's point of view is a hardware, a software or a legal/age limitation? What's the basic framework/dev/design issue here?
Is it something on the device(s) that prevents sleep data from even being collected on family/kids paired watches? (Therefore don't bother trying to build an app); I assume not becauses it's just a normal SE used by a kid; or
Is it "just" that Apple hasn't wanted to make that available without a kids iPhone too (Therefore you could certainly build a standalone app to do what Apple hasn't wanted to do); or
Netiher 1 nor 2, but Apple won't even allow Sleep data collection for kids for some legal/health data reason (Therefore don't bother trying to build the app).
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.
}
}
Hi guys, I’m writing an app that integrates Screen Time API more specifically one that has only DeviceActivityReport.
The app runs as expected but I have an issue when pushing it to AppStoreConnect:
Provisioning profile failed qualification: Profile doesn't support Family Controls (Development).
Provisioning profile failed qualification: Profile doesn't include the com.apple.developer.family-controls entitlement.
Provisioning profile failed qualification: Profile doesn't support Family Controls (Development).
Provisioning profile failed qualification: Profile doesn't include the com.apple.developer.family-controls entitlement.
My app has Family Controls (Development) set in Capabilities, entitlement file, provisioning profile and distribution certificates.
I have already requested Apple for the entitlement key for Family Controls (I’m waiting for them to reply).
So, I have few questions now:
Can I distribute a build for internal testers via TestFlight without waiting for Apple’s reply to my request?
Do I need to wait Apple’s reply to distribute the app to internal tester under Family Controls (Development) ?
Has anybody had the same issue and solved it without waiting for Apple to approve the request during development (no distribution)?
I have done a deep search about this topic but it is still not clear to me if an internal build could be distributed under Family Controls (Development) or if it is mandatory to wait for the distribution one
I need some assistance with the Screen Time API’s DeviceActivityReport extension. I know the extension is sandboxed but I need the data inside my app. Jomo is currently doing this so it’s not impossible. I see they’re saying it’s an estimate which is about 5 - 10 off of the actual screen time, but how are they doing this?
Any attempt to store the screen time data inside some sort of database or UserDefaults always fails of course due to the sandbox.
Any advice would be greatly appreciated!
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?
ActivityCategoryPolicy.all(except: allowList) blocks all apps, even the ones that should be exempted
Hello fellow Screen Time developers!
I have bee experimenting with the ActivityCategoryPolicy.all(except: allowList) API of the ManagedSettings framework in order to provide a digital detox feature to my app, where ALL apps would be blocked, except some important ones (phone, messages, maps).
The apps to be exempted can be configured by the user via the FamilyActivitySelection().
However, I am experiencing a strange bug, where all apps are restricted / blocked, even the ones that are set to be exempted and are part of the allowList Set.
Instead of not restricting allowed apps at all, these apps are restricted with a generic shield (screenshot below) – a concrete shield is not requested for them from my ShieldConfigurationDataSource.
I have also filed a radar under this number, attached is a minimalistic sample project and video that shows how to reproduce this: FB15500605
(no response so far)
I have also seen other people report on this issue, but couldn't find any useful information there:
https://forums.developer.apple.com/forums/thread/750988
https://forums.developer.apple.com/forums/thread/762287
Thanks a lot for your help, and have a nice day!
Hello,
I think it is quite a common use-case to open the parent app that owns the ShieldActionDelegate when the user selects an action in the Shield.
There are only three options available that we can do in response to an action:
ShieldActionResponse.none
ShieldActionResponse.close
ShieldActionResponse.defer
It would be great if this new one would be added as well:
ShieldActionResponse.openParentApp
While finding a workaround for now, the problem is that the ShieldActionDelegate is not a normal app extension. That means, normal tricks do not work to open the parent app from here.
For example, UIApplication.shared.open(url) does not work because we can’t access UIApplication from the ShieldActionDelegate unfortunately.
NSExtensionContext is also not available in the ShieldActionDelegate unfortunately, so that’s also not possible.
There are apps however, that managed to find a workaround, in my research I stumbled across these two:
https://apps.apple.com/de/app/applocker-passcode-lock-apps/id1132845904?l=en-GB
https://apps.apple.com/us/app/app-lock/id6448239603
Please find a screen recording (gif) attached.
Their workaround is 100% what I’m looking for, so there MUST be a way to do so that is compliant with the App Store guidelines (after all, the apps are available on the App Store!).
I had documented my feature request more than 2 years ago in this radar as well: FB10393561
Hello fellow Screen Time Fans!
I am encountering a strange problem since I started working with the Screen Time framework, and I don’t know what I’m doing wrong:
Imagine the app has two ManagedSettingsStores:
one to block apps during work hours (let’s say from 9am to 5pm) and one to block apps in the evening (let’s say from 5:30pm till midnight).
Imagine, the user has blocked Instagram in both.
When the user has Instagram open at 4:59pm it shows the Block during Work Hours Shield (so far, so good).
At 5pm, the shield is removed, and the user can use Instagram.
Then, at 5:30 the a shield is activated again: this time, the Instagram token is added to the evening store.
However, there is no new ShieldConfiguration requested from the ShieldConfigurationDataSource.
Instead, the previous shield from the work hour block is re-used and shown.
To me, it appears that the Framework does not request new shields, when the token is moved from one store to another while the app remains in foreground.
The Shield is only re-rendered when the user closes the shielded app and re-opens it.
This is really confusing behavior and I would like to fix it.
Did anyone here encounter something similar, and has a suggestion or workaround?
My feedback is also documented in FB14237883.
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!
Even on iOS 18 (16-17 also repo) we are seeing a crash on the FamilyActivityPicker when users tap on "Other" or just at random times. We see the follow debug message but no other way of identifying the issue in code.
[u 3C8AF272-DC4E-55C4-B8C6-34826D2BEB5B:m (null)] [com.apple.FamilyControls.ActivityPickerExtension(1150.1)] Connection to plugin invalidated while in use.
Even with the most basic implementation of FamilyActivityPicker (example below) we can repro this crash consistently.
Big applications (think Opal) see the same issue but it seems like they see it less, and are able to intercept the disconnect in order to show an error to the user. My two questions are
How can we intercept this crash/disconnect in order to alert our user and restart the experience?
Is this EVER gonna get fixed properly?
Usage Example:
var body: some View {
NavigationView {
ZStack {
familyPickerErrorView
.opacity(isHidden ? 0 : 1)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
isHidden = false
}
}
}
VStack {
Color.clear
.frame(height: 1)
.background(Color(UIColor.systemBackground))
FamilyActivityPicker(
headerText: "Select Apps To Be Blocked (Maximum of 50)",
footerText: "Want to block Safari? Check our FAQs",
selection: $familySelection)
.ignoresSafeArea(.all)
}
}
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: {
isPresented = false
}) {
Text("Cancel")
.foregroundColor(.black)
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
isPresented = false
}) {
Text("Done")
}
}
}
.navigationBarTitleDisplayMode(.inline)
.alert(isPresented: $showAlert) {
Alert(title: Text("Family Activity Picker Issue"), message: Text(alertMessage), dismissButton: .default(Text("OK")))
}
.onAppear {
isPresented = true
}
}
I am developing an app using DeviceActivity.
let schedule = DeviceActivitySchedule(
intervalStart: DateComponents(hour : 0, minute : 0, second: 1),
intervalEnd: DateComponents(hour : 23, minute : 59, second: 59),
repeats: true
)
I found that on the second day, intervalDidStart(for: DeviceActivityName) gets called multiple times.
I also tried
let schedule = DeviceActivitySchedule(
intervalStart: DateComponents(hour : 0, minute : 0, second: 1),
intervalEnd: DateComponents(hour : 23, minute : 59, second: 59),
repeats: false
)
and started monitoring for the next day in the intervalDidEnd(for: DeviceActivityName) method.
but , intervalDidStart(for: DeviceActivityName) still gets called multiple times.
How should I resolve this issue?
Hi, I noticed that all my apps are sometimes blocked even if I don’t have set up screen time for them. How can I fix it?
I use a parental control app and it should block apps only if I enable child mode, but after the iOS 18 update, it blocks apps even when I am still in parent mode.
But only until the update to iOS 18.0.1 came along just a few weeks later. EVERY TIME there's an update it breaks family share and screen time. EVERY TIME single time! And to fix, you must have the kid's phone in hand (easier for parents with little kids, not so easy with teens, also not easy for kids whose parents are split up and kid spends one week with you and one week with other parent). Then you have to make sure they are both on the same iOS version, then you have to log into iCloud and set up family sharing all over again. THIS SUCKS. Apple you can do better.