计算适合睡眠的时间,暂且估计可能要重复练习很多次,这里在日期中的处理还是比较麻烦的。

日期选择器

使用日期选择器之前需要创建一个Date()属性:

1
@State private var wakeUp = Date()

然后,你可以将其绑定到日期选择器,如下所示:

1
2
3
var body: some View {
DatePicker("Please enter a date", selection: $wakeUp)
}

如果要隐藏日期选择器的标题,除了删除掉文本标签(这样会使屏幕阅读器的用户无法使用)还可以将日期选择器包裹在Form表单

1
2
3
4
5
var body: some View {
Form {
DatePicker("Please enter a date", selection: $wakeUp)
}
}

或者添加..labelsHidden()修饰符:

1
2
3
4
var body: some View {
DatePicker("Please enter a date", selection: $wakeUp)
.labelsHidden()
}

日期选择器还有其他的属性,首先,我们可以displayedComponents用来决定用户应该看到哪种选项:

  • 如果不提供此参数,则用户会看到一天,一小时和一分钟。
  • 如果使用.date用户,请查看月,日和年。
  • 如果使用.hourAndMinute用户,则仅会看到小时和分钟。
1
DatePicker("Please enter a time", selection: $wakeUp, displayedComponents: .hourAndMinute)

最后,有一个in参数与之作用相同Stepper:我们可以为它提供一个日期范围,并且日期选择器将确保用户不能选择超出范围。

现在,我们已经使用一段时间了,你已经习惯于看到诸如1 … 5或的东西0 ..< 10,但是我们也可以将Swift日期与范围一起使用。例如:

1
2
3
4
5
6
7
8
// when you create a new Date instance it will be set to the current date and time
let now = Date()

// create a second Date instance set to one day in seconds from now
let tomorrow = Date().addingTimeInterval(86400)

// create a range from those two
let range = now ... tomorrow

确实对有用DatePicker,但还有更好的地方:Swift让我们形成一个单边范围 –我们指定起始或结束但不同时指定两个范围的范围,而让Swift推断另一边。

例如,我们可以这样创建一个日期选择器:

1
DatePicker("Please enter a date", selection: $wakeUp, in: Date()...)

这将允许将来使用所有日期,但不能使用过去的日期-读作“从当前日期到任何日期”。

让用户输入日期就像@State将类型的属性绑定Date到DatePickerSwiftUI控件一样容易,但是之后事情会变得更加混乱。

你会发现,处理日期非常困难。喜欢,真的很难。比你想象的要难。比我想象的要困难得多,而且我已经从事约会工作多年了。

看一下这个简单的例子:

1
2
3
let now = Date()
let tomorrow = Date().addingTimeInterval(86400)
let range = now ... tomorrow

这会创建一个从现在(Date()当前日期)到明天同一时间(86400是一天中的秒数)的范围。

这似乎很容易,但是整天有86,400秒吗?如果这样做的话,很多人将失业!想想夏时制:有时时钟前进(丢失一个小时),有时倒退(获得一个小时),这意味着那一天我们可能有23或25个小时。然后是leap秒:为了适应地球缓慢旋转而增加的时间。

如果你认为这很困难,请尝试在Mac的终端上运行它:cal。这会打印出当月的简单日历,向你显示星期几。现在尝试运行cal 9 1752,它向你显示1752年9月的日历–由于日历从Julian移到Gregorian,你会发现整整12天丢失了。

现在,我之所以这么说并不是为了吓你-毕竟在我们的程序中日期是不可避免的。相反,我希望你理解对于任何重要的事情-在我们的代码中实际使用的日期的任何重要使用-我们都应该依靠Apple的框架进行计算和格式化。

在项目中,我们将通过三种方式使用日期:

  • 选择一个合理的默认“唤醒”时间。
  • 阅读他们想醒来的小时和分钟。
  • 整齐地显示他们建议的就寝时间。

如果需要,我们可以手动完成所有操作,但是你将进入夏令时,秒和公历日历领域。

更好的是让iOS为我们完成所有艰苦的工作:工作量少得多,并且无论用户的区域设置如何,都可以保证它是正确的。

让我们从选择合理的唤醒时间开始,逐一解决每个问题。

读取或写入Date的特定部分

如你所见,Swift为我们Date提供了处理日期的功能,其中封装了年,月,日,时,分,秒,时区等。但是,我们不想考虑其中的大部分-我们想说“给我早上8点的起床时间,无论今天是星期几。”

Swift为此有一种略有不同的类型,称为DateComponents,它使我们可以读取或写入日期的特定部分而不是整个内容。

因此,如果我们想要一个表示今天上午8点的日期,我们可以编写如下代码:

1
2
3
4
var components = DateComponents()
components.hour = 8
components.minute = 0
let date = Calendar.current.date(from: components)

