ram2 ๐Ÿš—

[swift] DeviceActivity ์ •๋ฆฌ ๋ฐ ์Šคํฌ๋ฆฐํƒ€์ž„ ์ ‘๊ทผ ๊ถŒํ•œ, ์•ฑ ์ œํ•œํ•˜๊ธฐ ๋ณธ๋ฌธ

๐ŸŽ Swift

[swift] DeviceActivity ์ •๋ฆฌ ๋ฐ ์Šคํฌ๋ฆฐํƒ€์ž„ ์ ‘๊ทผ ๊ถŒํ•œ, ์•ฑ ์ œํ•œํ•˜๊ธฐ

coram22 2024. 9. 12. 16:34
728x90
๋ฐ˜์‘ํ˜•

์ง€๋‚œ ๊ธ€์— ์ด์–ด ์ด๋ฒˆ์—๋Š” DeviceActivity๋ฅผ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ๊ธ€์ด๋‹ค.

๋‹ค์Œ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์˜€๋‹ค !

https://developer.apple.com/documentation/DeviceActivity

 

DeviceActivity | Apple Developer Documentation

Monitor device activity with your app extension while maintaining user privacy.

developer.apple.com

 

์ด๋ฒˆ ๊ธ€์€ ๋‚ด๊ฐ€ ์ง„ํ–‰ ์ค‘์ธ ํ”„๋กœ์ ํŠธ์—์„œ ์ƒ๋‹นํžˆ ์ค‘์š”ํ•œ ์ฃผ๊ธฐ๋Šฅ์— ๊ผญ ํ•„์š”ํ•œ framework์ด๋ฏ€๋กœ, ์ฐจ๊ทผ์ฐจ๊ทผ ๋”ฐ๋ผํ•˜๋ฉด์„œ ํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค.

๊ทธ ์ „์—, ์ด framework๋Š” ์•ฑ ํ™•์žฅ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ด ์‚ฌ์šฉ์ž์˜ ํ”„๋ผ์ด๋ฒ„์‹œ๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ, ๊ธฐ๊ธฐ ํ™œ๋™์„ ๋ชจ๋‹ˆํ„ฐ๋ง ํ•œ๋‹ค.

 

์ด์ „ ๊ธ€๊ณผ ์กฐ๊ธˆ ๊ฒน์น˜๋Š” ๋ถ€๋ถ„์ด ์—†์ž–์•„ ์žˆ์ง€๋งŒ,

1. ํŠน์ • ์‹œ๊ฐ„๋™์•ˆ ํ™œ๋™์„ ๋ชจ๋‹ˆํ„ฐ๋ง ํ•  ์ˆ˜ ์žˆ๋‹ค.

2. ํ™œ๋™์ด ๋ฏธ๋ฆฌ ์„ค์ •๋œ ํ•œ๊ณ„์— ๋„๋‹ฌํ•  ๋•Œ ๊ฒฝ๊ณ ๋ฅผ ์ค„ ์ˆ˜ ์žˆ๋‹ค.

3. ์•ฑ๊ณผ ์›น์‚ฌ์ดํŠธ์—์„œ ์‚ฌ์šฉ๋œ ์‹œ๊ฐ„์„ ๋ชจ๋‹ˆํ„ฐ๋ง ํ•œ๋‹ค.

 

์ด์ œ ๊ฐ๊ฐ์˜ Topic์„ ํ•˜๋‚˜์”ฉ ๋‹ค๋ค„๋ณด์ž !

 


 

Manage Activities

  • struct DeviceActivityEvent
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜, ์นดํ…Œ๊ณ ๋ฆฌ ๋˜๋Š” ์›น์‚ฌ์ดํŠธ ํ™œ๋™์„ ๋‚˜ํƒ€๋‚ด๋Š” ์ด๋ฒคํŠธ
  • struct DeviceActivityName
    • ํ™œ๋™์˜ ๊ณ ์œ ํ•œ ์ด๋ฆ„์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.
  • struct DeviceActivitySchedule
    • ๊ธฐ๊ธฐ ํ™œ๋™์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•  ๋•Œ์˜ ์ผ์ • ๊ธฐ๋ฐ˜ ์Šค์ผ€์ค„์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.
  • struct DeviceActivityCenter
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ™•์žฅ์ด ์ผ์ •์— ๋”ฐ๋ผ ๊ธฐ๊ธฐ ํ™œ๋™ ๋ชจ๋‹ˆํ„ฐ๋ง์„ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ํด๋ž˜์Šค

 

