๐ŸŒ™

[Swift] ์‹ค์‹œ๊ฐ„ ๊ฑธ์Œ ์ˆ˜ ์œ„์ ฏ ๋งŒ๋“ค๊ธฐ

yeggrrr๐Ÿผ 2025. 12. 21. 21:13
728x90

 

HealthKit์œผ๋กœ '์‹ค์‹œ๊ฐ„์ฒ˜๋Ÿผ ๋ณด์ด๋Š”' ๊ฑธ์Œ ์ˆ˜ ์œ„์ ฏ ๋งŒ๋“ค๊ธฐ


iOS ์œ„์ ฏ์—์„œ ๊ฑธ์Œ ์ˆ˜๋ฅผ '์‹ค์‹œ๊ฐ„ ์ฒ˜๋Ÿผ' ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์€ ์ƒ๊ฐ๋ณด๋‹ค ๊ฐ„๋‹จํ•˜์ง€ ์•Š๋‹ค.
์œ„์ ฏ์€ ํ•ญ์ƒ ์ผœ์ ธ ์žˆ๋Š” ๋ทฐ๊ฐ€ ์•„๋‹ˆ๋ผ, ์‹œ์Šคํ…œ์ด ํ•„์š”ํ•  ๋•Œ๋งˆ ๊นจ์›Œ์„œ ์Šค๋ƒ…์ƒท์„ ๊ทธ๋ฆฌ๋Š” ๊ตฌ์กฐ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ์•„๋ž˜์˜ ๋‚ด์šฉ์„ ์ค‘์ ์œผ๋กœ ์ •๋ฆฌํ•˜๋ ค๊ณ  ํ•œ๋‹ค.

โ‘  ์œ„์ ฏ์ด ์ง„์งœ ์‹ค์‹œ๊ฐ„์ด ๋  ์ˆ˜ ์—†๋Š” ์ด์œ 
โ‘ก HealthKit + WidgetKit ๊ตฌ์กฐ ์„ค๊ณ„
โ‘ข HealthKit์—์„œ ๊ฑธ์Œ ์ˆ˜ ์ˆ˜์ง‘/ํ•ฉ์‚ฐํ•˜๊ธฐ
โ‘ฃ ์œ„์ ฏ ํƒ€์ž„๋ผ์ธ ๊ตฌ์„ฑํ•˜๊ธฐ(์˜ค๋Š˜ ๊ฑธ์Œ ์ˆ˜ ํ‘œ์‹œ)
โ‘ค ObserverQuery + background delivery๋กœ '์‹ค์‹œ๊ฐ„์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ' ๋งŒ๋“ค๊ธฐ

 


 

์œ„์ ฏ์ด ์ง„์งœ ์‹ค์‹œ๊ฐ„์ด ๋  ์ˆ˜ ์—†๋Š” ์ด์œ 


- ์œ„์ ฏ์€ ํ•ญ์ƒ ์‹คํ–‰๋˜๋Š” ํ”„๋กœ์„ธ์Šค๊ฐ€ ์•„๋‹˜

- ํ™”๋ฉด์— ๋ณด์ด๋Š” ๋™์•ˆ์—๋„, ๋‚ด๋ถ€์ ์œผ๋กœ๋Š” ํ•œ ๋ฒˆ ๋ Œ๋”๋ง ํ•œ ํ›„,
์‹œ์Šคํ…œ์ด ๋‹ค์‹œ ๊ฐฑ์‹ ์„ ํ—ˆ์šฉํ•  ๋•Œ๋งŒ ์ƒˆ๋กœ ๊ทธ๋ฆด ์ˆ˜ ์žˆ์Œ

- WidgetKit์€ reloadTimelines, reloadAllTimelines() ๋“ฑ์„ ํ†ตํ•ด '๊ฐฑ์‹  ์š”์ฒญ'์€ ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ,
์–ธ์ œ ์‹ค์ œ๋กœ ์ƒˆ๋กœ ๊ทธ๋ฆด์ง€๋Š” ์‹œ์Šคํ…œ์ด ํœด๋ฆฌ์Šคํ‹ฑ์œผ๋กœ ๊ฒฐ์ •ํ•จ

 


 

์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„: HealthKit + WidgetKit

