I am able to block apps using FamilyControl and Shield. Unblocking is also simple—just assign nil to store.shield.applications. However, I want to unblock them even when the app is not open.
Use case: Let's say the app allows users to create a session where a particular app is blocked for a specific duration. Once the session starts, the app should remain blocked, and as soon as the session time ends, it should automatically be unblocked.
Please help me with this. Thank you!
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
198 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
After reading Apple documentation (FamilyControls, DeviceActivity, ManagedSettings, ManagedSettingsUI, ScreenTime) and testing the API, I do not find a way to get the child's device apps on the parent device in order to block them or disable them for a certain time.
Is there a way of doing it?
Or can it only be done locally on the child device?
I'm at my wit's end here with an iOS app I'm developing. I've applied for the Family Controls entitlement, and while my extensions (like Device Monitor) have been accepted, the main target entitlement for my app still hasn't been approved.
Here's the timeline:
Extensions (Device Monitor etc.): Accepted about a month ago. Main App Entitlement: Still pending - it's been over 6 weeks now. I'm looking for:
Anyone who has gone through this process and can share how long it took for their main app entitlement to get approved after the extensions were. Any tips on what might speed up the process or what I might be doing wrong. Experiences with contacting Apple Developer Support regarding this issue.
If you've been through a similar ordeal or have any advice, I'd really appreciate it. Thanks for any help or insight you can offer!
I am developing a parental control app using Apple’s Screen Time API and FamilyControls Framework. My goal is to allow parents to remotely block apps on their child’s device from their own phone. Anyone have any idea how can i do that?
I am developing a parental control app using Apple’s Screen Time API and FamilyControls Framework. My goal is to allow parents to remotely block apps on their child’s device from their own phone. Does anyone have any idea how I can achieve that?
Hi there,
I am planning an app that requires use of the Family Controls Entitlement to access data on the user's screen time.
I understand that this has to be requested from Apple before it can be used in production.
I have found the following form to request approval, but it requires an App and bundle ID, which suggests that approval can only be requested after the app has been developed.
https://developer.apple.com/contact/request/family-controls-distribution
I'd like to avoid the situation where I spend a lot of time on developing the app, only to find out that the Family Controls Entitlement will not be granted for my use case.
Is there any way that I can request provisional pre-approval for my app? Perhaps based on an app description and some mockups? Or, at least some idea of whether my particular use case is likely to be approved?
Thanks.
I'm creating an app which gamifies Screen Time reduction. I'm running into an issue with apples Screen Time setting where the user can disable my apps "Screen Time access" and get around losing the game.
Is there a way to detect when this setting is disabled for my app? I've tried using AuthorizationCenter.shared.authorizationStatus but this didn't do the trick. Does anyone have an ideas?
I've successfully obtained Distribution entitlements for Family Controls. However, this seems to only apply to the main target/identifier and not the extensions, like DeviceActivityMonitor, ShieldConfigurationDataSource, or ShieldActionExtension.
Did I perhaps fill out the form with the wrong bundle ID? If I go to "Certificates, Identifiers & Profiles", my main identifier for the app has the Distribution entitlement. But the extensions and the wildcard don't. This means that trying to create an archive results in the following two errors, each repeated twice:
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)
Note that my entitlement files are set up correctly.
Do I need to fill out the form with a wildcard instead? Or am I doing something wrong? Thank you.
I'm trying to build an app with a DeviceActivityMonitor extension that executes some code after 15 minutes. I can confirm that the extension is set up correctly and that intervalDidStart is executed, but for some reason the intervalDidEnd method never gets called. What I'm doing in both is just registering a local notification.
class DeviceActivityMonitorExtension: DeviceActivityMonitor {
let store = ManagedSettingsStore()
override func intervalDidStart(for activity: DeviceActivityName) {
createPushNotification(
title: "Session activated!",
body: ""
)
super.intervalDidStart(for: activity)
}
override func intervalDidEnd(for activity: DeviceActivityName) {
createPushNotification(
title: "Session ended",
body: ""
)
super.intervalDidEnd(for: activity)
}
private func createPushNotification(title: String, body: String) {
let content = UNMutableNotificationContent()
content.title = title
content.body = body
// Configure the recurring date.
var dateComponents = Calendar.current.dateComponents([.era, .year, .month, .day, .hour, .minute, .second], from: Date().addingTimeInterval(1.0))
dateComponents.calendar = Calendar.current
dateComponents.timeZone = TimeZone.current
// Create the trigger as a repeating event.
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
let uuidString = UUID().uuidString
let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger)
// Schedule the request with the system.
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.add(request)
}
}
And this is the method that is starting the monitoring session:
@objc public static func startSession() -> String? {
// Calculate start and end times
let center = DeviceActivityCenter()
let minutes = 15
let startDate = Date().addingTimeInterval(1)
guard let endDate = Calendar.current.date(byAdding: .minute, value: minutes, to: startDate) else {
return "Failed to create end date?"
}
// Create date components and explicitly set the calendar and timeZone
let startComponents = Calendar.current.dateComponents([.era, .year, .month, .day, .hour, .minute, .second], from: startDate)
let endComponents = Calendar.current.dateComponents([.era, .year, .month, .day, .hour, .minute, .second], from: endDate)
// Create schedule
let schedule = DeviceActivitySchedule(
intervalStart: startComponents,
intervalEnd: endComponents,
repeats: false
)
print("Now", Date())
print("Start", startDate, startComponents)
print("End", endDate, endComponents)
print(schedule.nextInterval)
do {
// Use a consistent activity name for our simple implementation
let activity = DeviceActivityName("SimpleSession")
try center.startMonitoring(activity, during: schedule)
return nil
} catch {
return "Failed to start monitoring: \(error)"
}
}
I can confirm my dates & date components make sense with the 4 print statements. Here is the output:
Now 2025-02-12 04:21:32 +0000
Start 2025-02-12 04:21:33 +0000 era: 1 year: 2025 month: 2 day: 11 hour: 20 minute: 21 second: 33 isLeapMonth: false
End 2025-02-12 04:36:33 +0000 era: 1 year: 2025 month: 2 day: 11 hour: 20 minute: 36 second: 33 isLeapMonth: false
Optional(2025-02-12 04:21:33 +0000 to 2025-02-12 04:36:33 +0000)
I get the Session activated! notification but never get the Session ended notification. Half an hour later, I've tried debugging the DeviceActivityCenter by printing out the activities property and can see that it is still there. When I try to print out the nextInterval property on the schedule object i get from calling center.schedule(for:), it returns nil.
I'm running this on an iPhone 8 testing device with developer mode enabled. It has iOS 16.7.10. I'm totally lost as to how to get this to work.
I am developing an app that can help users disable selected apps at a specified time, so that users can get away from their phones and enjoy real life.
Here is my data structure:
extension ActivityModel {
@NSManaged public var id: UUID
@NSManaged public var name: String
@NSManaged public var weeks: Data
@NSManaged public var weekDates: Data
@NSManaged public var appTokens: Data
}
Among them, weeks is of [Bool] type, indicating which weeks from Sunday to Saturday are effective; weekDates is of [[Date,Date]] type, indicating the effective time period; appTokens is of Set type, indicating the selected apps。
At the beginning, I will open a main monitor:
let deviceActivityCenter = DeviceActivityCenter()
do{
try deviceActivityCenter.startMonitoring(
DeviceActivityName(activityModel.id),
during: DeviceActivitySchedule(
intervalStart: DateComponents(hour: 0,minute: 0,second: 0),
intervalEnd: DateComponents(hour: 23,minute: 59,second: 59),
repeats: true
)
)
}catch {
return false
}
Since the time range may be different every day, I will start the sub-monitoring of the day every time the main monitoring starts:
override func intervalDidStart(for activity: DeviceActivityName) {
super.intervalDidStart(for: activity)
if activity.rawValue.hasPrefix("Sub-") {
ActivityModelManager.disableApps(
Tools.getUUIDFromString(activity.rawValue)
)
return
}
let weekIndex = Calendar.current.component(.weekday, from: .now)
let weeks = ActivityModelManager.getWeeks(activity.rawValue)
if weeks[weekIndex] {
let weekDates =
ActivityModelManager.getWeekDates(activity.rawValue)
let deviceActivityCenter = DeviceActivityCenter()
do{
try deviceActivityCenter.startMonitoring(
DeviceActivityName("Sub-" + activityModel.id),
during: DeviceActivitySchedule(
intervalStart: getHourAndMinute(weekDates[weekIndex][0]),
intervalEnd: getHourAndMinute(weekDates[weekIndex][1]),
repeats: false
)
)
}catch {
return
}
}esle {
return
}
}
I will judge whether it is main monitoring or sub monitoring based on the different activity names.
When the sub-monitor starts, I will get the bound application and then disable it:
static func disableApps(_ id : UUID){
let appTokens = ActivityModelManager.getLimitAppById(id)
let name = ManagedSettingsStore.Name(id.uuidString)
let store = ManagedSettingsStore(named: name)
store.shield.applications = appTokens
return
}
When the child monitoring is finished, I resume the application:
static func enableApps(_ id : UUID){
let name = ManagedSettingsStore.Name(id.uuidString)
let store = ManagedSettingsStore(named: name)
store.shield.applications = []
}
The above is my code logic.
When using DeviceActivityMonitorExtension, I found the following problems:
intervalDidStart may be called multiple times, resulting in several sub-monitors being started.
After a period of time, the monitoring is turned off.
The static methods enableApps and disableApps are sometimes not called
The DeviceActivityReport view does not render immediately when added to the view hierarchy. Instead, it requires repeated navigation to the screen hosting the DeviceActivityReport view for it to appear.
Furthermore, there is no programmatic way to determine whether the view is being rendered for the user, leading to an inconsistent and often poor user experience.
I've created a sample project that demonstrates the issue.
As discussed and acknowledged here, there is a known bug with the FamilyActivityPicker. When a user expands a category that contains enough tokens to exceed the 50mb memory limit, the FamilyActivityPicker crashes.
This happens quite frequently for heavy Safari users. An apple engineer mentioned on this thread that WebDomains shown in the picker are present based on the last 30 days of usage data as surfaced by WebKit.
Is there any way a user can clear these WebDomains? Either programatically through our app or any other process we can guide them to as a workaround while this issue is getting fixed?
So what's the point of being able to block unto 50 apps per ManagedSettingStore via store.application.blockedApplications (which works fine) until removing the blocked apps or clearing the store. Where the following occurs
if you have a social networking group with more than 9 apps only 9 apps will go back into the group and all the others will go onto the springboard all jumbled
if you end up with an empty group then tap into the group, it is removed then during the reset all apps are placed back on to the springboard
Is it possible to change the title "Choose Activites" and also remove the search on the FamilyActivityPicker?
Hello Apple development team, I have developed an App for screen time management, which mainly uses ScreenTimeAPI. Users can set certain Apps to be disabled during a certain period of time.
After the App is released, users often report that the settings do not take effect as expected. I have seen many developers on the forum reporting that the DeviceActivityMonitor extension sometimes does not trigger callbacks. Based on this background, I have the following questions:
Is it a known problem that the DeviceActivityMonitor extension sometimes does not trigger callbacks? If so, are there any means to avoid or reduce the probability of occurrence?
In addition to being killed by the system when the running memory exceeds (I just called some ScreenTimeAPI and accessed UserDefaults in the extension, which should not exceed the running memory), under what other circumstances will the DeviceActivityMonitor extension be killed by the system? Will it automatically recover after being killed? Will some callbacks be called when killing?
Does ManagedSettingsStore have a life cycle? How do you avoid conflicts when configuring the underlying operating mechanism of multiple stores?
This is a random problem. I have never encountered it during development and debugging, but users often report it.
thanks
Hi team. I am working on an app that uses the Screen Time API. I got access to the family controls (distribution) capability through the request process for my main app. I added a DeviceActivityReport extension in XCode, but haven't been able to get the extension to show up on the screen. I noticed that the extension only has the development version of the family controls capability available. Is this the source of my errors? I was able to get the screen time displayed in a test app I built where both the main app and extension used the development version of the capability, which led me to believe that discrepancy could be the issue.
Let me know if there is anything I can provide to help in the debugging process. I didn't send a minimal example in this request due to the fact that I would have to remove most of my functionality to create a "minimal" example (since the signing is only for my main app), but I can do that if needed. Thanks! I looked through the logs in the console for the phone (I'm testing on a real iPhone 13 Pro Max), but didn't see anything that popped out after looking (not exactly sure what to look for though).
STEPS TO REPRODUCE:
Create an app with the Family Controls, Distribution capability. Then create the DeviceActivityReport with the Family Control, Development capability. Attempt to see the DeviceActivityReport in the main app.
NOTE: I was successfully able to create a minimal test app completely separately that used the Development versions of the capabilities for both with the exact same extension code. That's why I think the issue could be due to the capability version discrepancy.
When we request auth from the AuthorizationCenter, it seems that we're only really able to allow users to control the apps on the parent's phone. Is there a way to allow us to let parents manage apps on the kid's device directly through our parent app?
For context, we have 2 different apps, one for the parent and one for the child. The child is able to purchase screen time and the parent can redeem them (activate those minutes) from their end.
After transferring the App ownership to a different account, if you update the app on iOS, two identical apps will show up in Settings > Screen Time. Users can't control the blocking settings from before the update - the only fix is to restart the phone.
After the next execution of manageStore.shield.applications, users still can't manually disable the restrictions - their only option is to uninstall and reinstall the app. I believe this is related to how Screen Time API's authentication works - it's not just tied to the app's bundle ID, but also linked to the developer account's organization ID. Any suggestions for a clean solution that would allow smooth app updates after the transfer without running into these issues?
Hey, I’m having some issues with DeviceActivitySchedule and DeviceActivityMonitor. I want to create a schedule that blocks apps (by family control) when it starts. However, even when the schedule is supposed to start on this iPhone, nothing happens, and no logs are being recorded
main target:
// TestView_.swift
// Sloth
//
// Created by on 11/01/2025.
//
import SwiftUI
import DeviceActivity
import FamilyControls
import ManagedSettings
struct TestView_: View {
var body: some View {
VStack(spacing: 20) {
Text("Test DeviceActivityMonitor")
.font(.title)
Button("Start test mon") {
let now = Date()
let start = Calendar.current.date(byAdding: .minute, value: 2, to: now)!
let end = Calendar.current.date(byAdding: .minute, value: 20, to: now)!
print("thd")
DeviceScheduleTester().scheduleTestActivity(startDate: start, endDate: end)
}
}
.padding()
}
}
extension DeviceActivityName {
static let daily = DeviceActivityName("daily")
}
DeviceActivityMonitor:
class DeviceScheduleTester {
private let center = DeviceActivityCenter()
func scheduleTestActivity(startDate: Date, endDate: Date) {
let calendar = Calendar.current
let startComponents = calendar.dateComponents([.hour, .minute], from: startDate)
let endComponents = calendar.dateComponents([.hour, .minute], from: endDate)
// Tworzymy schedule
let schedule = DeviceActivitySchedule(
intervalStart: startComponents,
intervalEnd: endComponents,
repeats: true
)
do {
try center.startMonitoring(.daily, during:schedule)
print("startMonit /(\(schedule))")
} catch {
print("ghfgh")
}
}
}
struct TestView__Previews: PreviewProvider {
static var previews: some View {
TestView_()
}
}
DeviceActivityMonitor target:
// BlockingAppsMonitorExtension
//
// Created by on 10/01/2025.
import DeviceActivity
import FamilyControls
import ManagedSettings
import os
let logger = Logger()
public class BlockingAppsMonitor: DeviceActivityMonitor {
private let store = ManagedSettingsStore()
public override func intervalDidStart(for activity: DeviceActivityName) {
super.intervalDidStart(for: activity)
print("Rozpoczęcie interwału blokowania \(activity.rawValue)")
logger.info("intervalDidStart")
startBlocking()
}
public override func intervalDidEnd(for activity: DeviceActivityName) {
super.intervalDidEnd(for: activity)
print("Zakończenie interwału blokowania \(activity.rawValue)")
logger.info("intervalDidend")
stopBlocking()
}
@discardableResult
private func startBlocking() -> Int {
print("number of unique apps")
return 51
store.shield.applicationCategories = .all()
// return exceptions.count
}
private func stopBlocking() {
store.shield.applicationCategories = nil
store.shield.applications = nil
}
}
INB4:
In both files are added family controls
Secent file is added in DeviceActivityMonitor target.
Apple answer please?
I'm using ShieldActionExtention to make a HTTP request to a server when a user selects one of the buttons on their app shield. The apps are shielded, but nothing happens when I press one of the shield buttons. There is no message on the server signaling an HTTP request and nothing is printed to the XCode console while in debug mode.
Here is my code for my Shield Action Extention
// ShieldActionExtension.swift
// ShieldAction
//
//
import Foundation
import UIKit
import SwiftUI
import ManagedSettings
// Override the functions below to customize the shield actions used in various situations.
// The system provides a default response for any functions that your subclass doesn't override.
// Make sure that your class name matches the NSExtensionPrincipalClass in your Info.plist.
class ShieldActionExtension: ShieldActionDelegate {
override func handle(action: ShieldAction, for application: ApplicationToken, completionHandler: @escaping (ShieldActionResponse) -> Void) {
print(action)
let deviceID = UserDefaults.standard.string(forKey: UserDefaultKeys.userID.rawValue)!
Task{
do{
print("sending to server")
try await PlayerLosesGame(playerID: deviceID)
completionHandler(.close)
} catch {
print("error occured on the shield")
completionHandler(.none)
}
}
}
override func handle(action: ShieldAction, for webDomain: WebDomainToken, completionHandler: @escaping (ShieldActionResponse) -> Void) {
print(action)
let deviceID = UserDefaults.standard.string(forKey: UserDefaultKeys.userID.rawValue)!
Task{
do{
print("sending to server")
try await PlayerLosesGame(playerID: deviceID)
completionHandler(.close)
} catch {
print("error occured on the shield")
completionHandler(.none)
}
}
}
override func handle(action: ShieldAction, for category: ActivityCategoryToken, completionHandler: @escaping (ShieldActionResponse) -> Void) {
print(action)
let deviceID = UserDefaults.standard.string(forKey: UserDefaultKeys.userID.rawValue)!
Task{
do{
print("sending to server")
try await PlayerLosesGame(playerID: deviceID)
completionHandler(.close)
} catch {
print("error occured on the shield")
completionHandler(.none)
}
}
//completionHandler(.close)
}
func PlayerLosesGame(playerID: String) async throws{
let url = URL(string: ServerConnection.GetWebsite() + "game/find?playerID="+playerID)!
var request = URLRequest(url: url)
request.httpMethod = "GET"
print("trying this out")
let (data, _) = try await URLSession.shared.data(for: request)
}
}
I believe all my targets are set up correctly and should be working. Why is nothing happening?