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
๋ฐ˜์‘ํ˜•
728x90
๋ฐ˜์‘ํ˜•
๋ฐ˜์‘ํ˜•

 

๋ฐ˜์‘ํ˜•

 

์ตœ๊ทผ ์ง„ํ–‰ ์ค‘์ธ ํ”„๋กœ์ ํŠธ์—์„œ swift data๋ฅผ ์‚ฌ์šฉํ• ์ง€, realm์„ ์‚ฌ์šฉํ• ์ง€ ๊ณ ๋ฏผํ•ด์˜ค๊ธฐ๋กœ ํ–ˆ๋‹ค.

swift์— ์ž…๋ฌธํ•œ์ง€ ์–ผ๋งˆ ๋˜์ง€ ์•Š์€ ๋‚˜๋Š” ์‚ฌ์‹ค ๋‘˜ ๋‹ค ์ž˜ ๋ชจ๋ฅธ๋‹ค...

swift Data์— ๋Œ€ํ•œ ๊ณต์‹ ๋ฌธ์„œ์„ ์งง๊ฒŒ ํ•œ๊ตญ์–ด๋กœ, ๋‚ด๊ฐ€ ์ดํ•ดํ•œ ๋ฐ”๋ฅผ ํ’€์–ด์„œ ์ •๋ฆฌํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค.

 

์‹œ์ž‘!

 

๋ฐ˜์‘ํ˜•

 

๐ŸŽ Swift Data

swiftData๋Š” ์„ ์–ธํ˜• ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ data๋ฅผ ์‰ฝ๊ฒŒ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.

์šฐ๋ฆฌ๋Š” swift ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ data๋ฅผ query๋ฅผ ์‚ฌ์šฉ, filterํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ด๋Š” swiftUi์™€ ์›ํ™œํ•˜๊ฒŒ ํ†ตํ•ฉ๋˜๋„๋ก ์„ค๊ณ„๋˜์—ˆ๋‹ค๊ณ  ํ•œ๋‹ค.

 

 

๋ฐ˜์‘ํ˜•

 

๐ŸŽ swift๋กœ ๋ชจ๋ธ ์ƒ์„ฑํ•˜๊ธฐ

์ถ”๊ฐ€๋กœ ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜, ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ tool์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ @Model ์„ ์‚ฌ์šฉํ•ด์„œ data๋ฅผ ๊ตฌ์กฐํ™” ํ•˜๊ณ  ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

swiftData๋Š” ์ž๋™์œผ๋กœ ๋งŽ์€ ๊ด€๊ณ„๋“ค์„ ์ถ”๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ, ์šฐ๋ฆฌ๋Š” @Attribute(.unique)์™€ ๊ฐ™์€ ๊ฐ„๋‹จํ•œ ์„ ์–ธ์„ ํ†ตํ•ด ์ œ์•ฝ๋“ค์„ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‹ค์Œ์€ SwiftUI์—์„œ ์ด๋ฅผ ์ ์šฉํ•œ ์ฝ”๋“œ ์˜ˆ์‹œ์ด๋‹ค.

@Model
class Recipe {
	@Attribute(.unique) var name: String
	var summary: String?
	var ingredients: [Ingredient]
}

 

๋ฐ˜์‘ํ˜•

๐ŸŽ Automatic persistence

swiftData๋Š” ์šฐ๋ฆฌ์˜ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•ด์„œ ์‚ฌ์šฉ์ž ์ •์˜ ์Šคํ‚ค๋งˆ๋ฅผ ๊ตฌ์ถ•ํ•ด์ฃผ๊ณ , ํ•ด๋‹น ๋ชจ๋ธ์˜ fields๋ฅผ ๊ธฐ๋ฐ˜ ์Šคํ† ๋ฆฌ์ง€์— ํšจ์œจ์ ์œผ๋กœ ๋งคํ•‘ํ•ด์ค€๋‹ค.

์ฆ‰, ๋ชจ๋ธ์˜ ํ•„๋“œ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์˜ ์—ด๋กœ ๋งคํ•‘ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ!

swiftData๋กœ ๊ด€๋ฆฌ๋˜๋Š” ๊ฐ์ฒด๋“ค์€ ํ•„์š”ํ•  ๋•Œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ถ”๊ฐ€์ ์ธ ์ž‘์—… ์—†์ด ์›ํ•˜๋Š” ์ ์ ˆํ•œ ์‹œ์ ์— ์ž๋™์œผ๋กœ ์ €์žฅ๋œ๋‹ค. ๋˜ํ•œ, ModelContext API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ „์ฒด์ ์ธ ์ œ์–ด๋ฅผ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

 

๋ฐ˜์‘ํ˜•

 

๐ŸŽ Integrates with SwiftUI

SwiftUI view์—์„œ @Query๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

SwiftData์™€ SwiftUI๋Š” ํ•จ๊ป˜ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ์ˆ˜๋™์œผ๋กœ ์ƒˆ๋กœ๊ณ ์นจ์„ ํ•˜์ง€ ์•Š์•„๋„ ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ ๋œ๋‹ค.

@Query var recipes: [Recipe]
var body: some View {
	List(recipes) { recipe in
		NavigationLink(recipe.name, destination: RecipeView(recipe))
	}
}
๋ฐ˜์‘ํ˜•

 

๐ŸŽ Swift-native predicates

์ปดํŒŒ์ผ๋Ÿฌ์— ์˜ํ•ด ๊ฒ€์‚ฌํ•œ ํŠน์ • ํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ฟผ๋ฆฌํ•˜๊ณ , ํ•„ํ„ฐ๋งํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด, ๊ฐœ๋ฐœ ์ค‘ ์ƒ๊ธฐ๋Š” ์˜คํƒ€์™€ ์‹ค์ˆ˜๋ฅผ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