1. ๊ณต์šฉ HealthKit Manager (App + Extension์—์„œ ๊ฐ™์ด ์‚ฌ์šฉ)
    - ๊ฑธ์Œ ์ˆ˜ ๊ถŒํ•œ ์š”์ฒญ
    - ์˜ค๋Š˜ ๊ฑธ์Œ ์ˆ˜ ํ•ฉ์‚ฐ
    - ObserverQuery ๋“ฑ๋ก ๋ฐ background delivery

2. ๋ฉ”์ธ ์•ฑ
    - ์ตœ์ดˆ ๊ถŒํ•œ ์š”์ฒญ ํ™”๋ฉด
    - HealthKit Manager ์ดˆ๊ธฐํ™”
    - HealthKit ๋ณ€๊ฒฝ ์ด๋ฒคํŠธ ์ˆ˜์‹  ์‹œ → WidgetKit ๊ฐฑ์‹  ์š”์ฒญ

3. Widget Extension
    - TimelineProvider ์—์„œ ์˜ค๋Š˜ ๊ฑธ์Œ ์ˆ˜ ์กฐํšŒ
    - TimelineEntry ์— ๊ฑธ์Œ ์ˆ˜ + ๋‚ ์งœ/์‹œ๊ฐ„ ๋‹ด๊ธฐ
    - View ์— ํ‘œ์‹œ

// Shared (App + Widget์—์„œ ๊ณต์šฉ์œผ๋กœ ์“ธ ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“ˆ)
final class HealthKitStepService {
    // ๊ถŒํ•œ ์š”์ฒญ
    func requestAuthorization() async throws { ... }

    // ์˜ค๋Š˜ ๊ฑธ์Œ ์ˆ˜ ํ•ฉ์‚ฐ
    func fetchTodayStepCount() async throws -> Double { ... }

    // ObserverQuery + background delivery ์„ค์ •
    func startObservingStepChanges(callback: @escaping () -> Void) { ... }
}
// App
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    let healthService = HealthKitStepService()

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        healthService.startObservingStepChanges {
            // HealthKit ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ๊ฐ์ง€ ์‹œ
            WidgetCenter.shared.reloadAllTimelines()
        }
        return true
    }
}
// Widget Extension
struct StepWidgetProvider: TimelineProvider {
    func getTimeline(
        in context: Context,
        completion: @escaping (Timeline<StepEntry>) -> Void
    ) {
        Task {
            let steps = try? await HealthKitStepService.shared.fetchTodayStepCount()
            let entry = StepEntry(date: Date(), steps: Int(steps ?? 0))
            let next = Calendar.current.date(byAdding: .minute, value: 15, to: Date())!

            completion(Timeline(entries: [entry], policy: .after(next)))
        }
    }
}

 

HealthKit์—์„œ ๊ฑธ์Œ ์ˆ˜ ๊ฐ€์ ธ์˜ค๊ธฐ

โ–ถ ๊ถŒํ•œ ์š”์ฒญ
(info.plist์— ์ถ”๊ฐ€)
NSHealthShareUsageDescription
NSHealthUpdateUsageDescription

import HealthKit

final class HealthKitStepService {
    static let shared = HealthKitStepService()
    private let store = HKHealthStore()

    private init() {}

    private var stepType: HKQuantityType {
        HKObjectType.quantityType(forIdentifier: .stepCount)!
    }

    // MARK: - Authorization
    func requestAuthorization() async throws {
        guard HKHealthStore.isHealthDataAvailable() else { return }

        let readTypes: Set = [stepType]
        // ๊ฑธ์Œ ์ˆ˜๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ write๋Š” ํ•„์š” ์—†์Œ(ํ•„์š”ํ•˜๋ฉด ์ถ”๊ฐ€)
        let shareTypes: Set<HKSampleType> = []

        try await store.requestAuthorization(
            toShare: shareTypes,
            read: readTypes
        )
    }
}

 

โ–ถ '์˜ค๋Š˜' ๊ฑธ์Œ ์ˆ˜ ํ•ฉ์‚ฐ ์ฟผ๋ฆฌ
Widget๊ณผ App ์–‘์ชฝ์—์„œ ๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉํ•  ํ•จ์ˆ˜