Monitor Activity

  • class DeviceActivityMonitor
    • ์ผ์ •์— ๋”ฐ๋ผ ๊ธฐ๊ธฐ ํ™œ๋™์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” ๊ฐ์ฒด

 

Errors

  • enum MonitoringError
    • ํ™œ๋™ ๋ชจ๋‹ˆํ„ฐ๋ง์„ ์‹œ์ž‘ํ•  ๋•Œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์˜ค๋ฅ˜๋“ค์„ ๋‚˜์—ดํ•˜๋Š” ์—ด๊ฑฐํ˜•

 

Classes

  • class DeviceActivityAuthorization
    • ๊ธฐ๊ธฐ ํ™œ๋™ ๋ชจ๋‹ˆํ„ฐ๋ง์„ ์œ„ํ•œ ๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํด๋ž˜์Šค์ด๋‹ค.

 

Protocols

  • protocol DeviceActivityAuthorizing
    • ๊ธฐ๊ธฐ ํ™œ๋™ ๋ชจ๋‹ˆํ„ฐ๋ง์— ๋Œ€ํ•œ ๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ์ •์˜ํ•˜๋Š” ํ”„๋กœํ† ์ฝœ์ด๋‹ค.
  • protocol DeviceActivityReportExtension
    • ๊ธฐ๊ธฐ ํ™œ๋™ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๊ณ ํ•˜๋Š” ์•ฑ extension์ด๋‹ค.
  • protocol DeviceActivityReportScene
    • ์‚ฌ์šฉ์ž ์ •์˜ ๊ธฐ๊ธฐ ํ™œ๋™ report scene์„ ์ •์˜ํ•˜๋Š” ํ”„๋กœํ† ์ฝœ์ด๋‹ค.

 

Structures

  • struct DeviceActivityData
    • ํŠน์ • DeviceActivityData.Device์—์„œ DeviceActivityData.User์˜ ํ™œ๋™์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.
  • struct DeviceActivityFilter
    • ๋ณด๊ณ ์„œ์— ํฌํ•จํ•  ๊ธฐ๊ธฐ ํ™œ๋™ ๋ฐ์ดํ„ฐ๋ฅผ ํ•„ํ„ฐ๋งํ•˜๋Š” ํƒ€์ž….
  • struct DeviceActivityReport
    • ์‚ฌ์šฉ์ž์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜, ์นดํ…Œ๊ณ ๋ฆฌ ๋ฐ ์›น ๋„๋ฉ”์ธ ํ™œ๋™์„ ํ”„๋ผ์ด๋ฒ„์‹œ๋ฅผ ๋ณดํ˜ธํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ณด๊ณ ํ•˜๋Š” view์ด๋‹ค.
  • struct DeviceActivityReportBuilder
    • ํ•˜๋‚˜ ์ด์ƒ์˜ DeviceActivityReportScenes์„ ๊ฒฐํ•ฉํ•˜์—ฌ ๋‹จ์ผ ์žฅ๋ฉด์„ ์ƒ์„ฑํ•˜๋Š” ๊ฒฐ๊ณผ ๋นŒ๋”.
  • struct DeviceActivityResults
    • ํ•„ํ„ฐ๋ง๋œ ๊ธฐ๊ธฐ ํ™œ๋™ ๊ฒฐ๊ณผ์˜ ๋น„๋™๊ธฐ sequence.

 

 

ํ”„๋กœ์ ํŠธ์— ๋ฐ˜์˜ํ•˜๊ธฐ

์ด์ œ xcode์— ํ”„๋กœ์ ํŠธ๋ฅผ ํŒŒ๊ณ , ํ•ด๋‹น api๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๋Š” ๊ณผ์ •์„ ์ •๋ฆฌํ•˜๋ ค ํ•œ๋‹ค.

 