let simpleFood = #Predicate<Recipe> { recipe in
	recipe.ingredients.count < 3
}
๋ฐ˜์‘ํ˜•

 

๐ŸŽ CloudKit syncing

ClounKit์™€ ์—ฐ๋™ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

๋ฐ˜์‘ํ˜•

 

๐ŸŽ Compatible with Core Data

๊ธฐ์กด์— core data๋ฅผ ํ†ตํ•ด ๊ฐœ๋ฐœํ–ˆ๋‹ค๋ฉด, swift data๋กœ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

 

 

์–ผ๋ฅธ ํ•œ ๋ฒˆ ์จ๋ณด๊ณ  ์‹ถ๋‹ค !

์ฐธ๊ณ  ๋ฌธ์„œ : https://developer.apple.com/xcode/swiftdata/

 

SwiftUI Overview - Xcode - Apple Developer

SwiftUI is an innovative, exceptionally simple way to build user interfaces across all Apple platforms with the power of Swift.

developer.apple.com

 

728x90
๋ฐ˜์‘ํ˜•

 

 

728x90
๋ฐ˜์‘ํ˜•

 

728x90
๋ฐ˜์‘ํ˜•
728x90
๋ฐ˜์‘ํ˜•
๋ฐ˜์‘ํ˜•
๋ฐ˜์‘ํ˜•
๋ฐ˜์‘ํ˜•

 

(ํŽธ๋ฒ• ์•„๋‹Œ ํŽธ๋ฒ•์ด๋ผ๋„ ๊ดœ์ฐฎ๋‹ค๋ฉด....)

 

swiftUi๋กœ ๊ฐœ๋ฐœ์„ ํ•˜๋˜ ์ค‘, Text๋“ค์˜ ์œ„์น˜๋ฅผ ์™ผ์ชฝ ์ •๋ ฌํ•˜์—ฌ ๋ฐฐ์น˜ํ•ด์•ผ ํ•˜๋Š” ์ผ์ด ์žˆ์—ˆ๋‹ค.

ํ•˜์ง€๋งŒ, HStack์— alignment๋กœ leading์„ ์ฃผ๋”๋ผ๋„, ํ…์ŠคํŠธ์˜ ๊ธธ์ด๊ฐ€ ๊ธธ์ง€ ์•Š์œผ๋ฉด ์„ผํ„ฐ์— ๋ฐฐ์น˜๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ…์ŠคํŠธ๋Š” ์™ผ์ชฝ์œผ๋กœ ์ •์—ด๋˜์ง€๋งŒ, ํ…์ŠคํŠธ์˜ ์œ„์น˜๋Š” ์„ผํ„ฐ์— ์žˆ์—ˆ๋‹ค.

๊ทธ๋ž˜์„œ... ์•„๋ฌด๋ฆฌ ์ฐพ์•„๋„ ์ด๊ฑธ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•ด์•ผ ํ• ์ง€ ๋ชจ๋ฅด๊ฒ ๋”๋ผ...

 

ํŒจ๋”ฉ์„ ์ฃผ๊ธฐ๋„ ํ•˜๊ณ , frame์œผ๋กœ leading์„ ์„ค์ •ํ•˜๊ธฐ๋„ ํ–ˆ๋Š”๋ฐ, ์ ์šฉ๋˜์ง€ ์•Š์•˜๋‹ค.

 

๋‚˜๋Š” ์–ด๋–ป๊ฒŒ ํ•ด์„œ๋“  ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ด์•ผ ํ–ˆ๊ธฐ์—, ์ผ๋‹จ ํŽธ๋ฒ•์„ ์“ฐ๊ธฐ๋กœ ํ–ˆ๋‹ค.

 

์–ด์ฐŒ ๋๋“  ๊ฐ€์žฅ ํ•˜๋‹จ์— ์žˆ๋Š” '๋„์„œ DB ์ œ๊ณต' ๋ถ€๋ถ„์€ ์˜ค๋ฅธ์ชฝ ๋์— ์œ„์น˜ํ•˜๋„๋ก ๋‘ฌ์•ผ ํ–ˆ๊ธฐ์—, ์ด ํ…์ŠคํŠธ๋งŒ HStack์œผ๋กœ ๊ฐ์‹ธ์„œ ์™ผ์ชฝ์— spacer๋ฅผ ์คฌ๋‹ค. ๊ทธ๋žฌ๋”๋‹ˆ ํ•ด๊ฒฐ ์™„๋ฃŒ~~

 

๋งŒ์•ฝ, ์šฐ์ธก์— ๋ฐฐ์น˜ํ•˜๋Š” ํ…์ŠคํŠธ๊ฐ€ ์—†๋‹ค๋ฉด, ๊ทธ๋ƒฅ Text("") ์ด๋ ‡๊ฒŒ ํ•˜๊ณ  Spacer๋ฅผ ์ฃผ๋Š” ๊ฒƒ๋„ ๋ฐฉ๋ฒ•์ด์ง€ ์•Š์„๊นŒ...?

 

LazyVStack(spacing: 10) {
    VStack(alignment: .leading, spacing: 15){
            Text("์ฑ… ์†Œ๊ฐœ").fontWeight(.bold)
            Text("์ž์„ธํžˆ ๋ณด๊ธฐ").underline().font(.system(size: 12))
            HStack{
                Spacer()
                Text("๋„์„œ DB ์ œ๊ณต: ์•Œ๋ผ๋”˜").font(.system(size: 12)).padding(.top, 10)
            }
    }.padding(.horizontal, 20)
}
๋ฐ˜์‘ํ˜•
๋ฐ˜์‘ํ˜•

 

728x90
๋ฐ˜์‘ํ˜•

 

728x90
๋ฐ˜์‘ํ˜•

+ Recent posts