-
Notifications
You must be signed in to change notification settings - Fork 3
FamilyControls
Package
SwiftBindings.Apple.FamilyControls· Version26.2.6Auto-published fromapple-frameworks/FamilyControls/FAMILYCONTROLS-GUIDE.md.
SwiftBindings.Apple.FamilyControls exposes Apple's FamilyControls framework to C# through .NET 10's native Swift interop — direct Swift calls, not Objective-C proxy wrappers. FamilyControls is the authorization layer for Screen Time apps (parental controls, focus/wellbeing apps): you request authorization, let the user pick which apps/categories/websites to manage, and hand the resulting selection to ManagedSettings / DeviceActivity to enforce restrictions.
The model trips people up, so read The token model before anything else: FamilyControls deliberately gives you opaque tokens, not app names or bundle IDs. You can store and apply a selection, but you cannot inspect what's inside it. This guide documents exactly what the bindings emit and what you can and can't do with it.
- Requirements & install
- Naming conventions
- The token model
- Quick start: request authorization
- Authorization status
- The activity selection
- Persisting a selection
- Errors
- The picker (SwiftUI)
- Memory & threading
- Reference links
- .NET 10.0+
- iOS 26.2+
- macOS host for development
- The Family Controls capability enabled on your app (request the entitlement from Apple; it is not granted automatically)
dotnet add package SwiftBindings.Apple.FamilyControls
using FamilyControls;FamilyControls only does real work on a physical device with the Family Controls entitlement and (for child accounts) a Screen Time / Family Sharing setup. In the simulator the types load and authorization can be queried, but enforcement is a no-op.
| Swift | C# | Rule |
|---|---|---|
func requestAuthorization(for:) async throws |
RequestAuthorizationAsync(member, ct) |
async method gains Async suffix; first label dropped |
AuthorizationCenter.shared |
AuthorizationCenter.Shared (static property) |
singletons are PascalCase static properties |
enum AuthorizationStatus { case approved } |
AuthorizationStatus class with .Tag + CaseTag.Approved static singletons |
a Swift enum projects to a wrapper class with a Tag and static case singletons |
enum FamilyControlsError |
plain C# enum (int-backed) |
payload-free error enum |
error.errorDescription |
error.GetErrorDescription() (extension method) |
enum-attached members become extension methods |
selection.applicationTokens |
selection.ApplicationTokens |
properties are PascalCase |
A FamilyActivitySelection is the central value: it's the set of apps, categories, and websites the user chose to manage. Crucially, those choices are stored as opaque tokens, by design — Apple does not let your app learn which apps a child selected (that would defeat the privacy model). What the bindings emit reflects this exactly:
| Property | C# type | What you get |
|---|---|---|
ApplicationTokens |
Swift.SwiftSet<IntPtr> |
opaque application tokens — count them, store them, pass them on; you cannot read an app name or bundle ID from them |
CategoryTokens |
Swift.SwiftSet<IntPtr> |
opaque category tokens |
WebDomainTokens |
Swift.SwiftSet<IntPtr> |
opaque web-domain tokens |
Applications |
IReadOnlySet<Swift.ManagedSettings.Application> |
the selected applications as ManagedSettings values |
Categories |
IReadOnlySet<Swift.ManagedSettings.ActivityCategory> |
the selected categories |
WebDomains |
IReadOnlySet<Swift.ManagedSettings.WebDomain> |
the selected web domains |
IncludeEntireCategory |
bool |
whether picking a category implies all its apps |
The tokens (ApplicationTokens, etc.) are settable (get/set); the materialized Applications / Categories / WebDomains sets are read-only. The practical workflow is: get a selection from the picker → store it (as JSON) → later, load it and hand its tokens to ManagedSettings to apply shields/restrictions. You never decode the tokens yourself.
Authorization is the gate for everything else. Request it once, then check the status before applying any restriction.
using FamilyControls;
var center = AuthorizationCenter.Shared;
try
{
// FamilyControlsMember: Individual (this device's user) or Child (a child in Family Sharing)
await center.RequestAuthorizationAsync(FamilyControlsMember.Individual);
var status = center.AuthorizationStatus; // AuthorizationStatus (wrapper)
if (status.Tag == AuthorizationStatus.CaseTag.Approved)
{
// You may now read selections and apply Screen Time restrictions.
}
}
catch (Swift.Runtime.SwiftException ex)
{
// e.g. user declined, restricted, or the entitlement is missing
Console.WriteLine(ex.Message);
}RequestAuthorizationAsync(FamilyControlsMember member, CancellationToken ct = default) is throws in Swift, so failures arrive as a Swift.Runtime.SwiftException (see Errors).
There is also a closure-based
RequestAuthorization(Action<…>)overload and aRevokeAuthorization(Action<…>)overload, but theAsyncform above is the idiomatic one for C#.
FamilyControlsMember is a plain enum (long-backed): Child = 0, Individual = 1. Its GetDescription() extension method returns a human-readable label.
AuthorizationCenter.Shared.AuthorizationStatus returns an AuthorizationStatus — a wrapper over the Swift enum. Discriminate with .Tag against the CaseTag enum, or compare against the static singletons:
public enum CaseTag : uint { NotDetermined = 0, Denied = 1, Approved = 2 }var status = AuthorizationCenter.Shared.AuthorizationStatus;
switch (status.Tag)
{
case AuthorizationStatus.CaseTag.Approved: /* good to go */ break;
case AuthorizationStatus.CaseTag.Denied: /* user said no */ break;
case AuthorizationStatus.CaseTag.NotDetermined:/* not asked yet */ break;
}
// Or compare against the cached singletons:
if (status == AuthorizationStatus.Approved) { /* … */ }AuthorizationStatus static singletons: NotDetermined, Denied, Approved. Other members: Description (string), RawValue (int), Tag (CaseTag). It implements value equality (==, !=, Equals).
FamilyActivitySelection holds the user's choices. Construct an empty one, or one that treats category picks as "the entire category":
var selection = new FamilyActivitySelection(); // empty
var withCategories = new FamilyActivitySelection(includeEntireCategory: true);Read the token sets to drive enforcement or to show a count in your UI:
int appCount = selection.ApplicationTokens.Count;
int categoryCount = selection.CategoryTokens.Count;
int domainCount = selection.WebDomainTokens.Count;
Console.WriteLine($"User chose {appCount} apps, {categoryCount} categories, {domainCount} sites.");You cannot get an app name out of a token — that's the privacy contract. To display the selection to the user, use the picker UI (which renders names on the system's behalf) or the SwiftUI label/icon view bridges; to enforce it, pass the tokens to ManagedSettings.
FamilyActivitySelection implements value equality, so two selections with the same contents compare equal (== / Equals).
Because you can't inspect tokens, the way to persist a selection across launches is to serialize the whole value. The binding exposes JSON round-tripping (Foundation Codable under the hood):
// Save
byte[] json = selection.EncodeToJson();
File.WriteAllBytes(path, json);
// Restore
byte[] bytes = File.ReadAllBytes(path);
FamilyActivitySelection restored = FamilyActivitySelection.DecodeFromJson(bytes);EncodeToJson() throws InvalidOperationException if the Swift encoder rejects the value; DecodeFromJson(byte[]) throws ArgumentNullException for null input and InvalidOperationException if decoding fails.
The synthesized Swift
Codableencode(to:)/init(from:)members themselves are intentionally pruned from the binding (theirEncoder/Decoderare unresolvable existential protocols). Use theEncodeToJson/DecodeFromJsonhelpers instead — they are the supported persistence path.
RequestAuthorizationAsync surfaces failures as Swift.Runtime.SwiftException. The error codes are also available as a plain enum:
public enum FamilyControlsError : int
{
Restricted = 0,
Unavailable = 1,
InvalidAccountType = 2,
InvalidArgument = 3,
AuthorizationConflict = 4,
AuthorizationCanceled = 5,
NetworkError = 6,
AuthenticationMethodUnavailable = 7, // iOS 16+
}Each value carries localized text via an extension method:
string? text = FamilyControlsError.Restricted.GetErrorDescription(); // may be null
GetErrorDescription()returnsstring?and may benullon some OS versions — null-check before display.
Apple's FamilyActivityPicker — the system UI a user taps through to choose apps/categories — is a SwiftUI View. SwiftUI views are not bound as ordinary callable C# types; the generator emits a SwiftUI bridge instead (FamilyControls.SwiftUIBridge) that hosts the view in a UIHostingController reachable from C#. The bindings expose FamilyActivityPickerSession with a Create(...) factory and Dispose(). The session takes a FamilyActivitySelection, surfaces its ViewController for presentation, and exposes ReadSelection() to round-trip the user's choice back through the bridge.
FamilyActivityTitleView and FamilyActivityIconView are also SwiftUI View types; bridge sessions for them are not generated in this release — use the native SwiftUI API directly if you need them.
using var selection = new FamilyActivitySelection(includeEntireCategory: true);
// onAppear / onDisappear callbacks are optional lifecycle hooks
using var session = FamilyActivityPickerSession.Create(selection,
onAppear: () => Console.WriteLine("picker appeared"),
onDisappear: () => Console.WriteLine("picker dismissed"));
var viewController = session.ViewController; // UIViewController? — present from your host
// ...after the user dismisses the picker:
var updated = session.ReadSelection();
// You can also push a new selection into an already-displayed picker:
// session.UpdateSelection(newSelection);The native FamilyControlsBridge library that backs these P/Invokes is built and bundled as a separate xcframework inside the package — no host-side wiring required.
Generated types implement ISwiftObject / IDisposable. For short-lived locals the finalizer cleans up, but using var is the recommended pattern for deterministic cleanup — Dispose is safe on every generated type and double-Dispose is a no-op.
using var selection = new FamilyActivitySelection();-
AuthorizationStatussingletons (Approved, etc.) are cached and must not be disposed — disposing a cached singleton is a no-op by design, but don't wrap them inusing. -
Authorization is async and may prompt.
RequestAuthorizationAsyncshows system UI on a device; await it off the UI thread is fine, but marshal back before touching your UI. Pass aCancellationTokento cancel. -
AuthorizationCenter.Sharedis a process-wide singleton; fetch it where you need it rather than caching a field.
- Apple — FamilyControls framework
- Apple — ManagedSettings (apply the selection's tokens)
- Apple — DeviceActivity (monitor usage)
-
ActivityKit —
SwiftBindings.Apple.ActivityKitv26.2.6 -
CryptoKit —
SwiftBindings.Apple.CryptoKitv26.2.6 -
FamilyControls —
SwiftBindings.Apple.FamilyControlsv26.2.6 -
LiveCommunicationKit —
SwiftBindings.Apple.LiveCommunicationKitv26.2.6 -
Matter —
SwiftBindings.Apple.Matterv26.2.6 -
MatterSupport —
SwiftBindings.Apple.MatterSupportv26.2.6 -
MusicKit —
SwiftBindings.Apple.MusicKitv26.2.6 -
ProximityReader —
SwiftBindings.Apple.ProximityReaderv26.2.6 -
RealityFoundation —
SwiftBindings.Apple.RealityFoundationv26.2.6 -
RealityKit —
SwiftBindings.Apple.RealityKitv26.2.6 -
RoomPlan —
SwiftBindings.Apple.RoomPlanv26.2.6 -
StoreKit2 —
SwiftBindings.Apple.StoreKit2v26.2.6 -
TipKit —
SwiftBindings.Apple.TipKitv26.2.6 -
Translation —
SwiftBindings.Apple.Translationv26.2.6 -
WeatherKit —
SwiftBindings.Apple.WeatherKitv26.2.6 -
WorkoutKit —
SwiftBindings.Apple.WorkoutKitv26.2.6