extension HealthKitStepService {
    func fetchTodayStepCount() async throws -> Double {
        let calendar = Calendar.current
        let now = Date()
        let startOfDay = calendar.startOfDay(for: now)

        var components = DateComponents()
        components.day = 1
        components.second = -1
        let endOfDay = calendar.date(byAdding: components, to: startOfDay)!

        let predicate = HKQuery.predicateForSamples(
            withStart: startOfDay,
            end: endOfDay,
            options: .strictStartDate
        )

        let queryDescriptor = HKSampleQueryDescriptor(
            predicates: [
                .quantitySample(type: stepType, predicate: predicate)
            ],
            sortDescriptors: []
        )

        let samples = try await queryDescriptor.result(for: store) as! [HKQuantitySample]

        let total = samples.reduce(0.0) { partial, sample in
            partial + sample.quantity.doubleValue(for: HKUnit.count())
        }

        return total
    }
}

์‹ค์ œ ์•ฑ์—์„œ๋Š” HKStatisticsQuery๋ฅผ ์‚ฌ์šฉํ•ด ํ•ฉ์‚ฐํ•˜๋Š” ๋ฐฉ์‹๋„ ๋งŽ์ด ์‚ฌ์šฉํ•จ.
ํ•ต์‹ฌ์€ ์˜ค๋Š˜ ์ž์ •~23:59:59๊นŒ์ง€ ๋ฒ”์œ„๋ฅผ ๋ช…ํ™•ํžˆ ์ •์˜ํ•˜๋Š” ๊ฒƒ.

 


 

Widget ํƒ€์ž„๋ผ์ธ ๊ธฐ๋ณธ ๊ตฌํ˜„


- Entry ์ •์˜

import WidgetKit

struct StepEntry: TimelineEntry {
    let date: Date
    let steps: Int
}


- Provider ๊ตฌํ˜„

struct StepProvider: TimelineProvider {
    func placeholder(in context: Context) -> StepEntry {
        StepEntry(date: Date(), steps: 0)
    }

    func getSnapshot(in context: Context, completion: @escaping (StepEntry) -> Void) {
        let entry = StepEntry(date: Date(), steps: 1234)
        completion(entry)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<StepEntry>) -> Void) {
        Task {
            let steps = (try? await HealthKitStepService.shared.fetchTodayStepCount()) ?? 0

            let entry = StepEntry(date: Date(), steps: Int(steps))
            let nextUpdate = Calendar.current.date(byAdding: .minute, value: 15, to: Date())!

            let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))
            completion(timeline)
        }
    }
}

์—ฌ๊ธฐ๊นŒ์ง€๋Š” 15๋ถ„๋งˆ๋‹ค ํ•œ ๋ฒˆ ์ •๋„ ์˜ค๋Š˜ ๊ฑธ์Œ ์ˆ˜๋ฅผ ํ‘œ์‹œํ•˜๋Š” ์œ„์ ฏ์ด๋‹ค.
์ด์ œ๋ถ€ํ„ฐ '์‹ค์‹œ๊ฐ„์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ' ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด HealthKit์˜ Observer ๊ธฐ๋Šฅ์„ ๋ถ™์ผ ์˜ˆ์ •์ด๋‹ค.


 

ObserverQuery + Background Delivery๋กœ '์‹ค์‹œ๊ฐ„์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ' ๋งŒ๋“ค๊ธฐ


- HealthKit ๋ณ€๊ฒฝ ๊ฐ์ง€
๊ฑธ์Œ ์ˆ˜๋Š” Health ์•ฑ/์›Œ์น˜/๊ธฐ๊ธฐ์—์„œ ๋Š์ž„์—†์ด ์ถ”๊ฐ€๋œ๋‹ค.
HealthKit์€ ์ด ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ๋„๋ก ObserverQuery์™€ background delivery๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

