I have two repeating DeviceActivitySchedules running in my app and noticed a super strange behavior:
whenever I modify Screen Time API permission settings (granting an additional app permission, or removing permission for a different app (completely different unrelated screen time apps from App Store)) all my DeviceActivitySchedules are cancelled and the intervalDidEnd callbacks are called
my permission is not modified in this scenario
this is a super strange and unexpected behavior
is anyone able to reproduce this?
for me this happens 100% of the times I do this
any known workarounds?
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
185 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
How can I count the number of pickups for a certain application?
The docs say that there is a struct ApplicationActivity within the DeviceActivityData:
https://developer.apple.com/documentation/deviceactivity/deviceactivitydata
Using this code I receive a warning in XCode:
Value of type 'DeviceActivityData' has no member 'applicationActivity'
struct ApplicationPickupCountReport: DeviceActivityReportScene {
// Define which context your scene will represent.
let context: DeviceActivityReport.Context = .applicationPickupCount
// Define the custom configuration and the resulting view for this report.
let content: (String) -> ApplicationPickupCountView
func makeConfiguration(representing data:
DeviceActivityResults<DeviceActivityData>) async -> String {
var totalPickups = 0
let totalPickups = await data.flatMap { $0.applicationActivity }.reduce(0, {
$0 + $1.numberOfPickups
})
return "\(totalPickups)"
}
}
Hello,
I am using a DeviceActivityEvent to limit access to an app after the user has spent x minutes on it.
Sometimes it can happen that a DeviceActivitySchedule spans from 11:55pm - 12:25am (just an example).
In these cases, I have noticed, the DeviceActivityMonitorExtension’s eventDidReachThreshold is not called after the user has reached the threshold time interval in the observed app.
I assume this is because we have transitioned over midnight and something weird happens to the DeviceActivitySchedule.
I’ve tried adding the day components to the DeviceActivitySchedule as well, but then it fails completely.
Any advice how to handle this?
I could, of course, create two separate DeviceActivitySchedules: one for before midnight, and one for after. But depending on the user’s real app usage that could lead to slightly different (and unexpected) behavior.
If we want to un-shield category/application for particular schedule, then what is the mechanism for this, as for now we are using
ManagedSettingsStore().shield.applications = nil and ManagedSettingsStore().shield.applicationCategories = nil, which is actually un-shielding all category/applications without considering the selection of category/application for that schedule. What is the best approach to handle multiple schedules for shielding of apps?
How can I add an X as close button on the FamilyActivityPicker Toolbar?
Desired effect see image
Update 1: Seems like app limits do work, but they don't account for time spent already in the day (only counts it since it first was scheduled? - but just guessing). This seems to be the change in behavior that is making it seem like broken.
I have an app that uses individual FamilyControls / Screen Time authorization. I am still investigating but reporting ASAP. Will add feedback and more later.
I am not sure what's going on, but so far:
App limits generally work fine throughout the day(?)
At around 9 PM they stop working if I clear ManagedSettingStore/DeviceActivityCenter (it might be unrelated to the time, but just clearing, but this is just what I know so far)
I tried setting app limits from Apple's Setting app (so I could see if it's not just my app, and that system could have issues) and it crashes. The whole Settings app freezes and then crashes. (edit: after some freezing, including in the app picker, I was able to set a limit and it worked...did notice some odd behavior where 1 minute break did not work, but selecting 15 minute break did work)
going to also update: FB13688616
It's no secret, the Screen Time/Family Controls API is incredibly challenging to figure out.
If you want to talk with other people building on the API, join the discord here: https://discord.gg/sjxhKWgry4
I want to connect with other people building so I made a discord where we can discuss challenges, updates & questions in more depth.
Hey,
is there a possibility to dynamically decrease the font size of
Label(applicationToken).labelStyle(.titleOnly) to avoid overflow of the parent view?
Under the estimation that FamilyActivityPicker uses those labels there must be a way because the font sizes decreases there.
I'm using the Screen Time API to shield apps the user selects. The problem is, that the apps are always shielded, even outside of the schedule. Here is my function initiateMonitoring:
func initiateMonitoring(scheduleStart: DateComponents, scheduleEnd: DateComponents) {
let schedule = DeviceActivitySchedule(
intervalStart: scheduleStart,
intervalEnd: scheduleEnd,
repeats: true
)
let center = DeviceActivityCenter()
do {
try center.startMonitoring(.daily, during: schedule)
logger.log("STARTED MONITORING")
} catch {
logger.log("FAILED MONITORING: \(error.localizedDescription)")
}
}
The expected result should be that the selected Apps should be blocked during the schedule the user specified, but not outside the schedule, but it is blocked 24/7. I've experimented with the DateComponents, but the issue may be somewhere else.
What's maybe interesting is, I tried Logging the DeviceActivityMonitor and somehow it doesn't get called. The logging-output from initiateMonitoring is printed in the console, but for the DeviceActivityMonitor it's not. I implemented it as recommended as a separate Extension Target. For more context, here is my DeviceActivityMonitor:
class DeviceActivityMonitorExtension: DeviceActivityMonitor {
let store = ManagedSettingsStore()
let logger = Logger()
override func intervalDidStart(for activity: DeviceActivityName) {
super.intervalDidStart(for: activity)
logger.log("INTERVAL STARTED")
let model = ShieldManager.shared
let applications = model.selectionToDiscourage.applicationTokens
let categories = model.selectionToDiscourage.categoryTokens
let webCategories = model.selectionToDiscourage.webDomainTokens
store.shield.applications = applications.isEmpty ? nil : applications
store.shield.applicationCategories = ShieldSettings.ActivityCategoryPolicy.specific(categories, except: Set())
store.shield.webDomains = webCategories.isEmpty ? nil : webCategories
}
...
What's confusing me is that the apps are in fact shielded, so the MonitorExtension should be called, but neither the logging works nor my separate ShieldConfigurationExtension, maybe the issues are connected...
Any help would be gladly appreciated!
I am currently developing an app to help people focus on their work.
I use familycontrols , managedsettings and devie activity to develop it.
I am currently facing an issue where I would like to hide the app selected by the user from the main screen and resource library.
But I did not find any relevant information in the document. Who can help me.
when i watch the video (https://developer.apple.com/videos/play/wwdc2022/110336/)
i find the code
let socialStore = ManagedSettingsStore(named: .social)
let socialCategory = database.socialCategoryToken
socialStore.shield.applicationCategories = .specific([socialCategory])
i try to find ManagedSettingsStore shield and specific([socialCategory]) i Documentation , but i failed.
who can tell me wher i can find about ManagedSettingsStore shield or specific([socialCategory]) ?
thanks a lot.
There is a way to allow DeviceActivityMonitor to make UI changes?
Currently I have some code in the intervalDidStart and intervalDidEnd that updates Core Data entities, and it works correctly.
However, the changes are only reflected if the app is terminated and opened again.
Thus, if the app is opened and one of the methods inside the DeviceActivityMonitor is being called, the UI won't be updated.
PS: I am using @FetchRequest to retrieve the core data entities called BlockEntity, and I have a little circle in the UI which should indicate the status of the block: active/inactive, so it would be nice to update the color in real time when intervalDidStart or intervalDidEnd are called.
The functionality of authorizationStatus and requestAuthorization is completely broken. I'm using Xcode 15.3 and iOS 17.4.
Does anyone have a solution?
authorizationStatus doesn't behave as promised
Revoking authorization in the system-wide settings does not change the authorizationStatus while the app is not closed. Calls to center.authorizationStatus will still return .approved instead of .denied.
Even closing and relaunching the app after revoking authorization does not work: authorizationStatus is then .notDetermined when it should be .denied.
Tapping "Don't Allow" in the alert shown after an initial call to requestAuthorization leaves the authorizationStatus unchanged, i.e. at .notDetermined. This is contrary to the promised outcome .denied (defined as: "The user, parent, or guardian denied the request for authorization") and contrary to the definition of .notDetermined (defined as: "The app hasn’t requested authorization", when it just did).
Same issue when first tapping "Continue" followed by "Don't Allow" on the next screen.
As a consequence of authorizationStatus being broken, its publisher $authorizationStatus is worthless too.
requestAuthorization doesn't behave as promised
This is most likely a consequence of the corrupted authorizationStatus: when revoking authorization in the system-wide settings, a call to requestAuthorization opens the authorization dialogue instead of doing nothing. It is thus possible to repeatedly ask a user to authorize Family Controls.
Code sample
To reproduce, create a new SwiftUI app, add the "Family Controls" capability and a button executing the following task when tapped:
let center = AuthorizationCenter.shared
var status = center.authorizationStatus
print(status)
do {
try await center.requestAuthorization(for: .individual)
print("approved")
} catch {
print("denied")
}
status = center.authorizationStatus
print(status)
I want to block specific Websites with FamilyControls in my App. To do this, I need to access the WebDomainTokens of a specific Website, without the user selecting it in the FamilyActivityPicker. I basically just want to have a button e.g. "Block Instagram" and when clicking it, the instagram website is blocked. I don't see any way in accessing the WebDomainToken without the FamilyActivityPicker. Or is there maybe another approach to this problem?
Currently I tried the following:
var selectionToDiscourage = FamilyActivitySelection() {
willSet {
let applications = newValue.applicationTokens
store.shield.applications = applications.isEmpty ? nil : applications
let webDomain: WebDomain = .init(domain: "https://www.instagram.com/")
print("TOKEN: \(webDomain.token)")
print("DOMAIN: \(webDomain.domain)")
if let webDomainToken = webDomain.token {
let webDomainTokenSet: Set<WebDomainToken> = [webDomainToken]
store.shield.webDomains = webDomainTokenSet
}
}
}
But when only giving the domain, the token always prints nil.
Hope someone can help!
XCODE: 15.2
IOS: 16.0.1
Following the document about how to track the current status of AuthorizationStatus seems to be not working on my current environment.
https://developer.apple.com/documentation/familycontrols/authorizationcenter/authorizationstatus
The user authorization is .individual
Here is my sample code to identify if the status has changed after revoking the permission from the Settings > ScreenTime
@ObservedObject var center = AuthorizationCenter.shared
@Environment(\.dismiss) var dismiss
@State private var isAlert = false
var body: some View {
VStack {
Button {
dismiss()
} label: {
Text("Close")
}
.buttonStyle(GrowingButton())
}
VStack {
Button {
DispatchQueue.main.async {
if center.authorizationStatus != .approved {
Task {
do {
if #available(iOS 15.0, *) {
if #available(iOS 16.0, *) {
//let center = AuthorizationCenter.shared
try await center.requestAuthorization(for: .individual)
}
}
}
}
} else {
self.isAlert = true
}
}
} label: {
Text("Validate Screen Time Permission")
}
.buttonStyle(GrowingButton())
.alert(isPresented: $isAlert, content: {
switch center.authorizationStatus {
case .notDetermined:
Alert(title: Text("Screen Time not Determined"))
case .denied:
Alert(title: Text("Screen Time is Denied"))
case .approved:
Alert(title: Text("Screen Time is Approved"))
@unknown default:
Alert(title: Text("Unknown"))
}
})
}
}
}
It will still fallback to the APPROVED state even if the permission was already revoked from ScreenTime settings.
Highly appreciated if anybody from the community can help me figure out this thing to work.
Thank you.
I am trying to understand how to approach 'x minute' pauses for a DeviceActivitySchedule.
For instance, I would like to let the user pause for 5 minutes from an active schedule (meaning un-shielding the apps and re-applying the shield after the 5 min has passed).
The only way that came to my mind was calling the following:
Calling .startMonitoring to start monitoring a new event with the same apps starting .now and ending .now + 5 minutes;
Calling in the intervalDidStart, store.shield.applications.subtract(apps) so that the apps are removed from the shield.
Calling in the intervalDidEnd, store.shield.applications = apps so that the apps are now shielded again.
The problem is that, from the Apple Developer Documentation:
The minimum interval length for monitoring device activity is fifteen minutes.
So the minimum pause I could offer to the user would be 15 minutes.
And that tells me this approach is most likely wrong, because all other Screen Time apps, like Opal, Jomo, AppBlock offer also 5 min pause.
Does anyone know / can think of a different and better approach?
An app that uses screen time API.
After asking for permission and receiving them works, everything generally work, shields applications etc..
Upon new version update (manually update from testFlight old version or App Store)
I get an error message, I can then update again without any error but the permissions
are revoked and I have to ask for them again. shielding stopes.
This happens randomly on TestFlight or App Store and the permissions
Did anyone encounter this or solved this?
this seems to have started a month ago but maybe I just missed it before
Hello,
I want to make a simple project that simply displays the user's current daily ScreenTime.
I believe you may be able to do the functionality I want with: FamilyControls or ScreenTime. I believe that you may need to pay for the paid developer account in order to access the FamilyControl framework.
I am having trouble finding guidance as to how I would do this as most reddit/stackoverflow posts are incomplete with answers and I cannot find demos/code examples/tutorials/sample usage anywhere on google.
I would greatly appreciate any help and guidance. If the solution requires the paid developer tier, I am fine with that. Thank you in advance
I have implemented a shielding mechanism via Family Controls to prevent unauthenticated users from deleting specific shortcuts within the Shortcuts app. While this successfully secures the app, it has an unintended consequence: the Shortcuts app no longer appears in Spotlight Search and becomes inaccessible for users subject to this shielding. I understand that this is the default behavior when implementing app shielding or time limits on specific apps. Is there a way to retain the app's visibility in Spotlight Search despite it being shielded?
I'm writing an app that uses Family Control. Most of the functionality has already been debugged, including in the call try deviceActivityCenter.startMonitoring(activityName, during: schedule, events: [ eventName: event ]) Works fine on my IOS17 device, but on my old IOS16 device I found that it can't be called, like intervalDidStart, intervalDidEnd, eventDidReachThreshold etc can't be called. I checked some information, https://forums.developer.apple.com/forums/thread/724243 delete the Build Settings mentioned in this link, and change the ios Deployment Target to ios16, and found that it didn't work. Don't know where the problem is?