๋จผ์ €, ๊ด€๋ จ๋œ extension์„ ์„ค์น˜ํ•ด์ค€๋‹ค.

 

ํƒ€๊นƒ์—์„œ ํ•˜๋‹จ์˜ + ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ add target์„ ํ•ด์ค€๋‹ค.

 

 

๊ทธ๋Ÿผ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŒ์—…์ด ๋œจ๋Š”๋ฐ, ์ด ๋•Œ DeviceActivity Monitor Extension๊ณผ Device Activity Report Extension์„ ๊ฐ๊ฐ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

 

์ด๋ ‡๊ฒŒ ๋‘ ๊ฐœ์˜ extension์„ ๋ชจ๋‘ ์ถ”๊ฐ€ํ–ˆ๋‹ค๋ฉด, ์ด๋ ‡๊ฒŒ xcode์— ์ถ”๊ฐ€๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.



 

์ด์ œ extension์„ ์ถ”๊ฐ€ํ•˜์˜€์œผ๋ฉด, ๋‚˜์™€ ๊ฐ™์€ ๊ฒฝ์šฐ, ๋Œ€๋ถ€๋ถ„์˜ ์ฝ”๋“œ๋ฅผ UIKit์œผ๋กœ ์งค ์˜ˆ์ •์ด๊ธฐ์— SwiftUI ๊ธฐ๋ฐ˜์˜ api์™€ UIKit์„ ์„œ๋กœ ์—ฐ๊ฒฐํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

์ฆ‰, ๋‘ ๊ฐœ์˜ framework๋ฅผ ํ˜ผ์šฉํ•ด์„œ ์“ธ ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ์„ ์„ธํŒ… ํ•ด์•ผ ํ•œ๋‹ค.

 

 

UIKit๊ณผ SwiftUI ํ˜ผ์šฉํ•˜๊ธฐ

UIHostingController๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

ํ•˜์ง€๋งŒ, ์•„๋ฌด๋ฆฌ ์‚ฌ์šฉํ•ด์„œ ์—ฐ๊ฒฐ์„ ํ•ด๋„, ์ž๊พธ ์‚ฌ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” TotalActivityView๊ฐ€ scope์— ์—†๋‹ค๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ ๋ณด์˜€๋‹ค.

๊ณ„์† ์„œ์น˜ํ•˜๊ณ , ์‚ฝ์งˆ์„ ํ•˜๋‹ค ๋ณด๋‹ˆ,, ๋‚ด๊ฐ€ extension๋“ค์„ add target์œผ๋กœ ํ•ด์คฌ์œผ๋ฏ€๋กœ,, ์–˜๋„ค๋“ค์„ ๋ชจ๋‘ ํƒ€๊นƒ์— ์ถ”๊ฐ€ํ•ด์ค˜์•ผ ํ–ˆ๋‹ค.

 

์ง€๋‚œ ํ”„๋กœ์ ํŠธ ๋‹น์‹œ์—๋„ ์ด๊ฒƒ๋•Œ๋ฌธ์— ์‚ฝ์งˆ์„ ๊ฝค ํ–ˆ์—ˆ๋Š”๋ฐ.. ์•„์ฐจ์ฐจ ์ด๊ฑธ ๋˜ ์žŠ์—ˆ๋‹ค๋‹ˆ..

 

์˜ค๋ž˜ ์‚ฝ์งˆ ์•ˆํ•˜๊ณ  ์ง€๊ธˆ์ด๋ผ๋„ ์•Œ์•˜์œผ๋‹ˆ ๋‹คํ–‰์ด๋‹ค.

๋จผ์ €, viewController์˜ ์˜ค๋ฅธ์ชฝ inspector์—์„œ target์— ActivityReport๋ฅผ ์ฒดํฌํ•ด์ฃผ์—ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ , TotalActivityView์—์„œ๋„ ๋ฉ”์ธ ํ”„๋กœ์ ํŠธ ํƒ€๊นƒ์„ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ๋‹ค.

 

 

