Creating a ScreenTime ShieldConfigurationDataSource for iOS FamilyControls API

John Baker
5 min readMay 1, 2024

Recently I’ve been working with the FamilyControls/ScreenTime API.

I made an application called Stryde that allows user’s to earn ScreenTime based on how many steps they’ve taken. This process has been very painstaking & frustrating, mostly due to the restrictions around the FamilyControls API.

Get Stryde on the iOS App Store

Apple has done their best to make this a very painful process. There is very little documentation & very little third party resources as well. In this article I’m going to be quickly walking through how to set up your Shield Configuration for your ScreenTime blocked app.

Creating an App Extension

The first thing that is not super obvious if you haven’t worked with them before, is that you need to create an actual new target for your ScreenTimeShield. You can do this by going to your App Configuration & clicking + for a new target. Search for Shield Configuration & hit next. Hit activate if prompted.

Screenshot of XCode setup for Shield Configuration

You’ll be presented with a file that looks like this:

Screenshot of default implementation of ShiledConfigurationDataSource

You’ll notice that there are four different configuration setups:

  • override func configuration(shielding application: Application) -> ShieldConfiguration for when a user is out of screentime on a specifically blocked application, meaning it is not part of a category
  • override func configuration(shielding application: Application, in category: ActivityCategory) -> ShieldConfiguration for when a user is out of screentime on a blocked category
  • override func configuration(shielding webDomain: WebDomain) -> ShieldConfiguration for when a user is out of screentime on a blocked webDomain
  • override func configuration(shielding webDomain: WebDomain, in category: ActivityCategory) -> ShieldConfiguration for when a user is out of screentime ona blocked webcategory

The current default implementation will return the system default ScreenTime notification. Something like this except the default implementation will not include an Ignore Limit option.

Screenshot of Default ScreenTime blocked screen

Setting up our custom shield

To get started with setting up our custom shield let’s look at the init

return ShieldConfiguration(
backgroundBlurStyle: UIBlurEffect.Style?,
backgroundColor: UIColor?,
icon: UIImage?,
title: ShieldConfiguration.Label?,
subtitle: ShieldConfiguration.Label?,
primaryButtonLabel: ShieldConfiguration.Label?,
primaryButtonBackgroundColor: UIColor?,
secondaryButtonLabel: ShieldConfiguration.Label?)

It’s really that simple, just fill in the blanks to your liking. One thing to note is that some values will provide a system default if they are nil, & other’s will not appear if they are nil. For example if your secondaryButtonLabel is nil, it just will not be shown. Whereas if your primaryButtonLabel is nil, it will be given a system default value.

class ShieldConfigurationExtension: ShieldConfigurationDataSource {
var subtitleText: String {
if SharedData.screenTimeMode == .stepToday {
return "App is being blocked by Stryde. Get more steps in to earn more screen time"
} else if SharedData.screenTimeMode == .stepTomorrow {
return "App is being blocked by Stryde. Make sure to get more steps in today to earn screen time for tomorrow"
} else {
return ""
}
}

var secondaryButtonLabel: ShieldConfiguration.Label? {
if SharedData.screenTimeMode == .stepToday {
return .init(text: "Recalibrate to Steps", color: SharedAssets.uistepperBGPurple1)
} else {
return nil
}
}

override func configuration(shielding application: Application) -> ShieldConfiguration {
return ShieldConfiguration(backgroundBlurStyle: .light,
backgroundColor: SharedAssets.uistepperBGPurple1.withAlphaComponent(0.1),
icon: UIImage(named: "squaredIconSmallNoBG"),
title: .init(text: "Blocked By Stryde", color: SharedAssets.uistepperBGPurple1),
subtitle: .init(text: subtitleText, color: UIColor.secondaryLabel),
primaryButtonLabel: .init(text: "OK", color: SharedAssets.uistepperWhitePurpleHint),
primaryButtonBackgroundColor: SharedAssets.uistepperBGPurple1,
secondaryButtonLabel: secondaryButtonLabel)
}
}

For my application Stryde, I’ve configured it to match my app’s stylings. One way I did this was by creating a new Asset Catalog that is assigned to both my main application target, & my ShieldConfig target. I am pulling my application’s icon from here, as well as the colors for buttons & backgrounds.

This is how I can ensure the styling remains consistent through the application, even when a user is not actually in my application.

Interested in seeing how to customize this screen based on user configurations or other main app data? See more about this below a brief advertisement for my app — aka what made this article free instead of paywalled ;)

Steps = ScreenTime

Are you someone who spends too much time on a screen & not enough time getting active? Check out my App Stryde mentioned above. It helps limit your ScreenTime & motivate you to get active.

It can be found currently only on iOS (sorry Android, but this is a Swift article after all) from the app store

Get Stryde on iOS from the App Store

Link

Sharing data from main target to your ShieldConfig

So if you noticed in my ShieldConfig setup above I am customizing the subtitle based on what ScreenTime mode the user has setup. if SharedData.screenTimeMode == .stepToday { } . Setting the app up correctly to share this data between extensions is not super straightforward.

To do this you must first go to both targets & add the AppGroup capability for each target. Head on over to your Application’s configuration, select the target you wish to add, click to the Signing & Capabilities tab, click + capability & search for App Groups.

Now pay attention for one second because Apple does not make this clear, & it is hard to find information on how to do it correctly. After adding the App Group capability, you have to set up an actual App Group.

The app group must follow this exact syntax:

group.[Your Apps’ Bundle ID].[Details]

So for my example project here, I would set up an AppGroup:

group.com.b4k3r.FirebaseTest.sharedData

Otherwise you will get errors & spend hours trying to figure out what’s going on digging through unclear documentation & confusing forums.

Add this to each target & you are now set up to share data between your main target & your App’s extensions. If you’d like to learn more about how I set up my data to be easily shared to the group, head on over to my other article that focuses on it specifically:

Check out stepper on the app store

If this article helped you please don’t forget to clap, follow, subscribe & most importantly check out Stepper!! But seriously, thanks for reading & hope this helps you get your ShieldConfiguration set up properly.

--

--