본문 바로가기

IOS Swift

What's new in Screen Time API (2) - WWDC2022

안녕하세요. Hong입니다. 🐾

 

Screen Time API - WWDC2021

안녕하세요. Hong입니다. 🐶 WWDC21의 Meet ther Screen Time 영상 보면서 나의 앱에서 다른 앱의 사용 시간제한을 설정할 수 있다길래 신기해서 바로 구현해 봤습니다. 목차 WWDC 영상으로 Screen Time API 학

hongz-developer.tistory.com

이전 포스팅 Screen Time API (1) - WWDC2021에 이어서

WWDC22에 Screen Time API 추가된 것들이 있어서 바로 살펴보겠습니다. +_+


2021 Screen Time API 간단한 리마인드 

Screen Time API는 세 가지 프레임워크로 이루어져 있습니다.

1️⃣ Managed Settings 

앱에서 iOS 스크린타임과 마찬가지로 제한 사항을 적용하고, 웹 트래픽을 필터링하고 활동을 보호할 수 있고 또한 앱의 브랜딩 및 기능에 맞게 커스텀할 수 있습니다.

 

2️⃣ Family Controls

Screen Time API에 대한 접근을 승인하므로 게이트웨이와 같은 역할을 합니다. 

자녀 보호 앱이 제거되는 것을 방지할 수 있고 앱과 웹사이트를 식별하는 데 사용되는 불투명 토큰을 제공하여 사용자 개인 정보를 보호할 수 있습니다.

 

3️⃣ Device Activity

타이머의 시간 범위뿐 아니라 앱이나 웹사이트의 사용량이 임계값을 초과할 때마다 코드를 실행할 수 있습니다. 

앱이 종료되어 있으면 코드를 실행시킬 수가 없습니다. 그러나 Device Activity를 이용하면 앱이 실행 상태가 아닐 때도 특정 시간 범위 or 사용량의 임계값을 초과할 때마다 코드를 실행시킬 수 있습니다.

 


WWDC22 Screen Time API 새로운 변화

 

Family Controls

  • iOS 15 -> iCloud 인증을 통해서만 자녀 기기 인증만 가능
  • iOS 16 -> 가족 보호 기능을 통해 자신의 기기에서 독립적인 유저 인증 가능해짐. 또한 독립적인 유저 인증은 자녀 보호 기능 사용 사례가 아니기 때문에  iCloud 로그아웃 및 앱 삭제에 대한 암시적 제한이 적용되지 않습니다.

기존 자녀 보호 기능 승인과 달리 독립적인 유저 인증은 기기당 여러 앱에서 사용할 수 있습니다.

자세히 알아보겠습니다.


인증 요청

// APP: Request Authorization

import SwiftUI
import FamilyControls

@main
struct Worklog: App {
    let center = AuthorizationCenter.shared
    var body: some Scene {
        WindowGroup {
            VStack {…}
                .onAppear {
                    Task {
                        do {
                            try await center.requestAuthorization(for: .individual)
                        } catch {
                            print("Failed to enroll Aniyah with error: \(error)")
                        }
                    }
                }
        }
    }

 

위의 iOS 16의 독립적인 인증을 하고 나면

 

앱 설정에 두 개의 스위치가 추가됩니다.

하나는 스크린타임 액세스 목록이 있는 왼쪽 이미지이고

다른 하나는 스크린타임 제한으로 표시된 앱 설정인 오른쪽 이미지입니다.

부모와 개인 사용자는 이러한 스위치 중 하나를 통해 가족 제어에서 앱 승인을 취소하도록 선택할 수 있습니다.

이렇게 자녀 기기가 아니어도 자신의 기기에서 Screen Time API를 활용할 수 있어서

자녀 보호 앱뿐만 아니라 다른 앱들도 이러한 기능을 이용해 다채로운 서비스를 제공할 수 있습니다.


Managed Settings Restrictions

개발자가 쉽게 사용할 수 있도록 관리 설정이 개선되었습니다. 잘 모르시는 분들을 위해 말씀드리자면 관리 설정 저장소는 현재 사용자나 장치에 설정을 적용하는 데이터 저장소입니다.

iOS 15 -> 프로세스 하나당 하나의 관리 설정 저장소만 가질 수 있었습니다. 또한 앱과 기기의 활동 확장 프로그램에는 서로 다른 관리 설정 저장소가 있어야 했습니다.

이로 인해 장치 활동에 따라 설정을 변경하기가 어려웠습니다.

이제 iOS 16에서는 각각 고유한 이름을 가진 관리 설정 저장소를 프로세스당 최대 50개까지 생성할 수 있습니다. 이러한 명명된 스토어는 앱과 모든 앱 확장 간 자동으로 공유됩니다. 또한 이제 특정 명명된 저장소의 모든 설정을 한 번에 제거할 수 있습니다.

예시를 보겠습니다.

// App: Setup the default restrictions for our app

import ManagedSettings

extension ManagedSettingsStore.Name {
	static let gaming = Self("gaming") 
    static let social = Self("social")
}

func worklogSetup() {
	let gamingCategory = ActivityCategoryToken(...)
	let gamingStore = ManagedSettingsStore(named: .gaming)
    gamingStore.shield.webDomainCategories = .specific([gamingCategory])
    
    let socialCategory = ActivityCategoryToken(...)
    let socialStore = ManagedSettingsStore(named: .social)
    // 사용 제한거는 코드
    socialStore.shield.applicationCategories = .specific([socialCategory])
    socialStore.shield.webDomainCategories = .specific([socialCategory])
}

앱이 실행되고 Screen Time App 권한이 승인이 완료되면 gaming, social 저장소가 만들어집니다.

이 저장소는 게임과 소셜에 대한 제한 관련한 것들이 포함될 것입니다.

 

특정 시간 범위(10시 ~ 12시)에 앱 사용 제한을 걸거나 풀려면?

 

DeviceActivity 프레임워크 사용

import SwiftUI
import DeviceActivity

extension DeviceActivityName {
    static let activity = Self("activity")
}

struct Worklog: App {
    let deviceActivityCenter = DeviceActivityCenter()
    var body: some Scene {
        VStack {
            Text("Social Media")
            
            Button("Allow for Evenings", action: {
                try? deviceActivityCenter.startMonitoring(.activity,
                                                          during: DeviceActivitySchedule(
                                                            intervalStart: DateComponents(hour: 17),
                                                            intervalEnd: DateComponents(hour: 20),
                                                            repeats: true
                                                          )
                )
            })
        }
    }
}

