### Start and Stop Device Activity Monitoring Source: https://context7.com/coffeenaerirei/screentime_barebones/llms.txt Manages the creation and lifecycle of device activity monitoring schedules. Use `handleStartDeviceActivityMonitoring` to begin monitoring with a defined schedule and `handleStopDeviceActivityMonitoring` to halt all monitoring. ```swift import DeviceActivity import ManagedSettings class DeviceActivityManager: ObservableObject { static let shared = DeviceActivityManager() private init() {} let deviceActivityCenter = DeviceActivityCenter() // 디바이스 활동 모니터링 시작 func handleStartDeviceActivityMonitoring( startTime: DateComponents, endTime: DateComponents, deviceActivityName: DeviceActivityName = .daily, warningTime: DateComponents = DateComponents(minute: 5) ) { // 스케줄 생성 let schedule = DeviceActivitySchedule( intervalStart: startTime, // 모니터링 시작 시간 intervalEnd: endTime, // 모니터링 종료 시간 repeats: true, // 매일 반복 warningTime: warningTime // 종료 5분 전 경고 ) do { // 지정된 이름으로 모니터링 시작 try deviceActivityCenter.startMonitoring(deviceActivityName, during: schedule) print("모니터링 시작: \(deviceActivityCenter.activities)") } catch { print("모니터링 시작 실패: \(error)") } } // 모든 모니터링 중단 func handleStopDeviceActivityMonitoring() { deviceActivityCenter.stopMonitoring() print("모든 모니터링 중단됨") } } // DeviceActivityName 확장 - 스케줄 이름 정의 extension DeviceActivityName { static let daily = Self("daily") } // ManagedSettingsStore 이름 정의 extension ManagedSettingsStore.Name { static let daily = Self("daily") } // 사용 예시 - ScheduleVM에서 스케줄 저장 class ScheduleVM: ObservableObject { @AppStorage("scheduleStartTime", store: UserDefaults(suiteName: "group.com.example.app")) var scheduleStartTime = Date() @AppStorage("scheduleEndTime", store: UserDefaults(suiteName: "group.com.example.app")) var scheduleEndTime = Date() + 900 // 현재 시간 + 15분 @AppStorage("selection", store: UserDefaults(suiteName: "group.com.example.app")) var selection = FamilyActivitySelection() func saveSchedule(selectedApps: FamilyActivitySelection) { selection = selectedApps let startTime = Calendar.current.dateComponents([.hour, .minute], from: scheduleStartTime) let endTime = Calendar.current.dateComponents([.hour, .minute], from: scheduleEndTime) DeviceActivityManager.shared.handleStartDeviceActivityMonitoring( startTime: startTime, endTime: endTime ) } } ``` -------------------------------- ### Request Screen Time Permission Source: https://github.com/coffeenaerirei/screentime_barebones/blob/main/README.md Request authorization for Screen Time API access. Ensure user has completed manual permission setup before calling this. ```swift // ./ScreenTime_Barebones/Utils/FamilyControlsManager.swift import FamilyControls class FamilyControlsManager: ObservableObject { // MARK: - FamilyControls 권한 상태를 관리하는 객체 let authorizationCenter = AuthorizationCenter.shared // MARK: - ScreenTime 권한 상태를 활용하기 위한 멤버 변수 @Published var hasScreenTimePermission: Bool = false @MainActor func requestAuthorization() { if authorizationCenter.authorizationStatus == .approved { print("ScreenTime Permission approved") } else { Task { do { try await authorizationCenter.requestAuthorization(for: .individual) hasScreenTimePermission = true // 동의함 } catch { // 동의 X print("Failed to enroll Aniyah with error: \(error)") hasScreenTimePermission = false // 사용자가 허용안함. // Error Domain=FamilyControls.FamilyControlsError Code=5 "(null)" } } } } } ``` -------------------------------- ### Handle Schedule Events with DeviceActivityMonitorExtension Source: https://context7.com/coffeenaerirei/screentime_barebones/llms.txt Processes events related to monitoring schedules, such as when a schedule starts or ends. It applies Shields to restrict app usage based on user selections and clears them when the schedule concludes. ```swift import DeviceActivity import ManagedSettings import SwiftUI class DeviceActivityMonitorExtension: DeviceActivityMonitor { let store = ManagedSettingsStore(named: .daily) @StateObject var scheduleVM = ScheduleVM() // 스케줄 시작 시점 이후 처음 기기 사용 시 호출 override func intervalDidStart(for activity: DeviceActivityName) { super.intervalDidStart(for: activity) // 사용자가 선택한 앱/카테고리 토큰 가져오기 let appTokens = scheduleVM.selection.applicationTokens let categoryTokens = scheduleVM.selection.categoryTokens // 선택한 앱들에 Shield(제한) 적용 if appTokens.isEmpty { store.shield.applications = nil } else { store.shield.applications = appTokens } // 카테고리별 제한 적용 store.shield.applicationCategories = ShieldSettings .ActivityCategoryPolicy.specific(categoryTokens) } // 스케줄 종료 시점 이후 기기 사용 시 또는 모니터링 중단 시 호출 override func intervalDidEnd(for activity: DeviceActivityName) { super.intervalDidEnd(for: activity) // 모든 Shield 설정 해제 store.clearAllSettings() } // 이벤트가 임계값에 도달했을 때 호출 override func eventDidReachThreshold( _ event: DeviceActivityEvent.Name, activity: DeviceActivityName ) { super.eventDidReachThreshold(event, activity: activity) // 특정 앱 사용 시간이 설정한 임계값에 도달했을 때 처리 } // 스케줄 시작 전 경고 시점에 호출 override func intervalWillStartWarning(for activity: DeviceActivityName) { super.intervalWillStartWarning(for: activity) // 로컬 알림 전송 등의 처리 가능 } // 스케줄 종료 전 경고 시점에 호출 override func intervalWillEndWarning(for activity: DeviceActivityName) { super.intervalWillEndWarning(for: activity) // 종료 전 사용자에게 알림 전송 } } ``` -------------------------------- ### Enforce App Limits During Schedule Events Source: https://github.com/coffeenaerirei/screentime_barebones/blob/main/README.md Restrict app usage when a schedule is active by applying shields. This code handles the start and end of the scheduled interval. ```swift // ./DeviceActivityMonitor/DeviceActivityMonitorExtension.swift class DeviceActivityMonitorExtension: DeviceActivityMonitor { let store = ManagedSettingsStore(named: .daily) let vm = ScheduleVM() // MARK: - 스케줄의 시작 시점 이후 처음으로 기기가 사용될 때 호출되는 메서드 override func intervalDidStart(for activity: DeviceActivityName) { super.intervalDidStart(for: activity) // Handle the start of the interval. // FamilyActivityPicker로 선택한 앱들에 실드(제한) 적용 let appTokens = vm.selection.applicationTokens let categoryTokens = vm.selection.categoryTokens if appTokens.isEmpty { store.shield.applications = nil } else { store.shield.applications = appTokens } store.shield.applicationCategories = ShieldSettings.ActivityCategoryPolicy.specific(categoryTokens) } // MARK: - 스케줄의 종료 시점 이후 처음으로 기기가 사용될 때 or 모니터링 중단 시에 호출되는 메서드 override func intervalDidEnd(for activity: DeviceActivityName) { super.intervalDidEnd(for: activity) // Handle the end of the interval. // 해당 store에 대해 적용되던 모든 실드 해제 store.clearAllSettings() } } ``` -------------------------------- ### Project Structure Overview Source: https://github.com/coffeenaerirei/screentime_barebones/blob/main/README.md This snippet outlines the directory structure of the ScreenTime_Barebones project, detailing the organization of main targets, extensions, and supporting files. ```swift .\n├── ScreenTime_Barebones // 프로젝트의 메인 타겟입니다.\n│   ├── App\n│   │   ├── ContentView.swift\n│   │   ├── ScreenTime_BarebonesApp.swift\n│   │   └── XCConfig\n│   │   └── shared.xcconfig\n│   ├── Assets.xcassets\n│   │   ├── AccentColor.colorset\n│   │   │   └── Contents.json\n│   │   ├── AppIcon.appiconset\n│   │   │   ├── AppIcon_box.png\n│   │   │   └── Contents.json\n│   │   ├── AppSymbol.imageset\n│   │   │   ├── AppIcon.png\n│   │   │   └── Contents.json\n│   │   └── Contents.json\n│   ├── Extension\n│   │   ├── Bundle+Extension.swift\n│   │   └── Color+Extension.swift\n│   ├── Presentation │   │   ├── ViewModels\n│   │   │   ├── Components\n│   │   │   │   └── MainTabVM.swift\n│   │   │   ├── PermissionVM.swift\n│   │   │   └── ScheduleVM.swift // 스크린타임을 통해 사용시간을 트래킹하는 스케쥴을 관리하기 위한 ViewModel │   │   └── Views\n│   │   ├── Components\n│   │   │   └── MainTabView.swift\n│   │   ├── MonitoringView\n│   │   │   └── MonitoringView.swift\n│   │   ├── PermissionView\n│   │   │   └── PermissionView.swift\n│   │   └── ScheduleView\n│   │   └── ScheduleView.swift\n│   ├── Preview Content\n│   │   └── Preview Assets.xcassets\n│   │   └── Contents.json\n│   ├── ScreenTime_Barebones.entitlements\n│   └── Utils\n│   ├── DeviceActivtyManager.swift\n│   └── FamilyControlsManager.swift\ ­├── DeviceActivityMonitor // 생성한 스크린타임 스케쥴에 기반한 이벤트 발생 시 호출되는 메서드를 관리하기 위한 타겟\n│   ├── DeviceActivityMonitor.entitlements\n│   ├── DeviceActivityMonitorExtension.swift\n│   └── Info.plist\ ­├── ScreenTimeReport // 스크린타임을 통해 사용내역을 조회하고 관리하기 위한 타겟\n│   ├── Info.plist\ │   ├── ScreenTimeActivityReport.swift\ │   ├── ScreenTimeReport.entitlements\ │   ├── ScreenTimeReport.swift\ │   ├── TotalActivityReport.swift\ │   └── TotalActivityView.swift\ ­├── ShieldAction // 스크린타임을 통해 앱 사용이 제한된 화면에서의 이벤트 발생 시 호출되는 메서드를 관리하기 위한 타겟\n│   ├── Info.plist\ │   ├── ShieldAction.entitlements\ │   └── ShieldActionExtension.swift\ ­└── ShieldConfiguration // 스크린타임을 통해 앱 사용이 제한된 화면을 커스텀할 수 있게 도와주는 타겟\n ├── AppSymbol.png\n ├── Info.plist\n ├── ShieldConfiguration.entitlements\n └── ShieldConfigurationExtension.swift ``` -------------------------------- ### ScreenTimeReport Extension Entry Point Source: https://context7.com/coffeenaerirei/screentime_barebones/llms.txt The main entry point for the DeviceActivityReportExtension, defining which report scene to present. ```swift // 메인 리포트 Extension 진입점 @main struct ScreenTimeReport: DeviceActivityReportExtension { var body: some DeviceActivityReportScene { TotalActivityReport { totalActivity in TotalActivityView(activityReport: totalActivity) } } } ``` -------------------------------- ### Create Screen Time Schedule Source: https://github.com/coffeenaerirei/screentime_barebones/blob/main/README.md Create a schedule to monitor app usage during specific times. This involves setting up an interval and a warning time. ```swift // ./ScreenTime_Barebones/Utils/DeviceActivityManager.swift class DeviceActivityManager: ObservableObject { /// DeviceActivityCenter는 설정한 스케줄에 대한 모니터링을 제어해주는 클래스입니다. /// 모니터링 시작 및 중단 등의 동작 처리를 위해 인스턴스를 생성해줍니다. let deviceActivityCenter = DeviceActivityCenter() func handleStartDeviceActivityMonitoring( startTime: DateComponents, endTime: DateComponents, deviceActivityName: DeviceActivityName = .daily, warningTime: DateComponents = DateComponents(minute: 5) ) { let schedule: DeviceActivitySchedule if deviceActivityName == .daily { schedule = DeviceActivitySchedule( intervalStart: startTime, intervalEnd: endTime, repeats: true, warningTime: warningTime ) do { /// deviceActivityName 인자로 받은 이름의 Device Activity에 대해 schedule로 입력받은 기간의 모니터링을 시작합니다. try deviceActivityCenter.startMonitoring(deviceActivityName, during: schedule) /// 디버깅용 주석입니다. /// 현재 모니터링중인 DeviceActivityName과 스케줄을 확인할 수 있습니다. // print("모니터링 시작 --> \(deviceActivityCenter.activities.description)") // print("스케줄 --> \(schedule)") } catch { print("Unexpected error: \(error).") } } } } // MARK: - Schedule Name List extension DeviceActivityName { static let daily = Self("daily") } // MARK: - MAnagedSettingsStore List extension ManagedSettingsStore.Name { static let daily = Self("daily") } ``` -------------------------------- ### TotalActivityReport Scene Implementation Source: https://context7.com/coffeenaerirei/screentime_barebones/llms.txt Implements the DeviceActivityReportScene protocol to process raw device activity data and transform it into a structured ActivityReport model. ```swift // DeviceActivityReportScene 구현 struct TotalActivityReport: DeviceActivityReportScene { let context: DeviceActivityReport.Context = .totalActivity let content: (ActivityReport) -> TotalActivityView // DeviceActivityResults 데이터를 ActivityReport로 변환 func makeConfiguration( representing data: DeviceActivityResults ) async -> ActivityReport { var totalActivityDuration: Double = 0 var list: [AppDeviceActivity] = [] for await eachData in data { for await activitySegment in eachData.activitySegments { for await categoryActivity in activitySegment.categories { for await applicationActivity in categoryActivity.applications { let appName = applicationActivity.application.localizedDisplayName ?? "nil" let bundle = applicationActivity.application.bundleIdentifier ?? "nil" let duration = applicationActivity.totalActivityDuration totalActivityDuration += duration let numberOfPickups = applicationActivity.numberOfPickups let token = applicationActivity.application.token let appActivity = AppDeviceActivity( id: bundle, displayName: appName, duration: duration, numberOfPickups: numberOfPickups, token: token ) list.append(appActivity) } } } } return ActivityReport(totalDuration: totalActivityDuration, apps: list) } } ``` -------------------------------- ### Activity Report Data Model Source: https://context7.com/coffeenaerirei/screentime_barebones/llms.txt Models the overall activity report, containing the total screen time duration and a list of individual app activities. ```swift // 전체 활동 리포트 모델 struct ActivityReport { let totalDuration: TimeInterval // 총 사용 시간 let apps: [AppDeviceActivity] // 앱별 사용 내역 } ``` -------------------------------- ### App Device Activity Data Model Source: https://context7.com/coffeenaerirei/screentime_barebones/llms.txt Models the activity data for a single application, including its display name, usage duration, screen wake count, and application token. ```swift // 앱 활동 데이터 모델 struct AppDeviceActivity: Identifiable { var id: String // 앱 번들 ID var displayName: String // 앱 표시 이름 var duration: TimeInterval // 사용 시간 var numberOfPickups: Int // 화면 깨우기 횟수 var token: ApplicationToken? // 앱 토큰 } ``` -------------------------------- ### TotalActivityView SwiftUI View Source: https://context7.com/coffeenaerirei/screentime_barebones/llms.txt A SwiftUI view that displays the total screen time and a list of apps with their usage details, including duration and wake counts. ```swift // 리포트 뷰 struct TotalActivityView: View { var activityReport: ActivityReport var body: some View { VStack(spacing: 4) { Spacer(minLength: 24) Text("스크린타임 총 사용 시간") .font(.callout) .foregroundColor(.secondary) Text(activityReport.totalDuration.toString()) .font(.largeTitle) .bold() .padding(.bottom, 8) List { Section { ForEach(activityReport.apps) { app in HStack { if let token = app.token { Label(token) .labelStyle(.iconOnly) } Text(app.displayName) Spacer() VStack(alignment: .trailing) { Text("화면 깨우기: \(app.numberOfPickups)회") .font(.footnote) Text(app.duration.toString()) .font(.headline) .bold() } } } } } } } } ``` -------------------------------- ### Define Total Activity Report Context Source: https://context7.com/coffeenaerirei/screentime_barebones/llms.txt Defines a static context for the total activity report, used to identify and retrieve specific report data. ```swift import DeviceActivity import SwiftUI import ManagedSettings import FamilyControls // 리포트 컨텍스트 정의 extension DeviceActivityReport.Context { static let totalActivity = Self("Total Activity") } ``` -------------------------------- ### Persist FamilyActivitySelection using AppStorage Source: https://context7.com/coffeenaerirei/screentime_barebones/llms.txt Extend `FamilyActivitySelection` to conform to `RawRepresentable` for easy persistence using `AppStorage`. This allows saving and loading the selection data as a string, typically JSON encoded. ```swift // FamilyActivitySelection을 AppStorage에 저장하기 위한 확장 extension FamilyActivitySelection: RawRepresentable { public init?(rawValue: String) { guard let data = rawValue.data(using: .utf8), let result = try? JSONDecoder().decode(FamilyActivitySelection.self, from: data) else { return nil } self = result } public var rawValue: String { guard let data = try? JSONEncoder().encode(self), let result = String(data: data, encoding: .utf8) else { return "[]" } return result } } ``` -------------------------------- ### TimeInterval Extension for Formatting Source: https://context7.com/coffeenaerirei/screentime_barebones/llms.txt Extends TimeInterval to provide a custom string representation for time, formatted as HH:MM. ```swift // TimeInterval 확장 - 시간 포맷팅 extension TimeInterval { func toString() -> String { let time = NSInteger(self) let minutes = (time / 60) % 60 let hours = (time / 3600) return String(format: "%0.2d:%0.2d", hours, minutes) } } ``` -------------------------------- ### Request Screen Time API Authorization Source: https://context7.com/coffeenaerirei/screentime_barebones/llms.txt Manages Screen Time API permissions using FamilyControls. Request authorization for individual users and check the current status. Use this when your app needs to interact with Screen Time features. ```swift import FamilyControls class FamilyControlsManager: ObservableObject { static let shared = FamilyControlsManager() private init() {} let authorizationCenter = AuthorizationCenter.shared @Published var hasScreenTimePermission: Bool = false // 스크린타임 API 사용 권한 요청 @MainActor func requestAuthorization() { if authorizationCenter.authorizationStatus == .approved { print("ScreenTime Permission approved") hasScreenTimePermission = true } else { Task { do { // 개인 사용자 모드로 권한 요청 (.child는 자녀 관리용) try await authorizationCenter.requestAuthorization(for: .individual) hasScreenTimePermission = true } catch { print("Failed to request authorization: \(error)") hasScreenTimePermission = false // FamilyControlsError Code=5: 사용자가 권한 거부 } } } } // 현재 권한 상태 조회 func requestAuthorizationStatus() -> AuthorizationStatus { return authorizationCenter.authorizationStatus // .notDetermined: 아직 결정되지 않음 // .denied: 거부됨 // .approved: 승인됨 } // 스크린타임 권한 취소 func requestAuthorizationRevoke() { authorizationCenter.revokeAuthorization { switch result { case .success: print("Authorization revoked successfully") case .failure(let error): print("Failed to revoke: \(error)") } } } } // 사용 예시 struct PermissionView: View { @StateObject private var familyControlsManager = FamilyControlsManager.shared var body: some View { VStack { if familyControlsManager.hasScreenTimePermission { Text("권한이 승인되었습니다") } else { Button("스크린타임 권한 요청") { familyControlsManager.requestAuthorization() } } } } } ``` -------------------------------- ### Display App Selection UI with FamilyActivityPicker Source: https://context7.com/coffeenaerirei/screentime_barebones/llms.txt Use the `familyActivityPicker` modifier in SwiftUI to present a system UI for selecting apps and categories to restrict. The selection is stored in a `FamilyActivitySelection` object. Ensure `ScheduleVM` is an `EnvironmentObject` and `tempSelection` is a `@State` variable of type `FamilyActivitySelection`. ```swift import SwiftUI import FamilyControls struct ScheduleView: View { @EnvironmentObject var scheduleVM: ScheduleVM @State var tempSelection = FamilyActivitySelection() var body: some View { NavigationView { VStack { // 시간 설정 DatePicker("시작 시간", selection: $scheduleVM.scheduleStartTime, displayedComponents: .hourAndMinute) DatePicker("종료 시간", selection: $scheduleVM.scheduleEndTime, displayedComponents: .hourAndMinute) // 선택된 앱 목록 표시 Section(header: Text("제한할 앱")) { if tempSelection.applicationTokens.isEmpty && tempSelection.categoryTokens.isEmpty { Text("앱을 선택하세요") .foregroundColor(.secondary) } else { ForEach(Array(tempSelection.applicationTokens), id: \.self) { token in Label(token) // 앱 아이콘과 이름 표시 } ForEach(Array(tempSelection.categoryTokens), id: \.self) { token in Label(token) // 카테고리 아이콘과 이름 표시 } } } // 앱 선택 버튼 Button("앱 선택하기") { scheduleVM.isFamilyActivitySectionActive = true } // 스케줄 저장 버튼 Button("스케줄 저장") { scheduleVM.saveSchedule(selectedApps: tempSelection) } } // FamilyActivityPicker 시트 연결 .familyActivityPicker( isPresented: $scheduleVM.isFamilyActivitySectionActive, selection: $tempSelection ) } } } ``` -------------------------------- ### Customize Shield View for Restricted Apps Source: https://github.com/coffeenaerirei/screentime_barebones/blob/main/README.md Customize the Screen Time Shield View that appears when an app is restricted. This allows for custom titles, subtitles, and button labels. ```swift // ./ShieldConfiguration/ShieldConfigurationExtension.swift class ShieldConfigurationExtension: ShieldConfigurationDataSource { private func setShieldConfig( _ tokenName: String, hasSecondaryButton: Bool = false) -> ShieldConfiguration { let CUSTOM_ICON = UIImage(named: "AppSymbol.png") let CUSTOM_TITLE = ShieldConfiguration.Label( text: "Screen Time 101", color: ColorManager.accentColor ) let CUSTOM_SUBTITLE = ShieldConfiguration.Label( text: "\(tokenName)은(는) 사용이 제한되었습니다.", color: .black ) let CUSTOM_PRIMARY_BUTTON_LABEL = ShieldConfiguration.Label( text: "종료하기", ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.