๊ทธ๋žฌ๋”๋‹ˆ, ์ด์ œ TotalActivityView๋ฅผ ์ž˜ ์ฐพ์•„ ๋นŒ๋“œ๊ฐ€ ์„ฑ๊ณตํ–ˆ๋‹ค.

๋งŒ์•ฝ ์•ˆ๋œ๋‹ค๋ฉด, ์ƒ๋‹จ์˜ Product -> Clean Build Folder๋ฅผ ๊ผญ ํ•˜๊ธธ!!

 

์•„์ฐจ์ฐจ ๊ทธ๋Ÿฐ๋ฐ ํ™”๋ฉด์—๋Š” ์•„๋ฌด๊ฒƒ๋„ ๋ณด์ด์ง€ ์•Š์•˜๋Š”๋ฐ, ๊ทธ ์ด์œ ๊ฐ€ SceneDelegate.swift ํŒŒ์ผ์„ ์ˆ˜์ •ํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค..

ํ˜„์žฌ๋Š” StoryBoard๋กœ ์—ฐ๊ฒฐ๋˜๋„๋ก ๋ผ ์žˆ์–ด ์ด๊ฑธ ๋ฐ”๊ฟ”์ค˜์•ผ ํ•œ๋‹ค.

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let windowScene = (scene as? UIWindowScene) else { return }

    let window = UIWindow(windowScene: windowScene)

    let viewController = MainViewController()

    window.rootViewController = viewController

    window.makeKeyAndVisible()

    self.window = window
}

 

์ด๋ ‡๊ฒŒ ํ•˜๊ณ  ์‹คํ–‰ํ•˜๋‹ˆ ํ™”๋ฉด์ด ์ž˜ ์—ฐ๊ฒฐ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค !

 

์ง€๊ธˆ์€ ์ž๋™์œผ๋กœ ์ œ๊ณต๋˜๋Š” view๋งŒ ์—ฐ๊ฒฐํ–ˆ๋Š”๋ฐ, ๊ทธ๋ ‡๊ธฐ์— ์‹ค์ œ ๊ธฐ๊ธฐ์—์„œ ์‚ฌ์šฉํ•œ ์•ฑ์— ๋Œ€ํ•œ ์‹œ๊ฐ„๋“ค์ด ๋‚˜์˜ค๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ, ๋‚ด๊ฐ€ ํ•ด๋‹น VC๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ๋„ฃ์€ argument ๊ฐ’์ด ๊ทธ๋Œ€๋กœ ๋ณด์—ฌ์ง€๊ณ ๋งŒ ์žˆ์—ˆ๋‹ค.

 

์œ„ ์‹คํ–‰ ๊ฒฐ๊ณผ๋Š” ๊ธฐ์กด์˜ extension์—์„œ ์ค€ ์ฝ”๋“œ๋ฅผ rootView๋กœ ์—ฐ๊ฒฐํ•ด ์‹คํ–‰ํ•œ ๊ฒฐ๊ณผ์ด๋‹ค.

 

์ด์ œ, ActivityReport๋ฅผ ์ง์ ‘ ์—ฐ๊ฒฐํ•ด์„œ ๋ณด์—ฌ์ค˜์•ผ ํ•œ๋‹ค !!

 

 

 

๊ทธ ์ „์—, ์Šคํฌ๋ฆฐํƒ€์ž„์œผ๋กœ ๊ธฐ๊ธฐ์— ์žˆ๋Š” ์ „์ฒด ์•ฑ์„ ๊ฐ€์ ธ์™€ ์ œํ•œ์‹œ์ผœ๋ณด์ž !

 

์†”์งํžˆ ๋งํ•˜๋ฉด,, ์—ฌ๊ธฐ์„œ ์‚ฝ์งˆ์„ ์ง„์งœ ์™•๋งŽ์ด ํ–ˆ๋‹ค.

๊ฒฐ๊ตญ ์ ‘๊ทผ ๊ถŒํ•œ์„ ๋จผ์ € ํ—ˆ์šฉํ•ด์ค˜์•ผํ–ˆ๋‹ค๋Š”๊ฑธ ๋’ค๋Šฆ๊ฒŒ ๊นจ๋‹ฌ์•˜๋‹ค..

 

