๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐ŸŽ iOS/iOS

[iOS] ๊ฑด๊ฐ• ๋ฐ์ดํ„ฐ ์‚ฌ์šฉํ•ด๋ณด๊ธฐ(feat.HealthKit)

by Fomagran ๐Ÿ’ป 2021. 6. 8.
728x90
๋ฐ˜์‘ํ˜•

 

์•ˆ๋…•ํ•˜์„ธ์š” Foma ๐Ÿ‘Ÿ ์ž…๋‹ˆ๋‹ค!

 

์˜ค๋Š˜์€ ๋‚ด ์•„์ดํฐ์— ์žˆ๋Š” ๋‚˜์˜ ๊ฑด๊ฐ• ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€๋ณด๊ณ  ๋˜ ๊ธฐ๋กํ•ด๋ณด๋Š” ๊ฒƒ์„ ์ •๋ฆฌํ•ด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค!

 

๋ฐ”๋กœ ์‹œ์ž‘ํ• ๊ฒŒ์š”~


Info.plist

 

์•„๋ž˜์™€ ๊ฐ™์ด ๋‘ ์š”์ฒญ์„ ๋„ฃ์–ด์ค๋‹ˆ๋‹ค.

 

NSHealthShareUsageDescription ๋Š” ์ƒˆ๋กœ์šด ๊ฑด๊ฐ• ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์„ ๋•Œ

 

NSHealthUpdateUsageDescription ๋Š” ๊ธฐ์กด์˜ ๊ฑด๊ฐ• ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ฌ ๋•Œ ํ•„์š”ํ•ด์š”.

 

<key>NSHealthShareUsageDescription</key>
<string>๊ฑด๊ฐ• ๋ฐ์ดํ„ฐ๋ฅผ ์“ธ๋•Œ ํ•„์š”ํ•ด์š”.</string>

<key>NSHealthUpdateUsageDescription</key>
<string>๊ฑด๊ฐ• ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋Š”๋ฐ ํ•„์š”ํ•ด์š”.</string>

Signing & Capability

 

์•ฑ์˜ Targets์— Signing & Capbility์—์„œ ์•„๋ž˜์™€ ๊ฐ™์ด HealthKit์„ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”!

 


๋ฐ์ดํ„ฐ ์„ธํŒ…

 

HealthKit์„ import ํ•ด์ฃผ์„ธ์š”!

 

import HealthKit

 

๊ฐ€์žฅ ๋จผ์ € HKHealthStore ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•ด์ค๋‹ˆ๋‹ค.

 

let healthStore = HKHealthStore()
    

 

HKHealthStore๋Š” ํ—ฌ์Šคํ‚ท์˜ ๋ชจ๋“  ๋ฐ์ดํ„ฐ์˜ ํ—ˆ์šฉ์„ ๊ด€๋ฆฌํ•˜๋Š” ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.

 

 

 

์•„๋ž˜์™€ ๊ฐ™์ด ํ—ฌ์Šค์Šคํ† ์–ด์— ๊ถŒํ•œ์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.

 

๊ทผ๋ฐ ๊ถŒํ•œ์„ ์š”์ฒญํ•˜๊ธฐ ์œ„ํ•ด์„  ๋‘๊ฐ€์ง€ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ํ•„์š”ํ•œ๋ฐ์š”.

 

toShare๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์“ฐ๊ธฐ ์œ„ํ•œ ์š”์ฒญ, read๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ธฐ ์œ„ํ•œ ์š”์ฒญ์ž…๋‹ˆ๋‹ค.

 

 

 

์ €๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜๋ฉด ์ •๋ณด์— ๋Œ€ํ•œ ์“ฐ๊ธฐ์™€ ์ฝ๊ธฐ ํƒ€์ž…์„ ์ƒ์„ฑํ•ด์ค๋‹ˆ๋‹ค.

 

    let typeToShare:HKCategoryType? = HKObjectType.categoryType(forIdentifier: .sleepAnalysis)
    
    let typeToRead:HKSampleType? = HKObjectType.categoryType(forIdentifier: .sleepAnalysis)

 

์šฉ๋„์— ๋”ฐ๋ผ์„œ ๋‹ค์–‘ํ•˜๊ฒŒ ํƒ€์ž…์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

์ˆ˜๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ๋ฐฐ์—ด์„ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.

