Coding01

Coding 点滴

0%

学习 iOS Widgets 开发之睡眠小组件

这是我的第二个小组件,有关健康数据的获取和展示,今天我以「睡眠」时间为例。

设置权限

iOS 提供了 HealthKit 包,专门用于获取健康数据,在我的自我量化文章中有提到。

首先需要将 HealthKit 权限打开。

在 Info 中,加入获取健康权限的说明:

读取健康数据

在代码中,我们需要先在小组件的宿主 APP 中声明获取你需要的健康权限,本文以「睡眠时间」为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class SleepInteractor {

let healthStore = HKHealthStore()

func retrieveSleepWithAuth(completion: @escaping (String) -> ()) {

let sleepType = HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!

let typestoRead = Set([sleepType])

let typestoShare = Set([sleepType])

if (healthStore.authorizationStatus(for: sleepType) != HKAuthorizationStatus.sharingAuthorized) {

healthStore.requestAuthorization(toShare: typestoShare, read: typestoRead) { (success, error) -> Void in
if success == false {
NSLog(" Display not allowed")
} else {
self.retrieveSleep(completion: completion)
}
}
} else {
self.retrieveSleep(completion: completion)
}
}

先判断 APP 是否获取了 HKCategoryTypeIdentifier.sleepAnalysis 权限,如果没有,就直接获取。

获取权限后,我们就可以试试拿到我们的睡眠数据了。具体函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
func retrieveSleep(completion: @escaping (String) -> ()) {
let startDateString = "2020-09-22 00:00:00"
let endDateString = "2020-09-22 23:59:59"

let dateFormatter = DateFormatter()

dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"

let startDate = dateFormatter.date(from: startDateString)

let endDate = dateFormatter.date(from: endDateString)

let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: HKQueryOptions.strictEndDate)

// first, we define the object type we want
if let sleepType = HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis) {

// Use a sortDescriptor to get the recent data first
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)

// we create our query with a block completion to execute
let query = HKSampleQuery(sampleType: sleepType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: [sortDescriptor]) { (query, tmpResult, error) -> Void in

if error != nil {
return
}

var totalSeconds : Double = 0

if let result = tmpResult {
for item in result {
if let sample = item as? HKCategorySample {

let timeInterval = sample.endDate.timeIntervalSince(sample.startDate)

totalSeconds = totalSeconds + timeInterval
}
}
}

let result =
String(Int(totalSeconds / 3600)) + "小时 " +
String(Int(totalSeconds.truncatingRemainder(dividingBy: 3600) / 60)) + "分 " +
String(Int(totalSeconds.truncatingRemainder(dividingBy: 3600)
.truncatingRemainder(dividingBy: 60))) + "秒"

completion(result)
}

// finally, we execute our query
healthStore.execute(query)
}
}

代码就很简单了,指定一个时间间隔,拿到所有这个时间段内的所有睡眠时间,然后进行累加,输出。

显示睡眠时间数据

对于如何创建一个小组件,完全可以参考上一篇文章:学习 iOS Widgets 开发之 exchange 汇率转换工具,整个小组件的格式和内容基本一致,只有获取数据函数不同罢了。

1
2
3
4
5
6
7
8
9
10
11
12
func getTimeline(for configuration: SleepIntent, in context: Context, completion: @escaping (Timeline<SleepEntry>) -> Void) {
let currentDate = Date()
let refreshDate = Date() + 1.days
//逃逸闭包传入匿名函数 当调用completion时调用该匿名函数刷新Widget
SleepInteractor().retrieveSleepWithAuth { result in

let entry = SleepEntry(date: currentDate, data: result)

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

得到睡眠数据后,渲染到 View 上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import SwiftUI

struct SleepView: View {
let data: String

var redColor = Color(UIColor(displayP3Red: 1, green: 15/255, blue: 83/255, alpha: 1))

@Environment(\.widgetFamily) var family

var body: some View {
HStack(spacing: 20) {
// 1. Dislpay data
VStack(alignment: .center) {
Text(data)
.bold()
.font(.system(size: 28))
.foregroundColor(Color.black)
.shadow(color: .gray, radius: 15, x: 7, y: 7)
.minimumScaleFactor(0.5)
}

// 2. Make responsive widget, by setting up sepearate view for systemMedium family widget
if family == .systemMedium {
VStack(alignment: .center) {
Text("睡觉时间")
.bold()
.font(.system(size: 12))
.foregroundColor(redColor)
.shadow(color: .gray, radius: 15, x: 7, y: 7)
.minimumScaleFactor(0.5)
Text("💤️")
.bold()
.font(.system(size: 40))
.foregroundColor(Color.black)
.shadow(color: .gray, radius: 15, x: 7, y: 7)
.minimumScaleFactor(0.5)
}
}
}.padding(.all, 10)
}
}

运行看看效果了,因为我的 iWatch 坏了一直没修,看了看「健康」APP,有关睡眠的数据停留在 2020 年 9 月 22 日,所以我就以当天为例:

把小组件放在首页上,看看效果,基本和「健康」APP 一致:

总结

下一步就是将「2020 年 9 月 22 日」时间改为「昨天」,保证每天都能获取前一天的睡眠数据,同时,我也要把 iWatch 修好,做好睡眠数据的监控,实时掌握自己的睡眠质量。

下下一步,把更多健康数据以各种形式的「小组件」呈现出来,期待下吧~

Welcome to my other publishing channels