I am a macOS software developer using Xcode and Objective-C. I have a wallpaper app on the App Store, and recently, a few users have asked if the app can be excluded from the ‘Screen Time’ statistics. Is there a way to allow users to choose whether or not the app should appear in the ‘Screen Time’ tracking? I have already set LSUIElement to YES in the info.plist, but the app is still being tracked in ‘Screen Time.
Screen Time
RSS for tagShare and manage web-usage data, and observe changes made to Screen Time settings by a parent or guardian.
Posts under Screen Time tag
170 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
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!
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?
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'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?
Is it possible to change the title "Choose Activites" and also remove the search on the FamilyActivityPicker?
I'm building an app that uses the Screen Time API and DeviceActivityMonitoring Framework. It works when I run the simulator build on iPhone 16 but when I try to launch it on my own iPhone, I get these errors.
Provisioning profile "iOS Team Provisioning Profile: Kanso-
Digital-Wellness.Kanso-v2" doesn't include the com.apple.developer.device-activity.monitoring entitlement.
KansoMonitorExtension 1 issue
x Provisioning profile "iOS Team Provisioning Profile: Kanso-Digital-Wellness.Kanso-v2.KansoMonitorExtension" doesn't include the com.apple.developer.device-activity.monitoring en...
Read something online that said a reboot would fix this, but I tried and no luck. Any ideas?
I'm not very technical, so would pay someone to fix this for me :)
My screen time report says that I’m on Google an average of 24 hrs and 4 minutes a day. How is this possible? I can’t be on Google more than 10 minutes a day let alone 24 hrs. I think it’s probably tracking whatever Google does when I’m not in the app or on the cite as part of screen time, but I can’t get it to stop. Are other people having this issue? How do I stop this so I can see my real screen time?
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?
Hello,
In one of our apps we use DeviceActivityReportExtension to show the user how much screen time is remaining. The calculation is working as expected, but we have noticed that the labels in our ScreenTimeBriefReport are not localized to the device language.
Example:
Device with language set to Swedish
App fully translated to English and Swedish
Result:
All labels in app are using the Swedish translations, except the strings in our ScreenTimeBriefReport instance. These labels are using the English localization. I've verified it's using the English localization from our Localizable.xcstrings file.
I tried logging device language from our ScreenTimeBriefReport instance, but I could not see anything in Console.app. I guess this is intentional so no sensitive user information can be extracted.
Is this a known feature or bug? If it's the latter, is there a known workaround?
Sincerely,
César
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?
Am showing daily screen-time of a user in my app in Device Activity Report Extension. The only way to get that is to sum up all the activityDuration of apps/categories/domains. But it differs a lot from phone's settings screen-time, why?
I have debugged in details and counted manually the time spent on each app and it turned out that the calculation is appearing correctly in my app but Phone settings showing quite less time on top (Day).
STEPS TO REPRODUCE
Install the application “Dynamic-Lyrics" develop by me, which the bundle ID is com.bing.lyrics
(https://apps.apple.com/us/app/id6476125287)
Use this APP for a period of time
Go to Settings - Screen Time - See All App & Website Activity
I found that the name and icon displayed in Screen Time are incorrect.
The expectation is: “Dynamic-Lyrics", but the actual display is "com.microsoft.bing"
The guess is that the bundle ID contains the characters com.bing (bing is my name) and is incorrectly recognized as microsoft’s app.
After setting up all permissions, family members not showing up on the device list
I have noticed that when I select the app token of the youtube app to be blocked via a ManagedSettingsStore, the youtube website is blocked as well (which is a good and intended behavior IMO).
But how do I know in the ShieldConfigurationDataSource’s override func configuration(shielding webDomain: WebDomain, in category: ActivityCategory) -> ShieldConfiguration to which ManagedSettingsStore the WebDomain’s token is related?
We use different ManagedSettingsStores for different block purposes, which differentiate in their ShieldConfiguration.
Thanks a lot and have a great day!