var sleepData:[HKCategorySample] = []

๋ฐ์ดํ„ฐ ์š”์ฒญ 

 func configure() {
        if !HKHealthStore.isHealthDataAvailable() {
            requestAuthorization()
        }else {
            retrieveSleepData()
        }
    }
    
    func requestAuthorization() {
        
        self.healthStore.requestAuthorization(toShare: Set([typeToShare!]), read: Set([typeToRead!])) { success, error in
            if error != nil {
                print(error.debugDescription)
            }else{
                if success {
                    print("๊ถŒํ•œ์ด ํ—ˆ๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
                }else{
                    print("๊ถŒํ•œ์ด ์•„์ง ์—†์–ด์š”.")
                }
            }
        }
    }

 

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์š”์ฒญ ํ™”๋ฉด์ด ๋‚˜์˜ฌ๊ฑฐ์—์š”.

 


๋ฐ์ดํ„ฐ ๋ฐ›์•„์˜ค๊ธฐ

 

predicate๋กœ ๋ฐ›์•„์˜ฌ ๋ฐ์ดํ„ฐ์˜ ๋‚ ์งœ ๋ฒ”์œ„๋ฅผ ์„ค์ •ํ•ด์ค๋‹ˆ๋‹ค.

 

sortDescriptor๋กœ ๋‚ด๋ฆผ์ฐจ์ˆœ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ต๋‹ˆ๋‹ค.

 

query์— ์œ„ ๋‘ ์„ค์ •์„ ๋„ฃ๊ณ  limit์— ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋งŒํผ ์„ค์ •ํ•ด์ค๋‹ˆ๋‹ค.

 

๋งˆ์ง€๋ง‰์œผ๋กœ ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ๋ฅผ sleepData์— ๋„ฃ์–ด์ค๋‹ˆ๋‹ค. (ํ…Œ์ด๋ธ”๋ทฐ์— ๋ฐ์ดํ„ฐ๋ฅผ ๋„์šธ๊ฑฐ๋ผ ์•„๋ž˜์™€ ๊ฐ™์ด reload๋ฅผ ํ•ด์ค๋‹ˆ๋‹ค.)

 

  func retrieveSleepData() {
        let start = makeStringToDate(str: "2021-05-01")
        let end = Date()
        let predicate = HKQuery.predicateForSamples(withStart:start, end: end, options: .strictStartDate)
        let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
        let query = HKSampleQuery(sampleType: typeToRead!, predicate: nil, limit: 30, sortDescriptors: [sortDescriptor]) { [weak self] (query, sleepResult, error) -> Void in
            if error != nil {
                return
            }
            if let result = sleepResult {
                DispatchQueue.main.async {
                    self?.sleepData = result as? [HKCategorySample] ?? []
                    self?.table.reloadData()
                }
            }
        }
        healthStore.execute(query)
    }

๋ฐ์ดํ„ฐ ํ™•์ธํ•˜๊ธฐ

 

์•„๋ž˜์™€ ๊ฐ™์ด ๋ฐ์ดํŠธ๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋ฐ”๊ฟ”์ฃผ๋Š” ํ•จ์ˆ˜์™€ ๋ฌธ์ž์—ด์„ ๋ฐ์ดํŠธ๋กœ ๋ฐ”๊ฟ”์ฃผ๋Š” ํ•จ์ˆ˜๋ฅผ ์„ธํŒ…ํ•ด์ค๋‹ˆ๋‹ค.

 

   func makeStringToDate(str:String) -> Date {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"
        dateFormatter.locale = Locale(identifier: "ko_KR")
        dateFormatter.timeZone = TimeZone(abbreviation: "KST")

        return dateFormatter.date(from: str)!
    }
    
   func makeStringToDateWithTime(str:String) -> Date {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd-HH:mm"
        dateFormatter.locale = Locale(identifier: "ko_KR")
        dateFormatter.timeZone = TimeZone(abbreviation: "KST")

        return dateFormatter.date(from: str)!
    }
    
    func dateToString(date:Date) -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"

       return dateFormatter.string(from: date)
    }
    
    func dateToStringOnlyTime(date:Date) -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "HH:mm"

       return dateFormatter.string(from: date)
    }

 

 

๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ์ด์šฉํ•ด์„œ ํ…Œ์ด๋ธ”๋ทฐ ๋ฐ์ดํ„ฐ์†Œ์Šค์— ์–ธ์ œ๋ถ€ํ„ฐ ์–ธ์ œ๊นŒ์ง€ ์žค๋Š”์ง€ ํ™•์ธํ•ด์ค๋‹ˆ๋‹ค.

 

extension ViewController:UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return sleepData.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let sleep = sleepData[indexPath.row]
        
        let cell = table.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        
        let date = dateToString(date: sleep.startDate)
        let start = dateToStringOnlyTime(date: sleep.startDate)
        let end = dateToStringOnlyTime(date: sleep.endDate)
        cell.textLabel?.text = "\(date): \(start)๋ถ€ํ„ฐ ~ \(end)๊นŒ์ง€ ์žค๋„ค์š”."
        
        return cell
    }
}

 

์•„๋ž˜์™€ ๊ฐ™์ด 5์›” 1์ผ๋ถ€ํ„ฐ ํ˜„์žฌ๊นŒ์ง€ ์ œ๊ฐ€ ๋ช‡์‹œ๋ถ€ํ„ฐ ๋ช‡์‹œ๊นŒ์ง€ ์žค๋Š”์ง€ ๋ฐ์ดํ„ฐ๊ฐ€ ์ž˜ ๋‚˜์˜ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค!

 


๋ฐ์ดํ„ฐ ์ €์žฅํ•˜๊ธฐ

 

์•Œ์•„๋ณด๊ธฐ ์‰ฝ๊ฒŒ 6์›” 8์ผ 10์‹œ๋ถ€ํ„ฐ 7์›” 1์ผ 11์‹œ๊นŒ์ง€ ์ˆ˜๋ฉดํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

   func saveSleepData() {
        let start = makeStringToDateWithTime(str: "2021-07-10 10:00")
        let end = makeStringToDateWithTime(str: "2021-07-10 11:00")
        
        let object = HKCategorySample(type: typeToShare!, value: HKCategoryValueSleepAnalysis.inBed.rawValue, start: start,end: end)
        healthStore.save(object, withCompletion: { (success, error) -> Void in
            if error != nil {
                return
            }
            if success {
                print("์ˆ˜๋ฉด ๋ฐ์ดํ„ฐ ์ €์žฅ ์™„๋ฃŒ!")
                self.retrieveSleepData()
            } else {
                print("์ˆ˜๋ฉด ๋ฐ์ดํ„ฐ ์ €์žฅ ์‹คํŒจ...")
            }
        })
    }

 

์ด์ œ ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด retrieveSleepData์—์„œ predicate๋ฅผ nil๋กœ ์„ค์ •ํ•ด์ค€ ๋’ค ์‹คํ–‰์‹œํ‚ต๋‹ˆ๋‹ค. (๋ฒ”์œ„๋ฅผ ์ •ํ•ด์ฃผ์ง€ ์•Š๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค,)

 

HKSampleQuery(sampleType: typeToRead!, predicate: nil, limit: 30,...)

 

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐ€์žฅ ์ตœ๊ทผ์— ์ถ”๊ฐ€ํ•œ 7์›”์— ์ˆ˜๋ฉดํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค!

 

 


์˜ค๋Š˜์€ ์ด๋ ‡๊ฒŒ ๊ฑด๊ฐ• ๋ฐ์ดํ„ฐ ์ค‘ ์ˆ˜๋ฉด ์ •๋ณด๋ฅผ ๋ฐ›์•„์˜ค๊ณ  ๋˜ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ–ˆ๋Š”๋ฐ์š”.

 

์ˆ˜๋ฉด๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ฒด์˜จ,๊ฑธ์Œ,์‹ฌ์žฅ๋ฐ•๋™์ˆ˜ ๋“ฑ ์ง„์งœ ์—„์ฒญ ๋งŽ์€ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

ํ˜น์‹œ๋ผ๋„ ๊ถ๊ธˆํ•œ ์ ์ด ์žˆ๊ฑฐ๋‚˜ ํ‹€๋ฆฐ ์ ์ด ์žˆ๋‹ค๋ฉด ๋Œ“๊ธ€๋กœ ์•Œ๋ ค์ฃผ์„ธ์š”!


Source Code

 


Reference

 

 

Apple Developer Documentation

 

developer.apple.com

 

728x90
๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€