안녕하세요
오늘은 Screen Time API중 Family Controls과 ManagedSettings에 대해서 알아보려고합니다.

문서에 써잇는 간단한 소개글을 보면 아래와 같아요
Managed Setting 에서 보호자는 앱을 통해 계정 잠금, 웹 트래픽 필터링, 미디어 접근 제한 등의 작업을 수행할 수 있습니다.
자녀가 기기에서 앱을 실행할 이유가 전혀 없을 수 있으므로, 앱을 실행하지 않고도 기기에서 코드를 실행할 수 있도록 기기 Device Activity 프레임워크를 활용할 수 있습니다.
개인정보 보호를 위해 Family Control 프레임워크는 가족 공유를 통해 앱을 승인하기 위해 보호자의 동의를 요구합니다.
족 공유를 통한 보호자 승인을 요구함으로써, 단일 가족 공유 그룹 외부에서의 무단 정보 접근으로부터 사용자를 보호합니다.
Managed Setting - 계정잠금, 트래픽 필터링, 앱차단 등 기기사용을 보다 세밀하게 제어가능
Family Control - 승인 접근, 앱, 웹 활동제한을 위한 토큰 제공
Device Activity - 앱이 실행되지않아도 코드를 실행시킬 수 있는 방법 제공, 모니터링
간단하게 FamilyControl 프레임워크에서 앱 카테고리 토큰을 받아서
선택한 카테고리 앱들의 실행을 제한하는 간단한 샘플 앱을 만들어보겠습니다
구현시 주의사항으로
실제 디바이스 비번 설정이 안되있다면
authenticationMethodUnavailable 에러를 만나볼 수 있습니다.
(스크린타임 개발 디버깅 불가능)
해결방안으로는 아래와같이 진행하면 됩니다.
1. 설정 > Face ID 및 패스코드 - 패스코드 설정 또는 변경 - Face ID/Touch ID 재설정
2. 설정 > 스크린 타임 - 스크린 타임 켜기 - 스크린 타임 패스코드 설정 - 앱 및 웹사이트 활동 허용
Family Controls
FamilyControl사용을 위해서는
Capability에서 Family Control 을 먼저 추가한뒤
코드적으로도 사용자 승인을 받아야합니다
let authCenter = AuthorizationCenter.shared
func checkAuth() {
Task {
do {
try await authCenter.requestAuthorization(for: .individual)
} catch {
print("Fail: \(error)")
}
}
}
FamilyActivitySelection
.familyActivityPicker 모디파이어를 사용하여 기기내의 앱 리스트를 보여줍니다.
차단할 카테고리 세부앱들은 시뮬레이터에서는 확인이 불가능하여 카테고리 전체만 차단가능하고
실기기에선 각 앱별로 선택하여 차단할 수도 있습니다.
import FamilyControls
struct SomeView: View {
@State var selection = FamilyActivitySelection()
@State var isPresented = false
var body: some View {
SomeView2()
.familyActivityPicker(isPresented: $isPresented, selection: $selection)
.onChange(of: selection) { old, new in
let applications = new.applications
let categories = new.categories
let webDomains = new.webDomains
print(applications, categories, webDomains)
}
}
}

