I've followed along with the Screen Time API demos (https://developer.apple.com/videos/play/wwdc2021/10123/)
Also followed along with this guide, which is essentially the same: https://www.folio3.com/mobile/blog/screentime-api-ios/
I'm able to restrict access to apps/categories with the FamilyActivityPicker and FamilyActivitySelection.
I can set a DeviceActivitySchedule, and then use DeviceActivityCenter to start monitoring it.
I can tell that the schedule is working, because MyMonitor:intervalDidStart()
does get called, and it works except for the restricting of apps/categories/webCategories.
It's as if MyModel does not have any values set from within MyMonitor. I've confirmed that MyModel does have the correct FamilyActivitySelection apps etc, everywhere else in my Target, before and after the MyMonitor:intervalDidStart()
gets called.
MyMonitor is in a separate target called MonitorExtension
, that I created using the Device Activity Monitor Extension
template. But I made sure to set the Target Membership of MyModel to both my main target, and my extension target.
I have set NSExtensionPrincipalClass
to $(PRODUCT_MODULE_NAME).MyMonitor
, as suggested.
I have added MyModel.swift
to the Compiled Sources in my extensions Build Phases.
I have edited my apps build scheme, to make sure the extension target is also built.
One more interesting thing is that debugger breakpoints and print statements do not work from within my extension. I've even tried caching a string from within MyMonitor:intervalDidStart
, and tried to retrieve it afterwards in my main target, but it is nil.
Still, I've confirmed that intervalDidStart
was actually called by adding any removing store.application.denyAppInstallation = true
, and having it work correctly.
I've spent so much time on this problem, any help would be massive..
Here are the files I've referenced:
import UIKit import MobileCoreServices import ManagedSettings import DeviceActivity class MyMonitor: DeviceActivityMonitor { let store = ManagedSettingsStore() override func intervalDidStart(for activity: DeviceActivityName) { super.intervalDidStart(for: activity) let model = MyModel.shared let applications = model.selectionToDiscourage.applicationTokens let categories = model.selectionToDiscourage.categoryTokens let webCategories = model.selectionToDiscourage.webDomainTokens if applications.isEmpty { print("No applications to restrict") } else { store.shield.applications = applications } if categories.isEmpty { print("No categories to restrict") } else { store.shield.applicationCategories = ShieldSettings.ActivityCategoryPolicy.specific(categories, except: Set()) } if webCategories.isEmpty { print("No web categories to restrict") } else { store.shield.webDomains = webCategories } store.dateAndTime.requireAutomaticDateAndTime = true store.account.lockAccounts = true store.passcode.lockPasscode = true store.siri.denySiri = true store.appStore.denyInAppPurchases = true store.appStore.maximumRating = 200 store.appStore.requirePasswordForPurchases = true store.media.denyExplicitContent = true store.gameCenter.denyMultiplayerGaming = true store.media.denyMusicService = true store.application.denyAppInstallation = true } override func intervalDidEnd(for activity: DeviceActivityName) { super.intervalDidEnd(for: activity) store.shield.applications = nil store.shield.applicationCategories = nil store.shield.webDomains = nil store.dateAndTime.requireAutomaticDateAndTime = false store.account.lockAccounts = false store.passcode.lockPasscode = false store.siri.denySiri = false store.appStore.denyInAppPurchases = false store.appStore.maximumRating = 1000 store.appStore.requirePasswordForPurchases = false store.media.denyExplicitContent = false store.gameCenter.denyMultiplayerGaming = false store.media.denyMusicService = false store.application.denyAppInstallation = false } }
import Foundation import FamilyControls import DeviceActivity import ManagedSettings class MyModel: ObservableObject { static let shared = MyModel() let store = ManagedSettingsStore() private init() {} var selectionToDiscourage = FamilyActivitySelection() { willSet { let applications = newValue.applicationTokens let categories = newValue.categoryTokens let webCategories = newValue.webDomainTokens store.shield.applications = applications.isEmpty ? nil : applications store.shield.applicationCategories = ShieldSettings.ActivityCategoryPolicy.specific(categories, except: Set()) store.shield.webDomains = webCategories } } func initiateMonitoring(scheduleStart: DateComponents, scheduleEnd: DateComponents) { let schedule = DeviceActivitySchedule(intervalStart: scheduleStart, intervalEnd: scheduleEnd, repeats: true, warningTime: nil) print(scheduleStart) print(scheduleEnd) let center = DeviceActivityCenter() do { try center.startMonitoring(.daily, during: schedule) } catch { print ("Could not start monitoring \(error)") } store.dateAndTime.requireAutomaticDateAndTime = false store.account.lockAccounts = false store.passcode.lockPasscode = false store.siri.denySiri = false store.appStore.denyInAppPurchases = false store.appStore.maximumRating = 1000 store.appStore.requirePasswordForPurchases = false store.media.denyExplicitContent = false store.gameCenter.denyMultiplayerGaming = false store.media.denyMusicService = false store.application.denyAppInstallation = false } } extension DeviceActivityName { static let daily = Self("daily") }
import SwiftUI import FamilyControls struct AppPicker: View { @StateObject var model = MyModel.shared @State var isPresented = false var body: some View { Button("Select Apps to Discourage") { isPresented = true } .familyActivityPicker(isPresented: $isPresented, selection: $model.selectionToDiscourage) } }