WWDC Quick Look 💓 By SwiftGGTeam
Meet the Screen Time API

Meet the Screen Time API

观看原视频

Highlight

Apple 在 iOS 和 iPadOS 15 引入 Screen Time API,让家长控制类 App 通过 Family Controls、Device Activity 和 Managed Settings 请求监护人授权、监听使用时长、执行限制,同时把 App 和网站使用数据留在设备上。

核心内容

家长控制 App 的核心问题,一直在儿童设备上。

家长设置规则后,儿童通常不会主动打开这个 App。开发者很难在合适的时间运行代码,也很难保证规则一直生效。比如晚上十点后屏蔽游戏,或者学习 App 用满三十分钟后解除娱乐 App 的屏蔽,传统后台任务不适合这种场景。

隐私问题更敏感。家长控制 App 会碰到 App 使用记录、网站访问记录、分类选择等信息。开发者需要知道要限制什么,却不应该把儿童具体用过哪些 App、访问过哪些网站传到服务器。

Screen Time API 把这件事拆成三个框架。

Family Controls(家庭控制)负责授权和隐私边界。它依赖 Family Sharing(家人共享),要求监护人批准 App 使用 Screen Time API,并用不透明 token 表示 App、网站和分类。

Device Activity(设备活动)负责在 App 没有启动时运行扩展。开发者声明时间窗口和使用阈值,系统在时间开始、结束或阈值达到时调用扩展。

Managed Settings(受管理设置)负责真正执行限制。扩展被唤起后,可以锁定账户、阻止密码修改、过滤网页流量、屏蔽 App,或解除之前设置的屏蔽。

这三个框架连在一起,形成一个完整流程:监护人在儿童设备上授权,家长设备选择规则,规则同步到儿童设备,儿童设备上由 Device Activity 扩展触发 Managed Settings 限制。

详细内容

Family Controls:先让监护人授权

07:22)Homework demo 的第一步,是在 App 启动时请求 Family Controls 授权。演讲明确说明,这个请求会要求家庭中的监护人批准。第一次运行时,系统显示授权提示;监护人点击 Allow 后,需要用 Apple ID 和密码认证。

import FamilyControls
import SwiftUI

@main
struct HomeworkApp: App {
    @StateObject private var model = HomeworkModel()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(model)
                .task {
                    AuthorizationCenter.shared.requestAuthorization { result in
                        switch result {
                        case .success:
                            model.isAuthorized = true
                        case .failure:
                            model.isAuthorized = false
                        }
                    }
                }
        }
    }
}

final class HomeworkModel: ObservableObject {
    @Published var isAuthorized = false
}

关键点:

  • import FamilyControls 引入 Family Controls 框架。
  • AuthorizationCenter.shared 使用共享授权中心发起请求。
  • requestAuthorization 会触发监护人批准流程。
  • .success 表示当前儿童设备已经获得授权。
  • .failure 可能发生在登录的 iCloud 账号不属于 Family Sharing 儿童账号时,演讲在 08:06 提到了这一点。

08:25)授权成功还会带来系统级保护。儿童设备不能退出 iCloud;如果 App 包含基于 Network Extensions(网络扩展)的端侧网页内容过滤器,系统会自动安装,并阻止儿童移除。

Device Activity:让扩展按时间窗口运行

09:31)儿童不会主动打开家长控制 App,所以 Homework 用 Device Activity Schedule(设备活动时间表)每天设置屏蔽。演讲说,Schedule 是一个时间窗口,系统会在窗口开始和结束时调用 App 的扩展。

import DeviceActivity

final class HomeworkMonitor: DeviceActivityMonitor {
    override func intervalDidStart(for activity: DeviceActivityName) {
        // 时间窗口开始后,系统第一次使用设备时调用这里。
    }

    override func intervalDidEnd(for activity: DeviceActivityName) {
        // 时间窗口结束后,系统第一次使用设备时调用这里。
    }
}

关键点:

  • DeviceActivityMonitor 是扩展的 principal class(主类)。
  • intervalDidStart 对应时间窗口开始。
  • intervalDidEnd 对应时间窗口结束。
  • 演讲在 10:14 说明,这两个方法会在时间开始或结束后,设备第一次被使用时调用。

10:26)主 App 需要创建 Activity 名称和 Schedule。名称用于在扩展里识别活动;Schedule 用来表示监控的时间范围。Homework demo 把活动命名为 daily,设置为每天重复。

import DeviceActivity
import Foundation

let daily = DeviceActivityName("daily")

let schedule = DeviceActivitySchedule(
    intervalStart: DateComponents(hour: 0, minute: 0),
    intervalEnd: DateComponents(hour: 23, minute: 59),
    repeats: true
)

let center = DeviceActivityCenter()
try center.startMonitoring(daily, during: schedule)

