Introduction:
Recently, Apple released the ScreenTime API for iOS. Although the ScreenTime feature has been there since the release of iOS 13, from iOS 15, Apple has introduced ScreenTime API for developers to allow them to develop apps for their user base with customized UI for an enhanced experience for parental controls. It mostly provides features for parental controls such as allowing parents to add restrictions to apps and websites to their child’s device, enabling parents to set time limits on their child’s usage of the device, and sharing usage of the child’s device with parents.
ScreenTime API comprises 3 frameworks:
- Managed Settings
- Family Controls
- Device Activity
Below are the functionalities provided by each framework.
Managed Settings Framework:
- Shield apps and websites with custom UI
- Provides web content filtering
- Set restrictions on devices and keep them in place
- Provides multiple options for restrictions on AppStore, Media, Safari, Game Center, etc.
Family Controls Framework
- Authorizes access to Screen Time API on child’s device
- Prevents removal/deletion of your app by the child
- Requires parent’s authorization to remove your app
- Provides FamilyActivityPicker to choose apps and websites to restrict
Device Activity Framework
- Monitors schedules defined by the user and execute code accordingly.
- Monitors usage thresholds defined and execute code when threshold reached
- The type and time of usage can be defined.
- Provides Device Activity extension that executes the code on respective schedules and events without even the child opening the app.
Prerequisites
- Apple Family Sharing needs to be enabled by the user and family members’ Apple ID should be added there
- Adding apple id as a family member enables screen time monitoring for all iOS devices associated with that Apple ID.
- Child’s Apple ID can be created by a parent or an existing ID with an age below 18 can be added.
Now let’s code…
First of all, we need to add a Device Monitor Extension to our target. This acts as a background service on a child’s device for various functionalities.
Go to File > New > Target > Choose Device Activity Monitor Extension.
Give the extension the desired name and click Finish. A new group named as your extension will be added in the project. Create a file named as MyMonitor in this group and write the following code.
import UIKit
import MobileCoreServices
import ManagedSettings
import DeviceActivity
class MyMonitor: DeviceActivityMonitor {
let store = ManagedSettingsStore()
override func intervalDidStart(for activity: DeviceActivityName) {
super.intervalDidStart(for: activity)
print("interval did start")
let model = MyModel.shared
let applications = model.selectionToDiscourage.applicationTokens
store.shield.applications = applications.isEmpty ? nil : applications
store.dateAndTime.requireAutomaticDateAndTime = true
}
override func intervalDidEnd(for activity: DeviceActivityName) {
super.intervalDidEnd(for: activity)
store.shield.applications = nil
store.dateAndTime.requireAutomaticDateAndTime = false
}
}
The above code overrides the two methods of Device Activity Monitor which are called on the interval start and end respectively, for the intervals defined in the other parts of code. On interval start, we are restricting user/child’s device from changing their date and time along with applying shield to all those apps restricted by the parent.
Now, in the AppDelegate of the main app, write the following code. Also, don’t forget to import FamilyControls framework in AppDelegate
.
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
AuthorizationCenter.shared.requestAuthorization { result in
switch result {
case .success:
print("Success")
case .failure(let error):
print("error for screentime is \(error)")
}
}
_ = AuthorizationCenter.shared.$authorizationStatus
.sink() {_ in
switch AuthorizationCenter.shared.authorizationStatus {
case .notDetermined:
print("not determined")
case .denied:
print("denied")
case .approved:
print("approved")
@unknown default:
break
}
}
return true
}
}
The above code will show the below alert to the user for the first time and will require a parent to authenticate on the child’s device.
After parent successfully authenticates the child, the next time alert won’t show up and the child cannot delete the app without the parent/guardian’s approval just like as above.
Now create a new Swift file MyModel and write the following code.
import Foundation
import FamilyControls
import DeviceActivity
import ManagedSettings
class MyModel: ObservableObject {
static let shared = MyModel()
let store = ManagedSettingsStore()
private init() {}
var selectionToDiscourage = FamilyActivitySelection() {
willSet {
print ("got here \(newValue)")
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() {
let schedule = DeviceActivitySchedule(intervalStart: DateComponents(hour: 0, minute: 0), intervalEnd: DateComponents(hour: 23, minute: 59), repeats: true, warningTime: nil)
let center = DeviceActivityCenter()
do {
try center.startMonitoring(.daily, during: schedule)
}
catch {
print ("Could not start monitoring \(error)")
}
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 = false
}
}
extension DeviceActivityName {
static let daily = Self("daily")
}
FamilyActivitySelection is a picker provided by the FamilyControls framework which displays all the apps, categories, and websites on a child’s device and allows parents to restrict those apps.
Above code has the method initiateMonitoring
in which we are creating a schedule for 24 hours named as daily for which the Device activity monitor extension will observe. Along with that, we are setting some additional restrictions for the child such as child cannot change date and time, cannot change apple id, passcode, use Siri, download apps above certain rating and much more.
Finally, a view is created in the below code snapshot to display FamilyActivitySelection
picker on a button click and start monitoring on child’s device after which all the additional restrictions are applied.
import SwiftUI
import FamilyControls
struct ContentView: 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)
Button("Start Monitoring") {
model.initiateMonitoring()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
When a child launches a restricted website or app, the following screen appears which is also customizable in a limited scope. We can only change the logo, title and message of the following screen which we haven’t done in this code. Also, a restriction sign appears on the app icon of the restricted app.
Issue Found
Although ScreenTime API provides a package to develop a parental control app there’s a lot of immaturity in the API itself. Implementing the above code should have followed the specific behavior of the app flow mentioned by Apple in WWDC21 while the following were the issues faced.
Should be App Flow:
- Parent/Guardian opens the app on the child’s device and authorizes with their Apple ID
- Parents can then open the app on their device and choose settings, restrictions, and rules, and ScreenTime API sends that info to the child’s device
- Then on the child’s device, it creates schedules and events using the Device Activity extension
Issues faced:
- For the first few times, FamilyActivityPicker does not populate the apps on the child’s device under the categories
- No option yet for opening the picker for a specific child
- Currently, restricting apps only from a child’s device works even if the apps populate in the picker on parent’s device
- Does not works on Simulator. Always needs a real device
- Error returned from the Authorization is of type NSError and not the FamilyControlsError
- This causes problem to get the error type and perform respective actions
- Works on iOS 15 and above and requires SwiftUI implementation.
What’s Next
Recently Apple has introduced some new features in ScreenTime API all 3 frameworks in WWDC22 but I haven’t explored them yet. These new features are available to explore for iOS 16 and above. Also, this tutorial does not have restriction screen customization which can also be explored.