步进器

1
2
3
Stepper(value: $sleepAmount, in: 4...12, step: 0.25) {
Text("\(sleepAmount, specifier: "%.2f") hours")
}

in代表范围,step代表每次点击变化的幅度。value绑定之前设置过的@state状态。

日期选择器

使用日期选择器之前需要创建一个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,但这超出了该项目的范围!

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

使用Create ML训练模型

在iOS 11中,设备上机器学习已从“极度困难”变为“相当可能,而且功能强大”,这要归功于一个苹果框架:Core ML。一年后,Apple引入了另一个名为Create ML的框架,该框架在列表中添加了“易于执行”,然后第二年之后,Apple引入了Create ML应用程序,该应用程序拖累了整个过程。所有这些工作的结果是,任何人都可以将机器学习添加到他们的应用程序中。

Core ML能够处理各种训练任务,例如识别图像,声音甚至运动,但是在这种情况下,我们将研究表格回归。这是一个花哨的名字,在机器学习中很常见,但是它的真正含义是我们可以在Create ML上放置大量类似于电子表格的数据,并要求它找出各种值之间的关系。

机器学习分两个步骤进行:我们训练模型,然后让模型进行预测。训练是计算机查看我们所有数据以弄清我们拥有的所有值之间的关系的过程,而在大型数据集中,这可能需要很长时间-轻松几个小时,甚至可能更长。预测是在设备上完成的:我们将训练后的模型提供给它,它将使用以前的结果对新数据进行估算。

现在开始培训过程:请在Mac上打开“创建ML”应用。如果你不知道它在哪里,可以通过Xcode菜单并选择Open Developer Tool> Create ML从Xcode启动它。

Create ML应用程序要做的第一件事是要求你创建一个项目或打开一个上一个项目–请单击“新建文档”以开始使用。你会看到有很多模板可供选择,但是如果向下滚动到底部,则会看到Tabular Regressor。请选择该选项,然后按Next。对于项目名称,请输入BetterRest,然后按Next,选择你的桌面,然后按Create。

首先,在这里创建ML似乎有些棘手,因为你会看到一个包含很多选项的屏幕。不过,请不要担心-一旦我引导你完成,就不会那么困难。

第一步是向Create ML提供一些训练数据。这是要查看的原始统计数据,在我们的案例中,该统计数据包含四个值:当某人想要醒来时,他们认为自己希望拥有多少睡眠,每天喝多少咖啡以及他们有多少睡眠实际需要。

我已经在BetterRest.csv中为你提供了此数据,该文件位于该项目的项目文件中。这是Create ML可以使用的逗号分隔值数据集,我们的第一项工作是导入该数据集。

因此,在“创建ML”中,在“数据输入”下查找,然后在“培训数据”标题下选择“选择”。当你按Select File时,它将打开一个文件选择窗口,你应该选择BetterRest.csv。

重要提示:此CSV文件包含用于此项目的示例数据,不应将其用于与健康相关的实际工作。

下一步是确定目标,这是我们希望计算机学习预测的值,而功能是我们希望计算机检查以预测目标的值。例如,如果我们选择某人认为需要多少睡眠和实际需要多少睡眠作为功​​能,我们可以训练计算机以预测他们喝了多少咖啡。

在这种情况下,我希望你为目标选择“ actualSleep”,这意味着我们希望计算机学习如何预测他们实际需要多少睡眠时间。现在,按“选择功能”,然后选择所有三个选项:“唤醒”,“估计睡眠”和“咖啡” –我们希望计算机在生成预测时将所有这三个因素都考虑在内。

“选择功能”按钮下面是该算法的下拉按钮,有五个选项:“自动”,“随机森林”,“增强树”,“决策树”和“线性回归”。每个人都采用不同的方法来分析数据,尽管这不是一本关于机器学习的书,但我还是想简要解释一下它们的工作。

线性回归是最容易理解的,因为它几乎完全是我们大脑的工作方式。他们试图通过将变量视为线性函数(例如)的一部分来估计变量之间的关系applyAlgorithm(var1, var2, var3)。线性回归的目标是能够在所有数据点上绘制一条直线,其中直线与每个数据点之间的平均距离应尽可能小。

决策树回归器形成自然的树状结构,使我们可以将信息组织为一系列选择。尝试设想这几乎就像一个有20个问题的游戏:“你是人还是动物?如果你是一个人,你是活着还是死了?如此等等–每次树可以根据每个问题的答案而分支出来,直到最终有了一个确定的答案。

增强型树回归器使用一系列决策树来工作,其中每棵树旨在纠正前一棵树中的任何错误。例如,第一个决策树会尽最大的可能去寻找一个好的预测,但是下降了20%。然后,将其传递到第二个决策树以进行进一步细化,然后重复该过程–但这一次,误差降至10%。那进入了第三棵树,误差降低到8%,第四棵树,误差降低到7%。

随机森林模型与增强树相似,但略有不同:对于增强树,树中的每个决策都可以访问所有可用数据,而对于随机树,每个树只能访问数据的一个子集。

这听起来可能很奇怪:你为什么要保留数据?好吧,想象一下你正面临一个编码问题并试图提出一个解决方案。如果你要求同事提供想法,他们将根据你所了解的知识为你提供一些想法。如果你要求其他同事提供想法,他们可能会根据他们所知道的知识为你提供不同的想法。如果你向一百位同事提出想法,你将获得一系列解决方案。

每个同事的背景,教育程度和工作经历都与其他同事不同,因此你会得到一系列建议。但是,如果你将每个人的建议平均化(不管大多数人怎么说,不管他们是怎么做出决定的),那么你就有最好的机会获得正确的解决方案。

这就是随机森林回归器的工作方式:每个决策树都有自己的数据视图,该视图与其他树不同,并且通过将所有预测结合在一起以形成平均值,你很有可能获得良好的结果。

有用的是,有一个自动选项可以尝试自动选择最佳算法。这并不总是正确的,实际上它确实极大地限制了我们的选择范围,但是对于这个项目来说,已经足够了。

准备就绪后,单击窗口标题栏中的“训练”按钮。几秒钟后–我们的数据很小!–你会看到一些结果指标。我们关心的值称为“均方根误差”,你应该获得大约180的值。这意味着该模型平均能够预测建议的准确睡眠时间,而误差仅为180秒或三分钟。

更好的是,如果你在右上角看到一个MLModel图标,上面写着“输出”,它的文件大小约为438字节。Create ML占用了180KB的数据,并将其压缩为438个字节-几乎没有。

现在,我知道438个字节听起来很小,但是值得一提的是,几乎所有这些字节都是元数据:作者的名字就在其中,默认名称为“经过训练用于回归的机器学习模型”。它甚至对所有字段的名称进行编码:wake,estimateSleep,coffee和actualSleep。

硬数据占用的实际空间量-如何根据我们的三个变量来预测所需的睡眠量-远远低于100个字节。这是可能的,因为Create ML实际上并不关心值是什么,而只关心关系是什么。因此,它花费了数十亿个CPU周期,为每个功能尝试了权重的各种组合,以查看哪些功能产生的值与实际目标值最接近,并且一旦知道了最佳算法,便会简单地存储该值。

既然我们的模型已经训练完毕,我希望你将图标从Create ML拖到你的桌面上,以便我们在代码中使用它。

提示:如果你想再次尝试训练-也许尝试使用我们可用的各种算法-单击“创建ML”窗口右下角的“制作副本”。

参考资料

查看下一天的SwiftUI学习笔记

关于100days英文课程