关键点:

  • DeviceActivityName("daily") 给这条监控规则命名,扩展回调会收到同一个名称。
  • DeviceActivitySchedule 定义时间边界。
  • repeats: true 表示这条规则重复执行。
  • DeviceActivityCenter 负责注册监控。
  • startMonitoring 把名称和时间表交给系统,之后由系统调用扩展。

Family Activity Picker:让监护人选择 App、网站和分类

11:09)限制前需要知道监护人想限制什么。Family Controls 提供 SwiftUI 组件 Family Activity Picker(家庭活动选择器)。监护人可以从家庭使用过的 App、网站和分类里选择项目。

import FamilyControls
import SwiftUI

struct ContentView: View {
    @State private var isPickerPresented = false
    @State private var selection = FamilyActivitySelection()

    var body: some View {
        Button("Select discouraged apps") {
            isPickerPresented = true
        }
        .familyActivityPicker(
            isPresented: $isPickerPresented,
            selection: $selection
        )
    }
}

关键点:

  • FamilyActivitySelection 保存监护人的选择。
  • Button 触发选择器显示。
  • familyActivityPicker 是演讲提到的 SwiftUI view modifier。
  • selection 绑定到 App 模型后,监护人的选择更新会回写到模型。
  • 选择结果是不透明 token,用来限制或监控对应的 App、网站和分类。

04:08)这些 token 是隐私设计的关键。系统允许 App 使用 token 做限制和监控,但 token 不暴露 App 或网站的可读身份,家庭共享组外的人无法知道儿童具体使用了什么。

Managed Settings:在扩展里屏蔽和解除屏蔽

11:59)拿到监护人的选择后,扩展在时间窗口开始时设置应用屏蔽,在时间窗口结束时清空屏蔽。演讲明确提到,需要导入 Managed Settings 模块,访问 application shield restriction。

import DeviceActivity
import ManagedSettings

final class HomeworkMonitor: DeviceActivityMonitor {
    private let store = ManagedSettingsStore()
    private let model = HomeworkModel.shared

    override func intervalDidStart(for activity: DeviceActivityName) {
        store.shield.applications = model.discouragedSelection.applicationTokens
    }

    override func intervalDidEnd(for activity: DeviceActivityName) {
        store.shield.applications = nil
    }
}

关键点:

  • import ManagedSettings 引入限制能力。
  • ManagedSettingsStore 保存要应用到设备上的限制。
  • store.shield.applications 对应演讲里的 application shield restriction。
  • model.discouragedSelection.applicationTokens 来自 Family Activity Picker 的不透明 token。
  • nil 表示移除之前设置的应用屏蔽,演讲在 12:14 直接描述了这个操作。

12:30)Managed Settings 不限于屏蔽 App。演讲列出的限制还包括阻止账户创建或移除、完全阻止 App 和网站、按年龄限制媒体内容。媒体 App 还可以读取 Movies 和 TV 内容限制,并且不需要 Family Controls 授权。

Device Activity Events:用使用阈值解除限制

13:51)Homework demo 的第二条规则是:儿童使用足够多的鼓励类 App 后,解除娱乐 App 的屏蔽。这里用 Device Activity Events(设备活动事件)监控使用阈值。

import DeviceActivity
import FamilyControls
import Foundation

let daily = DeviceActivityName("daily")
let encouraged = DeviceActivityEvent.Name("encouraged")

let event = DeviceActivityEvent(
    applications: model.encouragedSelection.applicationTokens,
    threshold: DateComponents(minute: 30)
)

let center = DeviceActivityCenter()
try center.startMonitoring(
    daily,
    during: schedule,
    events: [encouraged: event]
)

关键点:

  • DeviceActivityEvent.Name("encouraged") 给事件命名,扩展回调会用这个名称识别事件。
  • applications 使用监护人选择的鼓励类 App token。
  • threshold 表示要累计的使用时长,演讲把它称为 usage threshold。
  • events 和 Schedule 一起注册,这点在 13:41 提到。
  • 当阈值达到时,系统调用 Device Activity monitor 扩展。

14:46)扩展通过 eventDidReachThreshold 接收通知。Homework 在收到 encouraged 事件后,把应用屏蔽设为 nil,让之前被屏蔽的 App 恢复可用。

import DeviceActivity
import ManagedSettings

final class HomeworkMonitor: DeviceActivityMonitor {
    private let store = ManagedSettingsStore()

    override func eventDidReachThreshold(
        _ event: DeviceActivityEvent.Name,
        activity: DeviceActivityName
    ) {
        if event == DeviceActivityEvent.Name("encouraged") {
            store.shield.applications = nil
        }
    }
}

关键点:

  • eventDidReachThreshold 在使用阈值达到时被调用。
  • event 表示触发的事件名称。
  • activity 表示事件属于哪条 Schedule。
  • 判断 encouraged 后再解除屏蔽,避免别的事件误触发。
  • store.shield.applications = nil 复用前面的解除屏蔽方式。

Custom Shields:把系统屏蔽页做成自己的体验