1. ์Šคํฌ๋ฆฐํƒ€์ž„ ์ ‘๊ทผ ๊ถŒํ•œ ์š”์ฒญํ•˜๊ธฐ

๋จผ์ € ์ ‘๊ทผ ๊ถŒํ•œ์„ ํ—ˆ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” family controls๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ด๋ ‡๊ฒŒ Family Controls๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ๋œ๋‹ค.

์•„์ฐจ์ฐจ Family Controls์˜ ์ผ๋ถ€๋งŒ ์ฝ๊ณ  ๋‚ด ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์“ฐ์ง€ ์•Š์„๊ฑฐ๋ผ๊ณ  ์ž๋ถ€ํ–ˆ๋Š”๋ฐ..ใ…‹ใ…Žใ…‹ใ…Ž

 

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        
    guard let windowScene = (scene as? UIWindowScene) else { return }

    // SwiftUI ๋ทฐ๋ฅผ ํ˜ธ์ŠคํŒ…ํ•˜๋Š” UIHostingController๋ฅผ ์ƒ์„ฑ - ์ฆ‰ SwiftUI ์—ฐ๊ฒฐํ•˜๊ธฐ
    let totalActivityView = TotalActivityView(totalActivity: "hello")
    let hostingController = UIHostingController(rootView: totalActivityView)

    // UIKit์˜ UIWindow ์„ค์ •
    window = UIWindow(windowScene: windowScene)
    window?.rootViewController = hostingController
    window?.makeKeyAndVisible()

    // ์Šคํฌ๋ฆฐํƒ€์ž„ ๊ถŒํ•œ ๊ถŒํ•œ ์š”์ฒญ
    Task {
        let center = AuthorizationCenter.shared
        do {
            try await center.requestAuthorization(for: .individual)
        } catch {
            print("Fail: \(error)")
        }
    }
}

 

์ด๋ ‡๊ฒŒ ๊ถŒํ•œ ์š”์ฒญ์„ ์—ฐ๊ฒฐํ•˜๋ฉด ๋œ๋‹ค !!

012

 

2. ๊ธฐ๊ธฐ์˜ ์•ฑ ๋ฆฌ์ŠคํŠธ ๋ณด์—ฌ์ฃผ๊ธฐ

์ด์ œ ๊ธฐ๊ธฐ์— ์žˆ๋Š” ๋ชจ๋“  ์•ฑ์˜ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ณด์—ฌ์ค˜์•ผ ํ•œ๋‹ค.

import UIKit
import SwiftUI
import FamilyControls

struct AppListView: View {

    // ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด @State ์‚ฌ์šฉํ•˜๊ธฐ
    // FamilyActivitySelection()์€ ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ๊ฐ€์กฑ ํ™œ๋™ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฐ์ฒด.
    @State var selection = FamilyActivitySelection()

    // isPresented๋Š” familyActivityPicker๊ฐ€ ํ™”๋ฉด์— ํ‘œ์‹œ๋˜๋Š”์ง€ ํ†ต์ œํ•จ
    @State var isPresented = false

    var body: some View {
        
        // VStack์€ ์ˆ˜์ง์œผ๋กœ ๊ทธ๋ฆฌ๊ธฐ
        VStack {
            Button {
                // ๋ฒ„ํŠผ์ด ํด๋ฆญ๋˜๋ฉด isPresented ์ƒํƒœ๋ฅผ true๋กœ ์„ค์ •
                isPresented = true
            } label: {
                // ๋ฒ„ํŠผ์˜ ๋ ˆ์ด๋ธ” ์ง€์ •ํ•˜๊ธฐ
                Text("show app list")
            }
        }
        // .familyActivityPicker๋Š” family activity๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” Picker ํ™”๋ฉด์— ๋ณด์—ฌ์ฃผ๊ธฐ
        // isPresented๊ฐ€ true์ผ ๋•Œ Picker๊ฐ€ ํ‘œ์‹œ.
        // selection์€ ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ํ™œ๋™ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•จ.
        .familyActivityPicker(isPresented: $isPresented, selection: $selection)
    }
}