사용자의 개인 정보를 보호하기 위해 FamilyActivitySelection은 사용자가 선택한 범주, 애플리케이션 및 웹 도메인을 나타내는 불투명한 값을 보유합니다.
그런 다음 이러한 불투명한 값을 ManagedSettings 및 DeviceActivity 프레임워크의 인스턴스와 메서드에 전달하여 보호 기능을 설정하고 관리할 수 있습니다.
extension에서 사용할 것이라면 appGroup을 이용해서 이값을 저장하고 읽어오면 됩니다.
UserDefault에 타입을 바로 저장할수는 없으니 Data형식으로 인코딩해서 저장하고 디코딩해서 읽는 방식을 사용합니다.
인코딩 디코딩 하는 예시코드
struct AppModel: Codable {
init(selection: FamilyActivitySelection) {
self.selection = selection
}
let selection: FamilyActivitySelection
}
func setShieldRestrictions() {
let applications = selectionToDiscourage
do {
let model = AppModel(selection: applications)
let appData = try JSONEncoder().encode(model)
userDefaults.set(appData, forKey: "testKey")
} catch {
print(error)
}
}
func loadSelectedApps() -> FamilyActivitySelection? {
let userDefaults = UserDefaults(suiteName: "group.test.familyControl")
guard let data = userDefaults?.data(forKey: "testKey") else {
return nil
}
do {
let model = try JSONDecoder().decode(AppModel.self, from: data)
print(model)
return model.selection
} catch {
print("앱 목록 로드 실패: \(error)")
return nil
}
}
Managed Settings
ManagedSettingsStore
현재 사용자 또는 장치에 설정을 적용하는 데이터 저장소.
관리 설정 데이터 저장소는 기능을 기준으로 설정을 그룹화합니다.
각 그룹은 관련 설정(예: 기본값, 가능한 최소값 및 최대값)에 대한 관련 데이터를 포함합니다.
FamilyActivitySelection타입으로 선택한값을 ManagedSettingsStore에 전달합니다
import ManagedSettings
@Observable
final class ManagedModel {
private let store = ManagedSettingsStore()
var selectionToDiscourage: FamilyActivitySelection = .init()
func setShieldRestrictions() {
let applications = selectionToDiscourage
store.shield.applications = applications.applicationTokens.isEmpty ? nil : applications.applicationTokens
store.shield.applicationCategories = applications.categoryTokens.isEmpty
? nil
: .specific(applications.categoryTokens)
}
}
store.shield(ShieldSettings타입) 을통해서 앱이나 웹사이트를 제한 할 수 있습니다.
Shield
앱이 차단되는 화면을 커스텀하고싶으면 addTarget에서 아래중 원하는것을 선택하면됩니다.

Shield Configuration - ShieldConfigurationDataSource
차단시 UI설정
override func configuration(shielding application: Application) -> ShieldConfiguration {
// Customize the shield as needed for applications.
ShieldConfiguration(
backgroundBlurStyle: .dark,
backgroundColor: .black,
icon: nil,
title: .init(text: "앱 차단⚠️!!", color: .systemPink),
subtitle: .init(text: "서브타이틀임", color: .systemPink),
primaryButtonLabel: .init(text: "primary버튼", color: .systemPink),
primaryButtonBackgroundColor: .black,
secondaryButtonLabel: .init(text: "secondary버튼", color: .systemPink)
)
}
override func configuration(shielding application: Application, in category: ActivityCategory) -> ShieldConfiguration {
// Customize the shield as needed for applications shielded because of their category.
ShieldConfiguration(
backgroundBlurStyle: .dark,
backgroundColor: .black,
icon: nil,
title: .init(text: "카테고리 차단⚠️!!", color: .systemPink),
subtitle: .init(text: "서브타이틀임\(category.localizedDisplayName)", color: .systemPink),
primaryButtonLabel: .init(text: "primary버튼", color: .systemPink),
primaryButtonBackgroundColor: .black,
secondaryButtonLabel: .init(text: "secondary버튼", color: .systemPink)
)
}


CGV앱을 제한걸고(시뮬에서 테스트불가능해서 실제폰에서 테스트) CGV앱을 실행시키면 앱 차단이 노출됩니다.
이와 다르게 생산성 및 금융이라는 카테고리를 제한시키고 앱을 실행시키면 카테고리 차단이 노출됩니다.
카테고리가 더 상위개념으로 카테고리를 차단하면 앱차단보다 우선순위로 노출됩니다.
Shield Action Extension - ShieldActionDelegate
차단시 각 버튼에 따른 이벤트 처리
override func handle(action: ShieldAction, for application: ApplicationToken, completionHandler: @escaping (ShieldActionResponse) -> Void) {
// Handle the action as needed.
switch action {
case .primaryButtonPressed:
completionHandler(.close)
case .secondaryButtonPressed:
completionHandler(.defer)
@unknown default:
fatalError()
}
}
defer은 버튼을 눌러도 아무동작이 없습니다. 참고하세요
'iyOmSd > Title: Swift' 카테고리의 다른 글
| [Swift] Vision FaceDetect (0) | 2025.11.29 |
|---|---|
| [Swift] Screen Time API(DeviceActivity) (0) | 2025.10.27 |
| [Swift] WWDC25 Explore concurrency in SwiftUI (2) | 2025.08.28 |
| [Swift] WWDC25 Wake up to the AlarmKit API (3) | 2025.07.22 |
| [Swift] Vision OCR 인식 (0) | 2025.06.25 |