extension HealthKitStepService {
    func startObservingStepChanges(callback: @escaping () -> Void) {
        let query = HKObserverQuery(sampleType: stepType, predicate: nil) { [weak self] _, completionHandler, error in
            if let error {
                print("ObserverQuery Error: \(error)")
                completionHandler()
                return
            }

            // ์—ฌ๊ธฐ์„œ anchor ๊ธฐ๋ฐ˜ AnchoredObjectQuery๋ฅผ ๊ฐ™์ด ๋Œ๋ ค๋„ ๋จ
            callback()

            // ๋ฐ˜๋“œ์‹œ ํ˜ธ์ถœํ•ด์•ผ background mode์—์„œ ๋‹ค์‹œ ๊นจ์šฐ๊ธฐ
            completionHandler()
        }

        store.execute(query)

        // Background delivery ์„ค์ •
        store.enableBackgroundDelivery(for: stepType, frequency: .immediate) { success, error in
            if let error {
                print("Background delivery error: \(error)")
            } else {
                print("Background delivery enabled: \(success)")
            }
        }
    }
}

์œ„์™€ ๊ฐ™์ด ์„ค์ •ํ•ด๋‘๋ฉด,
์›Œ์น˜/์•„์ดํฐ์ด ๊ฑธ์Œ ๋ฐ์ดํ„ฐ๋ฅผ HealthKit์— ์Œ“์„ ๋•Œ๋งˆ๋‹ค ์•ฑ์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๊นจ์šฐ๋Š” ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

 


 

์œ„์ ฏ ๊ฐฑ์‹  ์š”์ฒญ ์—ฐ๊ฒฐ


Observer ์ฝœ๋ฐฑ์—์„œ WidgetKit ๊ฐฑ์‹  ์š”์ฒญ์„ ํ˜ธ์ถœํ•œ๋‹ค.

import WidgetKit

final class StepWidgetRefresher {
    static let shared = StepWidgetRefresher()

    func requestReload() {
        WidgetCenter.shared.reloadAllTimelines()
    }
}


์•ฑ ๊ตฌ๋™ ์‹œ,

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    let healthService = HealthKitStepService()

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {

        healthService.startObservingStepChanges {
            StepWidgetRefresher.shared.requestReload()
        }

        return true
    }
}


ํ๋ฆ„์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  1. ์›Œ์น˜/ํฐ์—์„œ ์ƒˆ๋กœ์šด ๊ฑธ์Œ ๋ฐ์ดํ„ฐ๊ฐ€ HealthKit์— ๊ธฐ๋ก๋œ๋‹ค.
  2. HealthKit → ObserverQuery ์ฝœ๋ฐฑ ํ˜ธ์ถœ (๋ฐฑ๊ทธ๋ผ์šด๋“œ ๊ฐ€๋Šฅ)
  3. ์ฝœ๋ฐฑ์—์„œ WidgetCenter.shared.reloadAllTimelines() ํ˜ธ์ถœ
  4. ์‹œ์Šคํ…œ์ด ํ—ˆ์šฉํ•˜๋Š” ์‹œ์ ์— ์œ„์ ฏ์ด ๋‹ค์‹œ ๊ทธ๋ ค์ง
  5. getTimeline ์ด ๋‹ค์‹œ ์‹คํ–‰๋˜๋ฉด์„œ fetchTodayStepCount()๋ฅผ ํ˜ธ์ถœ
  6. ์ƒˆ๋กœ ํ•ฉ์‚ฐ๋œ ๊ฑธ์Œ ์ˆ˜๊ฐ€ ์œ„์ ฏ์— ๋ฐ˜์˜

์œ„์ ฏ์ด 1์ดˆ ๋‹จ์œ„๋กœ ๊ณ„์† ๋ฐ”๋€Œ์ง€๋Š” ์•Š์ง€๋งŒ,
๋ฐ์ดํ„ฐ๊ฐ€ ๊ฐฑ์‹ ๋  ๋•Œ๋งˆ๋‹ค ๊ฝค ๋น ๋ฅด๊ฒŒ ๋”ฐ๋ผ๊ฐ€๋Š” ํ˜•ํƒœ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

 


 

'์‹ค์‹œ๊ฐ„์ฒ˜๋Ÿผ' ๋ณด์ด๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•œ ํŠœ๋‹ ํฌ์ธํŠธ


- ํƒ€์ž„๋ผ์ธ ์ •์ฑ… ์„ค๊ณ„
getTimeline ์˜ policy ๋ฅผ ๋„ˆ๋ฌด ๊ธธ๊ฒŒ ์žก์œผ๋ฉด,
Observer ์ชฝ์—์„œ reload ์š”์ฒญ์„ ํ•ด๋„ ๋ฐ˜์˜์ด ๋Šฆ๊ฒŒ ๋А๊ปด์งˆ ์ˆ˜ ์žˆ๋”ฐ.