์ด๋ ‡๊ฒŒ ์ฝ”๋“œ๋ฅผ ์งœ๋ฉด ๋˜๋Š”๋ฐ, ๋‚˜๋Š” SwftUI๋ฅผ ์ „ํ˜€ ์•Œ์ง€ ๋ชปํ•ด์„œ ์ฃผ์„์œผ๋กœ ์ •๋ฆฌ๋ฅผ ์ข€ ํ•ด๋’€๋‹ค.

 

๋‚˜๋Š” ์ƒˆ๋กœ์šด ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด์„œ ๋ฆฌ์ŠคํŠธ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ถ€๋ถ„์„ ๊ตฌํ˜„ํ–ˆ๋Š”๋ฐ, ์ด ๋•Œ ๋นŒ๋“œ๋Š” ์‹คํŒจํ•˜๊ณ , ์‹คํŒจ ๋ฉ”์‹œ์ง€๊ฐ€ ์ž๊พธ ๋ณด์—ฌ์กŒ๋‹ค ์‚ฌ๋ผ์กŒ๋‹ค.

์‹ค์ˆ˜๋„ ๋ฐ˜๋ณต๋˜๋ฉด ์‹ค๋ ฅ์ธ๋ฐ ๋˜ ํƒ€๊นƒ ์„ค์ • ๋ฌธ์ œ์˜€๋‹ค.

 

์ง€๊ธˆ familyCotrol์„ ์‚ฌ์šฉํ•˜๋ ค ํ•˜๊ธฐ ๋•Œ๋ฌธ์— target์— activity report๋ฅผ ์ถ”๊ฐ€ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋นŒ๋“œํ•˜๋‹ˆ, ์ด๋ ‡๊ฒŒ ์ž˜ ๋‚˜์˜ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

01

 

์ด์ œ ๋ฆฌ์ŠคํŠธ์—์„œ ์„ ํƒํ•œ ์•ฑ๋“ค์„ ์ฝ˜์†”์— ์ถœ๋ ฅํ•ด๋ณด๊ณ , ์„ ํƒํ•œ ์•ฑ๋“ค์˜ ์‚ฌ์šฉ์„ ์ œํ•œํ•ด๋ณด์ž.

 

 

3. ์„ ํƒํ•œ ์•ฑ ์‚ฌ์šฉ ์ œํ•œํ•˜๊ธฐ

๋จผ์ € ์„ ํƒํ•œ ์•ฑ๋“ค์„ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด์ค€๋‹ค.

import SwiftUI
import FamilyControls
import ManagedSettings

// ObservableObject ํ”„๋กœํ† ์ฝœ์„ ์ฑ„ํƒํ•œ Model ํด๋ž˜์Šค ์ •์˜
class Model: ObservableObject {
    
    // ์ด ํด๋ž˜์Šค์˜ ๊ณต์œ  ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ (์‹ฑ๊ธ€ํ†ค ํŒจํ„ด)
    static var shared = Model()
    
    // ManagedSettingsStore์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์„ค์ •์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•จ
    let store = ManagedSettingsStore()
    
    // ์‚ฌ์šฉ์ž๊ฐ€ ์ œํ•œํ•˜๋ ค๋Š” ์•ฑ ๋ฐ ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ์„ ์ €์žฅํ•˜๋Š” ๋ณ€์ˆ˜, ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค UI ์—…๋ฐ์ดํŠธ
    @Published var selectedtoLimit: FamilyActivitySelection
    
    // ์ดˆ๊ธฐํ™” ๋ฉ”์„œ๋“œ: selectedtoLimit์„ ์ƒˆ๋กœ์šด FamilyActivitySelection ๊ฐ์ฒด๋กœ ์ดˆ๊ธฐํ™”
    init() {
        selectedtoLimit = FamilyActivitySelection()
    }
    
    // ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ์•ฑ๊ณผ ์นดํ…Œ๊ณ ๋ฆฌ์— ๋Œ€ํ•ด ์ฐจ๋‹จ ์ œํ•œ์„ ์„ค์ •ํ•˜๋Š” ํ•จ์ˆ˜
    func setShieldRestrictions() {
        // ์‚ฌ์šฉ์ž๊ฐ€ ์ œํ•œํ•  ์•ฑ๊ณผ ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ์„ ๊ฐ€์ ธ์˜ด
        let applications = Model.shared.selectedtoLimit
        
        // ์•ฑ ์ฐจ๋‹จ์„ ์„ค์ •, ์„ ํƒ๋œ ์•ฑ์ด ์—†์œผ๋ฉด nil๋กœ ์„ค์ •
        store.shield.applications = applications.applicationTokens.isEmpty ? nil : applications.applicationTokens
        
        // ์•ฑ ์นดํ…Œ๊ณ ๋ฆฌ ์ฐจ๋‹จ ์„ค์ •, ์„ ํƒ๋œ ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์—†์œผ๋ฉด nil๋กœ ์„ค์ •
        store.shield.applicationCategories = applications.categoryTokens.isEmpty
        ? nil
        : ShieldSettings.ActivityCategoryPolicy.specific(applications.categoryTokens)
    }
}

 

 

์ด์ œ ๋ชจ๋ธ์„ ์ถ”๊ฐ€ํ–ˆ์œผ๋‹ˆ, ํ™”๋ฉด์„ ๊ทธ๋ฆฌ๋Š” ๋ถ€๋ถ„๋„ ๋ชจ๋ธ์— ๋‹ด๊ณ  ๋ชจ๋ธ ๋‹จ์œ„๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ์กฐ๊ธˆ ์ˆ˜์ •ํ•ด์ค€๋‹ค.

struct AppListView: View {
    
    // ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด @State ์‚ฌ์šฉํ•˜๊ธฐ
    // FamilyActivitySelection()์€ ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ๊ฐ€์กฑ ํ™œ๋™ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฐ์ฒด.
    @State var selection = FamilyActivitySelection()
    
    // isPresented๋Š” familyActivityPicker๊ฐ€ ํ™”๋ฉด์— ํ‘œ์‹œ๋˜๋Š”์ง€ ํ†ต์ œํ•จ
    @State var isPresented = false
    @EnvironmentObject var model: Model
    
    var body: some View {
        
        // VStack์€ ์ˆ˜์ง์œผ๋กœ ๊ทธ๋ฆฌ๊ธฐ
        VStack {
            Button {
                // ๋ฒ„ํŠผ์ด ํด๋ฆญ๋˜๋ฉด isPresented ์ƒํƒœ๋ฅผ true๋กœ ์„ค์ •
                isPresented = true
            } label: {
                // ๋ฒ„ํŠผ์˜ ๋ ˆ์ด๋ธ” ์ง€์ •ํ•˜๊ธฐ
                Text("show app list")
                // .familyActivityPicker๋Š” family activity๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” Picker ํ™”๋ฉด์— ๋ณด์—ฌ์ฃผ๊ธฐ
                // isPresented๊ฐ€ true์ผ ๋•Œ Picker๊ฐ€ ํ‘œ์‹œ.
                // selection์€ ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ํ™œ๋™ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋ธ์— ์ €์žฅํ•จ.
            }.familyActivityPicker(isPresented: $isPresented, selection: $model.selectedtoLimit)
        }
        // ์ดํ›„ ์„ ํƒํ•œ ์•ฑ๋“ค์— ๋Œ€ํ•œ shieldRestriction์ด ์‹คํ–‰๋œ๋‹ค.
        .onChange(of: model.selectedtoLimit, {
            Model.shared.setShieldRestrictions()
        })
    }
}

 

 

๊ทธ๋ฆฌ๊ณ , ์Šคํฌ๋ฆฐํƒ€์ž„์— ๋Œ€ํ•œ ๊ถŒํ•œ ์š”์ฒญ์„ AppDelegate๋กœ ๋„˜๊ฒจ์ค€๋‹ค.