15:27)默认屏蔽页来自 Screen Time。Managed Settings 还提供两个扩展点:一个定制屏蔽页外观,一个处理屏蔽页按钮动作。

import ManagedSettings
import UIKit

final class HomeworkShieldConfigurationProvider: ShieldConfigurationProvider {
    override func configuration(shielding application: Application) -> ShieldConfiguration {
        ShieldConfiguration(
            backgroundBlurStyle: .systemMaterial,
            backgroundColor: .systemBackground,
            icon: UIImage(systemName: "book.closed.fill"),
            title: ShieldConfiguration.Label(text: "Homework first", color: .label),
            subtitle: ShieldConfiguration.Label(text: "Read for 30 minutes to unlock this app.", color: .secondaryLabel),
            primaryButtonLabel: ShieldConfiguration.Label(text: "Ask for Access", color: .white),
            primaryButtonBackgroundColor: .systemBlue,
            secondaryButtonLabel: ShieldConfiguration.Label(text: "OK", color: .systemBlue)
        )
    }
}

关键点:

  • ShieldConfigurationProvider 是外观定制扩展的主类。
  • configuration 接收当前被屏蔽的 application。
  • ShieldConfiguration 返回背景效果、背景色、图标、标题、副标题和按钮标签。
  • 系统会把这个配置显示在 Homework 屏蔽的 App 上,演讲在 16:37 说明了这一点。

16:50)第二个扩展点处理按钮。扩展要继承 ShieldActionHandler,重写 handle,并调用 completion handler 返回 shield action response。演讲列出的响应包括关闭被屏蔽 App,或延迟动作并重绘屏蔽配置。

import ManagedSettings

final class HomeworkShieldActionHandler: ShieldActionHandler {
    override func handle(
        action: ShieldAction,
        for application: ApplicationToken,
        completionHandler: @escaping (ShieldActionResponse) -> Void
    ) {
        switch action {
        case .primaryButtonPressed:
            completionHandler(.defer)
        case .secondaryButtonPressed:
            completionHandler(.close)
        @unknown default:
            completionHandler(.close)
        }
    }
}

关键点:

  • ShieldActionHandler 是按钮动作扩展的主类。
  • handle 告诉扩展用户按下了主按钮还是副按钮。
  • application 表示当前被屏蔽的 App token。
  • completionHandler 必须被调用,演讲在 17:14 明确说明了这个要求。
  • .defer 可用于等待监护人操作,并让屏蔽页更新状态。
  • .close 关闭被屏蔽的 App。

核心启发

1. 学习换娱乐的儿童模式

  • 做什么:儿童使用阅读、学习或创作类 App 满 30 分钟后,自动解锁游戏和视频 App。
  • 为什么值得做:Device Activity Events 可以监听鼓励类 App 的使用阈值,Managed Settings 可以解除之前的屏蔽。
  • 怎么开始:用 Family Activity Picker 让监护人选两组 App;用 DeviceActivityEvent 监控鼓励类 App;在 eventDidReachThreshold 中把 store.shield.applications 设为 nil

2. 固定时段的专注模式

  • 做什么:每天晚饭后到睡前自动屏蔽社交、游戏和短视频 App。
  • 为什么值得做:Device Activity Schedule 可以在重复时间窗口开始和结束时唤起扩展,儿童不用主动打开 App。
  • 怎么开始:创建 DeviceActivitySchedule,设置 repeats: true;在 intervalDidStart 设置屏蔽;在 intervalDidEnd 清空屏蔽。

3. 家庭网页过滤工具

  • 做什么:为儿童设备安装端侧网页内容过滤器,并配合家长规则调整访问范围。
  • 为什么值得做:演讲提到,Family Controls 授权后,App 中的 Network Extensions 网页内容过滤器会自动安装,儿童无法移除。
  • 怎么开始:先加入 Family Controls capability;请求监护人授权;把网页过滤逻辑放进 Network Extensions 扩展,再用 Managed Settings 管理相关限制。

4. 面向内容 App 的年龄分级保护

  • 做什么:视频或电视 App 根据设备上的 Movies 和 TV 限制,隐藏不合适的内容。
  • 为什么值得做:Managed Settings 提供读取 Movies 和 TV 内容限制的 API,演讲说明这种读取不需要 Family Controls 授权。
  • 怎么开始:在媒体 App 启动或刷新内容列表时读取系统媒体限制;用限制结果过滤内容列表,避免展示超过允许年龄的内容。

5. 品牌化的屏蔽页

  • 做什么:把默认 Screen Time 屏蔽页改成带 App 品牌、原因说明和“请求访问”按钮的页面。
  • 为什么值得做:Custom Shields 允许设置背景、图标、标题、副标题和按钮,并让扩展处理按钮动作。
  • 怎么开始:实现 ShieldConfigurationProvider 返回自定义 ShieldConfiguration;实现 ShieldActionHandler 处理主按钮和副按钮;主按钮可返回 .defer 等待监护人确认。

关联 Session

评论

GitHub Issues · utterances