์ผ๋ฐ˜์ ์ธ ํŒจํ„ด์€

  • ๊ธฐ๋ณธ ํƒ€์ž„๋ผ์ธ์€ policy: .after(Date().addingTimeInterval(60*30)) ์ •๋„๋กœ ์—ฌ์œ ์žˆ๊ฒŒ
  • ObserverQuery ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์‹œ์—๋Š” ์ฆ‰์‹œ reload ์š”์ฒญ์— ์˜์กด

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด,

  • ์‚ฌ์šฉ์ž๊ฐ€ ์œ„์ ฏ์„ ๊ฑฐ์˜ ์•ˆ๋ณด๋Š” ์ƒํ™ฉ์—์„œ๋Š” ์‹œ์Šคํ…œ์ด ์•Œ์•„์„œ ๋œ ์ž์ฃผ ๊ฐฑ์‹ ํ•˜๊ณ 
  • HealthKit ๋ฐ์ดํ„ฐ๊ฐ€ ์ž์ฃผ ๋ฐ”๋€” ๋•Œ๋Š” Observer๋ฅผ ํ†ตํ•ด ์ข€ ๋” ์ž์ฃผ ๊ฐฑ์‹  ์š”์ฒญ์„ ํ•˜๊ฒŒ ๋œ๋‹ค.



- ์œ„์ ฏ ํฌ๊ธฐ(๊ฐ€๋กœ/์„ธ๋กœ)๋ณ„ UI ์„ค๊ณ„
์‹ค์‹œ๊ฐ„์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ ํ•˜๋ ค๋ฉด, ์ˆซ์ž๊ฐ€ ๋ฐ”๋€Œ๋Š” ๋А๋‚Œ์„ ๊ฐ•ํ™”ํ•˜๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•˜๋‹ค.

  • small ์œ„์ ฏ
    • '์˜ค๋Š˜ ๊ฑธ์Œ ์ˆ˜' 1์ค„ + ํฐ ์ˆซ์ž
    • ์ตœ๊ทผ ์—…๋ฐ์ดํŠธ ์‹œ๊ฐ(ex. ์ตœ๊ทผ 2๋ถ„ ์ „) ์ž‘์€ ํ…์ŠคํŠธ
  • Medium/Large ์œ„์ ฏ
    • ์˜ค๋Š˜ ์ด ๊ฑธ์Œ ์ˆ˜
    • ์‹œ๊ฐ„๋Œ€๋ณ„ ๋ง‰๋Œ€ ๊ทธ๋ž˜ํ”„ (๋ฏธ๋‹ˆ ์ฐจํŠธ)
    • '๋ชฉํ‘œ ๋Œ€๋น„%' ๊ฒŒ์ด์ง€

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด, ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ๋Š” ์ˆซ์ž๊ฐ€ ์ž์ฃผ ๋ฐ”๋€๋‹ค๋Š” ์ธ์‹์ด ์ƒ๊ธฐ๋ฉด์„œ ์‹ค์‹œ๊ฐ„ ์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ ๋œ๋‹ค.

 

- ๊ถŒํ•œ/์—๋Ÿฌ ์ƒํƒœ ์ฒ˜๋ฆฌ
HealthKit ๊ธฐ๋ฐ˜ ์œ„์ ฏ์€ ์•„๋ž˜์™€ ๊ฐ™์€ ์ƒํƒœ๋ฅผ ๊ณ ๋ คํ•ด์•ผ ํ•œ๋‹ค.

  • ๊ถŒํ•œ ๋ฏธ๋ถ€์—ฌ(์ฒ˜์Œ ์„ค์น˜ ํ›„)
  • ์˜ค๋Š˜ ๋ฐ์ดํ„ฐ ์—†์Œ
  • ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ „๋‹ฌ ๋น„ํ™œ์„ฑํ™”(์‚ฌ์šฉ์ž๊ฐ€ ๋ˆ ๊ฒฝ์šฐ)
  • ์œ„์น˜ ๋ฏธ์—ฐ๋™/์•„์ดํฐ๋งŒ ๋“ค๊ณ  ์žˆ์Œ