 startMonitor를 해주면 17시에 intervalStart 20시에 intervalEnd의 앱 확장(DeviceActivityMonitor) 코드를 호출하게 됩니다.

아래 Monitor의 intervalDidStart, intervalDidEnd 부분이 호출되는 것이죠.

// MONITOR EXTENSION: Handle Social category at start/end of interval

import DeviceActivity
import ManagedSettings

class WorklogMonitor: DeviceActivityMonitor {
    let database = BarkDatabase() // -> 앱에서 직접 구현해야함
    
    override func intervalDidStart(for activity: DeviceActivityName) {
        super.intervalDidStart(for: activity)
        let socialStore = ManagedSettingsStore(named: .social)
        socialStore.clearAllSettings()
    }
    
    override func intervalDidEnd(for activity: DeviceActivityName) {
        super.intervalDidEnd(for: activity)
        let socialStore = ManagedSettingsStore(named: .social)
        let socialCategory = database.socialCategoryToken
        // database에 저장된 불투명 토큰을 가져와 다시 사용 제한을 걸음
        socialStore.shield.applicationCategories = .specific([socialCategory])
        socialStore.shield.webDomainCategories = .specific([socialCategory])
    }
}

ㅎㅎ 그런데 이 부분 직접 구현해 보면 꽤나 골치 아픕니다. 🥲 그 이유는..

1. 디버깅이 힘듭니다.

더보기

 

DeviceActivityMonitor는 본 앱(AppLocker)과 다른 프로세스로 xcode에 print문 같은 로그가 안 나옵니다.

-> 그러면 DeviceActivityMonitor 프로세스도 로그를 볼 수 있게 추가하면 되지 않을까요? 맞습니다. Debug -> Attach to Process 해주면 다른 프로세스의 로그를 볼 수 있습니다.

근데 저는 이 방법 잘 안되더라고요. 해볼 때마다Debug -> Attach to Process 하는 것도 

시간 많이 걸리고 해서

로컬 푸시 알림으로 디버깅했습니다.

 

2. 앱과 앱 확장 간 불투명 토큰을 공유하기 위한 저장 영역을 따로 구현해야 합니다.

더보기

 

위의 애플 예제 코드 보면 딱 이렇게만 나와 있고, BarkDatabase에 대해 참고할 수 있을만한 게 없습니다.

즉, 예제의 BarkDatabase처럼 불투명 토큰을 저장할 영역을 따로 만들어야 한다는 것입니다.

그런데 제가 아까 DeviceActivity는 본 앱과 다른 하나의 프로세스라고 했는데 그렇다면 서로 다른 프로세스가 같은 영역을 공유한다는 게 가능한 일인가 궁금할 것입니다. 카카오톡앱에서 네이버앱의 로컬 저장소의 데이터를 사용한다는 건 말이 안 되는 일이니까요.

이것을 위해 Apple은 App Group이란 것을 만들었습니다. 이것을 설정해 주면 앱과 앱 확장 간 데이터 공유를 할 수 있습니다.

이러한 저장 영역을 본 앱에서 구현하고 그곳에 불투명 토큰 저장하고 DeviceActivity에서 저장된 불투명 토큰을 꺼내서 앱 제한 거는 데 사용하면 됩니다.

 

본 앱에서 불투명 토큰 저장하기

func saveSelection() {
    let documentsDirectory = FileManager().containerURL(forSecurityApplicationGroupIdentifier: "group.HongzCloud.AppLocker")

    let archiveURL = documentsDirectory?.appendingPathComponent("selection.plist")

    let encoder = PropertyListEncoder()

    if let dataToSave = try? encoder.encode(selectionToDiscourage) {
        do {
            try dataToSave.write(to: archiveURL!)
            print("저장 성공")
        } catch {
            print("Error: \(error.localizedDescription)")
        }
   }
}

 

DeviceActivityMonitor에서 불투명 토큰 꺼내기

func getSelection() -> FamilyActivitySelection {
    let documentsDirectory = FileManager().containerURL(forSecurityApplicationGroupIdentifier: "group.HongzCloud.AppLocker")
        
    guard let archiveURL = documentsDirectory?.appendingPathComponent("selection.plist") else {
        print("FamilyActivitySelection 가져오기 실패: selection.plist 없음")
        return FamilyActivitySelection()
    }
        
    guard let codeData = try? Data(contentsOf: archiveURL) else {
        print("FamilyActivitySelection 가져오기 실패: dodeData 없음")
           
        return FamilyActivitySelection()
    }
        
    print("FamilyActivitySelection 가져오기 성공")
        
    let decoder = PropertyListDecoder()
    let loadedSelection = (try! decoder.decode(FamilyActivitySelection.self, from: codeData))
    print("loadedSelection: \(loadedSelection.applicationTokens)")
        
    return loadedSelection
}

 


Device Activity