现在,由于日期验证方面的困难,该date(from:)方法实际上返回了一个可选日期,因此最好使用nil合并来表示“如果失败,请给我返回当前日期”,如下所示:

1
let date = Calendar.current.date(from: components) ?? Date()

提取Date中小时和分钟的组成部分

第二个挑战是我们如何阅读他们想要醒来的时间。请记住,DatePicker这势必Date会给我们带来很多信息,因此我们需要找到一种仅提取小时和分钟组成部分的方法。

再一次,你DateComponents需要进行救援:我们可以要求iOS从某个日期开始提供特定的组件,然后再将其读出来。一个令人困扰的事情是,我们要求的值和由于工作方式而获得的值之间存在脱节DateComponents:我们可以要求小时和分钟,但是我们将返回一个DateComponents实例,该实例的所有属性都带有可选值。是的,我们知道小时和分钟会在那儿,因为这些正是我们所要求的,但是我们仍然需要拆开可选件或提供默认值。

因此,我们可能会编写如下代码:

1
2
3
let components = Calendar.current.dateComponents([.hour, .minute], from: someDate)
let hour = components.hour ?? 0
let minute = components.minute ?? 0

将日期转化为字符串

最后的挑战是如何格式化日期和时间,而Swift再次为我们提供了一种特定的类型来为我们完成大部分工作。这次称为DateFormatter,它使我们可以通过多种方式将日期转换为字符串。

例如,如果我们只想从日期开始的时间,则可以这样写:

1
2
3
let formatter = DateFormatter()
formatter.timeStyle = .short
let dateString = formatter.string(from: Date())

我们还可以设置.dateStyle为获取日期值,甚至可以使用完全自定义格式进行传递dateFormat,但这超出了该项目的范围!

关键是约会很辛苦,但苹果公司为我们提供了很多帮助程序,以减少约会的难度。如果你学习好使用它们,你将编写更少的代码,并且也编写更好的代码!

保留两位小数

Text("$ \(finalmon, specifier: "%.2f")")

警告提示框

1
2
3
.alert(isPresented: $showingAlert){
Alert(---
title: Text(alertTitle), message: Text(alertMessage), dismissButton: .default(Text("OK")))

代码

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//
// ContentView.swift
// Practise2-4
//
// Created by 张洪Hoo on 2020/4/2.
// Copyright © 2020 张洪Hoo. All rights reserved.
//

import SwiftUI

struct ContentView: View {
@State private var wakeUp = hoursAndMinutes
@State private var sleeph: Double = 8
@State private var coffeec = 1

@State private var alertTitle = ""
@State private var alertMessage = ""
@State private var alertShow = false

static var hoursAndMinutes: Date {
var components = DateComponents()
components.hour = 8
components.minute = 0

return Calendar.current.date(from: components) ?? Date()
}

var body: some View {

NavigationView {
Form {

VStack(alignment: .leading) {
Text("你什么时候起床?")
.bold()
DatePicker(selection: $wakeUp, displayedComponents: .hourAndMinute, label: { Text("Please enter a date") })
.labelsHidden()
.datePickerStyle(WheelDatePickerStyle())
}

VStack(alignment: .leading) {
Text("你的期望睡眠时间")
.bold()
Stepper(value: $sleeph, in: 4...12, step: 0.25) {
Text("\(self.sleeph, specifier: "%.2f") 小时")
}
}

VStack(alignment: .leading) {
Text("每日饮用咖啡杯数")
.bold()
Stepper(value: $coffeec, in: 1...10) {
Text("\(self.coffeec) 杯")
}
}
}


.navigationBarTitle(Text("最佳就寝时间"))
.navigationBarItems(trailing: Button(action: {
//do something
self.calculateBedTime()
}) {
Text("计算")
})
}
.alert(isPresented: $alertShow){
Alert(---
title: Text("\(self.alertTitle)"), message: Text("\(self.alertMessage)"), dismissButton: .default(Text("确定")))
}


}

func calculateBedTime() {
let model = SleepCalculateor()
let cocudate = Calendar.current.dateComponents([.hour, .minute], from: wakeUp)

let hour = (cocudate.hour ?? 0) * 60 * 60
let minute = (cocudate.minute ?? 0) * 60

do {
let prediction = try model.prediction(wake: Double(hour + minute), estimatedSleep: sleeph, coffee: Double(coffeec))

let sleepTime = wakeUp - prediction.actualSleep

let formatter = DateFormatter()

formatter.timeStyle = .short

alertMessage = formatter.string(from: sleepTime)
alertTitle = "你应该在这之前睡觉"

} catch {
self.alertTitle = "发生错误"
self.alertMessage = "这个错误无法预料"

}

self.alertShow = true
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

还是需要再练习一次,主要针对时间的处理上。

一个是通过components来存储时间的不同数据,一个是涉及到时间的计算。还有就是通过DateFormatter来将时间转换为字符串。

SwiftPlayground 进度