๊ฐ ์ƒํƒœ์— ๋Œ€ํ•ด ์œ„์ ฏ์—์„œ

  • Health ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค.
  • ์•„์ง ๊ฑธ์Œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค

๋“ฑ์˜ ๋ฌธ์ž์—ด์„ ๋ณ„๋„๋กœ ๋…ธ์ถœํ•ด์ฃผ๋ฉด UX ๊ฐœ์„ ์ด ๋  ๊ฒƒ ๊ฐ™๋‹ค.

 


 

๋์œผ๋กœ,

HealthKit ๊ธฐ๋ฐ˜์˜ ๊ฑธ์Œ ์ˆ˜ ์œ„์ ฏ์€ ๊ตฌ์กฐ์ ์œผ๋กœ ์™„์ „ํ•œ ์‹ค์‹œ๊ฐ„ ๊ฐฑ์‹ ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜์ง€๋งŒ,
ObserverQuery·background delivery·ํƒ€์ž„๋ผ์ธ ์ •์ฑ…์„ ์ ์ ˆํžˆ ์กฐํ•ฉํ•˜๋ฉด
์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ๋Š” '์‹ค์‹œ๊ฐ„์ฒ˜๋Ÿผ ๋ฐ˜์‘ํ•˜๋Š” ์œ„์ ฏ'์„ ์ถฉ๋ถ„ํžˆ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๊ฒฐ๊ตญ ํ•ต์‹ฌ์€,
- HealthKit์˜ ๋ณ€ํ™”๋ฅผ ๋น ๋ฅด๊ฒŒ ๊ฐ์ง€ํ•˜๊ณ 
- ๊ฐ€๋Šฅํ•œ ํ•œ ์ฆ‰์‹œ WidgetKit ๊ฐฑ์‹ ์„ ์š”์ฒญํ•˜๋ฉฐ
- ์œ„์ ฏ UI์™€ ํƒ€์ž„๋ผ์ธ์„ “์‹ค์‹œ๊ฐ„์— ๊ฐ€๊น๊ฒŒ ๋ณด์ด๋„๋ก” ์„ค๊ณ„ํ•˜๋Š” ๊ฒƒ

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ์ด๋Ÿฌํ•œ ๊ตฌ์กฐ๋ฅผ ์‹ค์ œ ์ฝ”๋“œ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋‚˜์”ฉ ๊ตฌํ˜„ํ•ด๋ณด๋ฉฐ
'์‹ค์‹œ๊ฐ„์ฒ˜๋Ÿผ ๋ณด์ด๋Š” ๊ฑธ์Œ ์ˆ˜ ์œ„์ ฏ'์ด ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ์™„์„ฑ๋˜๋Š”์ง€ ํ๋ฆ„์„ ์ •๋ฆฌํ•ด๋ณด์•˜๋‹ค.
HealthKit๊ณผ WidgetKit์˜ ํŠน์„ฑ์„ ์ดํ•ดํ•˜๊ณ  ๋‚˜๋ฉด,
์œ„์ ฏ์„ ๋‹จ์ˆœํ•œ ํ‘œ์‹œ์šฉ ์š”์†Œ๊ฐ€ ์•„๋‹Œ
๊ธฐ๊ธฐ ๋ฐ์ดํ„ฐ์™€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์—ฐ๋™๋˜๋Š” ํ•˜๋‚˜์˜ UI ์ปดํฌ๋„ŒํŠธ๋กœ ๋ฐ”๋ผ๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

๊ธฐํšŒ๊ฐ€ ๋œ๋‹ค๋ฉด, ๊ฑธ์Œ ์ˆ˜ ๊ทธ๋ž˜ํ”„, ๋ชฉํ‘œ ๋Œ€๋น„ UI, ๋‹ค์–‘ํ•œ ์œ„์ ฏ ํฌ๊ธฐ ๋Œ€์‘ ๋“ฑ
UIโˆ™๋ฐ์ดํ„ฐ ํ‘œํ˜„ ํ™•์žฅ์— ๋Œ€ํ•œ ๋‚ด์šฉ๋„ ๋ณ„๋„๋กœ ์ •๋ฆฌํ•ด๋ณผ ์ƒ๊ฐ์ด๋‹ค.

 


 

728x90