  • iOS 15 -> Device Activity를 통해 시간 범위 사용 제한은 물론 앱과 웹 사이트의 사용 임계값에 응답할 수 있었습니다.
  • iOS 16 -> SwiftUI를 사용하여 커스텀화된 Device Activity Report를 생성할 수 있게 되었습니다.

// App: Top-level view

import SwiftUI
import DeviceActivity

extension DeviceActivityReport.Context {
	static let pieChart = Self("Pie chart")
}

@main
struct Worklog: App {
	private let thisWeek = DateInterval(...)
    @State private var context: DeviceActivityReport.Context = .pieChart
    @State private var filter = DeviceActivityFilter(segment: .daily(during: thisWeek))
    
    var body: some Scene {
    WindowGroup {
		GeometryReader { geometry in
        	VStack(alignment: .leading) {
            	DeviceActivityReport(context: context, filter: filter)
                	.frame(height: geometry.size.height * 0.75)
		}
	}
}

DeviceActivityReport.Context 및 DeviceActivityFilter를 설정하는 코드입니다.

DeviceActivity.Context는 DeviceActivity 데이터를 기반으로 그릴 View 유형을 보고서에 알려주는 사용자 지정 가능한 유형입니다.

DeviceActivityFilter를 지정하여 보고서 콘텍스트의 타이밍 창을 커스텀화 할 수 있습니다. 그런 다음 장면에 무엇을 나타낼지 알려주기 위해 DeviceActivityReport Scene 내부에 장치 활동 보고서 컨텍스트의 정의를 설정합니다.

// REPORT EXTENSION: Configure Custom Device Activity Report

import SwiftUI
import DeviceActivity

struct PieChartReport: DeviceActivityReportScene {
    let context: DeviceActivityReport.Context = .pieChart
    let content: (PieChartView.Configuration) -> PieChartView
    
    func makeConfiguration(representing data: [DeviceActivityData]) 
        -> PieChartView.Configuration {
        var totalUsageByCategory: [ActivityCategory:TimeInterval]
        totalUsageByCategory = data.map(…)
        
        return PieChartView.Configuration(totalUsageByCategory: totalUsageByCategory)
    }
}

content는 커스텀한 설정과, PieChartView.Configuration을 Swift UI View 결과로 보이도록 정의합니다.

그리고 makeConfiguration은 원형 차트 뷰의 구성을 보여주기 위해 DeviceActivity 데이터를 맵핑합니다.

프레임워크는 새로운 사용 데이터를 가져올 때마다 해당 메서드를 호출하기에 직접 호출할 필요는 없습니다.

 

// REPORT EXTENSION: Configure Custom Device Activity Report

import SwiftUI
import DeviceActivity

struct PieChartView: View {

    // pie chart view를 위해 화면 데이터를 제공
    struct Configuration {
        let totalUsageByCategory: [ActivityCategory:TimeInterval]
    }
    
    let configuration: Configuration
    
    var body: some View {
        // A complex view that renders a bar graph based on Aniyah’s usage per category.
        PieChart(usage: configuration.totalUsageByCategory)
    }
}

 

이렇게 PieChartView를 만들어졌습니다.

그리고 이렇게 만든 것을 아래의 DeviceActivityReportExtension을 통해 커스텀화된 SwiftUI 보고서를 렌더링 하면 됩니다.

// REPORT EXTENSION: Draw Custom Device Activity Report

import SwiftUI
import DeviceActivity

@main
struct WorklogReportExtension: DeviceActivityReportExtension {
    var body: some DeviceActivityReportScene {
        PieChartReport { configuration in
            PieChartView(configuration: configuration)
        }
    }
}

 

 

완료

 


참고

- Screen Time API 사용해 보기 전체 코드: https://github.com/HongzCloud/AppLocker

반응형