// AppDelegate.swift

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
           
   guard let windowScene = (scene as? UIWindowScene) else { return }

   // `Model.shared`์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑ
   let model = Model.shared

   // SwiftUI์˜ AppListView๋ฅผ ์„ค์ •
   let contentView = AppListView().environmentObject(model)

   // ์ƒˆ๋กœ์šด UIWindow๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ดˆ๊ธฐ ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ๋กœ ์„ค์ €
   let window = UIWindow(windowScene: windowScene)
   window.rootViewController = UIHostingController(rootView: contentView)
   self.window = window
   window.makeKeyAndVisible()
}

 

 

๊ทธ๋ฆฌ๊ณ  ๋งˆ์ง€๋ง‰์œผ๋กœ, SceneDelegate๋ฅผ ์ˆ˜์ •ํ•ด์ค€๋‹ค. ์—ฌ๊ธฐ์— ๋ชจ๋ธ์„ ๋‹ด๋„๋ก ์ˆ˜์ •ํ•˜๊ณ , ๊ถŒํ•œ ํ—ˆ์šฉ ๋ถ€๋ถ„์„ ์œ„์—์„œ ํ•œ ๊ฒƒ ์ฒ˜๋Ÿผ AppDelegate๋กœ ๋„˜๊ฒจ์คฌ๋‹ค.

// SceneDelegate.swift

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
           
   guard let windowScene = (scene as? UIWindowScene) else { return }

   // `Model.shared`์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑ
   let model = Model.shared

   // SwiftUI์˜ AppListView๋ฅผ ์„ค์ •
   let contentView = AppListView().environmentObject(model)

   // ์ƒˆ๋กœ์šด UIWindow๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ดˆ๊ธฐ ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ๋กœ ์„ค์ €
   let window = UIWindow(windowScene: windowScene)
   window.rootViewController = UIHostingController(rootView: contentView)
   self.window = window
   window.makeKeyAndVisible()
}

 

์ด๋ ‡๊ฒŒ ํ•˜๊ณ , ์•ฑ์„ ์‹คํ–‰ํ•ด๋ณด์ž !

 

 

์‹คํ–‰ ๊ฒฐ๊ณผ

 

 

 

์•„๋‹ˆ ์ด๋ ‡๊ฒŒ ๊ฐ„๋‹จํ•œ ๊ธฐ์ˆ ๊ฒ€์ฆ์„ ๊ทธ๋•Œ๋Š” ์™œ๊ทธ๋ ‡๊ฒŒ ์‚ฝ์งˆํ•˜๊ณ  ํž˜๋“ค์–ดํ–ˆ์„๊นŒ..

์‹œ๊ฐ„์ด ์ด‰๋ฐ•ํ•˜๋‹ค๋Š” ์ด์œ ๋กœ reference๋ถ€ํ„ฐ ์ฐพ๊ณ , ๋ƒ…๋‹ค ์‹คํ–‰ํ•ด๋ณด๊ณ  ๋„˜์–ด๊ฐ”๋˜ ๊ณผ๊ฑฐ์˜ ๋‚˜.. ๋ฐ˜์„ฑํ•˜๋ผ..

 

์ด์ œ ์• ํ”Œ ๊ณต์‹๋ฌธ์„œ ๊ตฌ์กฐ๋„ ์–ด๋Š์ •๋„ ๊ฐ์ด ์žกํ˜”๊ณ , ์•„์ง ๊ทธ๊ฑธ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฝ”๋“œ๋Š” ๋ชป์งœ๊ฒ ์ง€๋งŒ,, ๋ญ,, ์•ž์œผ๋กœ ์ฐจ๊ทผ์ฐจ๊ทผ ํ•ด๋ด์•ผ๊ฒ ๋‹ค !!

 

๋‹ค์Œ ๊ธ€์—์„œ๋Š” ์ด์ œ ์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ์˜ ์ฃผ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด๋ด์•ผ์ง€ !

 

 

 

 

์ฐธ๊ณ ์ž๋ฃŒ

https://hongz-developer.tistory.com/246

 

728x90
๋ฐ˜์‘ํ˜•