SwiftUI 学习笔记 51:项目 4 复习 (Practise 2-4)
计算适合睡眠的时间,暂且估计可能要重复练习很多次,这里在日期中的处理还是比较麻烦的。
日期选择器
使用日期选择器之前需要创建一个Date()
属性:
1 | private var wakeUp = Date() |
然后,你可以将其绑定到日期选择器,如下所示:
1 | var body: some View { |
如果要隐藏日期选择器的标题,除了删除掉文本标签(这样会使屏幕阅读器的用户无法使用)还可以将日期选择器包裹在Form
表单
1 | var body: some View { |
或者添加..labelsHidden()
修饰符:
1 | var body: some View { |
日期选择器还有其他的属性,首先,我们可以displayedComponents用来决定用户应该看到哪种选项:
- 如果不提供此参数,则用户会看到一天,一小时和一分钟。
- 如果使用.date用户,请查看月,日和年。
- 如果使用.hourAndMinute用户,则仅会看到小时和分钟。
1 | DatePicker("Please enter a time", selection: $wakeUp, displayedComponents: .hourAndMinute) |
最后,有一个in参数与之作用相同Stepper:我们可以为它提供一个日期范围,并且日期选择器将确保用户不能选择超出范围。
现在,我们已经使用一段时间了,你已经习惯于看到诸如1 … 5或的东西0 ..< 10,但是我们也可以将Swift日期与范围一起使用。例如:
1 | // when you create a new Date instance it will be set to the current date and time |
确实对有用DatePicker,但还有更好的地方:Swift让我们形成一个单边范围 –我们指定起始或结束但不同时指定两个范围的范围,而让Swift推断另一边。
例如,我们可以这样创建一个日期选择器:
1 | DatePicker("Please enter a date", selection: $wakeUp, in: Date()...) |
这将允许将来使用所有日期,但不能使用过去的日期-读作“从当前日期到任何日期”。
让用户输入日期就像@State将类型的属性绑定Date到DatePickerSwiftUI控件一样容易,但是之后事情会变得更加混乱。
你会发现,处理日期非常困难。喜欢,真的很难。比你想象的要难。比我想象的要困难得多,而且我已经从事约会工作多年了。
看一下这个简单的例子:
1 | let now = Date() |
这会创建一个从现在(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 | var components = DateComponents() |
现在,由于日期验证方面的困难,该date(from:)方法实际上返回了一个可选日期,因此最好使用nil合并来表示“如果失败,请给我返回当前日期”,如下所示:
1 | let date = Calendar.current.date(from: components) ?? Date() |
提取Date中小时和分钟的组成部分
第二个挑战是我们如何阅读他们想要醒来的时间。请记住,DatePicker这势必Date会给我们带来很多信息,因此我们需要找到一种仅提取小时和分钟组成部分的方法。
再一次,你DateComponents需要进行救援:我们可以要求iOS从某个日期开始提供特定的组件,然后再将其读出来。一个令人困扰的事情是,我们要求的值和由于工作方式而获得的值之间存在脱节DateComponents:我们可以要求小时和分钟,但是我们将返回一个DateComponents实例,该实例的所有属性都带有可选值。是的,我们知道小时和分钟会在那儿,因为这些正是我们所要求的,但是我们仍然需要拆开可选件或提供默认值。
因此,我们可能会编写如下代码:
1 | let components = Calendar.current.dateComponents([.hour, .minute], from: someDate) |
将日期转化为字符串
最后的挑战是如何格式化日期和时间,而Swift再次为我们提供了一种特定的类型来为我们完成大部分工作。这次称为DateFormatter,它使我们可以通过多种方式将日期转换为字符串。
例如,如果我们只想从日期开始的时间,则可以这样写:
1 | let formatter = DateFormatter() |
我们还可以设置.dateStyle为获取日期值,甚至可以使用完全自定义格式进行传递dateFormat,但这超出了该项目的范围!
关键是约会很辛苦,但苹果公司为我们提供了很多帮助程序,以减少约会的难度。如果你学习好使用它们,你将编写更少的代码,并且也编写更好的代码!
保留两位小数
Text("$ \(finalmon, specifier: "%.2f")")
警告提示框
1 | .alert(isPresented: $showingAlert){ |
代码
1 | // |
还是需要再练习一次,主要针对时间的处理上。
一个是通过components
来存储时间的不同数据,一个是涉及到时间的计算。还有就是通过DateFormatter
来将时间转换为字符串。
SwiftPlayground 进度
- 感谢你赐予我前进的力量