What you learned - 你学到了什么
在这一点上,您应该真的开始对SwiftUI的工作方式感到满意。我知道,对于某些人来说,这可能是一个巨大的心理障碍,因为我们失去了控制程序的精确流程的能力,而是需要构造整个状态,然后才在流程中进行设置。但是,您现在已经建立了四个完整的项目,并且拥有两个深层次的技术项目,所以希望您开始对SwiftUI的工作方式有所了解。
尽管您现在又拥有两个应用程序,但是您可以根据需要进行处理,同时还可以获得许多有价值的技能:
- 如何使用
Stepper
从用户那里读取数字,包括在标签为简单文本视图时使用其较短的格式。 - 让用户使用
DatePicker
输入日期,包括使用displayedComponents
参数控制日期或时间。 - 使用
Date
,DateComponents
和DateFormatter
在Swift中处理日期。 - 如何引入机器学习以利用现代iOS设备的全部功能。
- 使用
List
构建数据的滚动表,特别是如何直接从数据数组创建行。 - 使用
onAppear()
在显示视图时运行代码。 - 通过使用
Bundle
类查找路径,从我们的应用程序包中读取文件,包括从那里加载字符串。 - 使用
fatalError()
打断程序运行,以及为什么这实际上可能是一件好事。 - 如何使用
UITextChecker
检查字符串的拼写是否正确。 - 使用
animation()
修饰符隐式创建动画。 - 自定义具有延迟和重复的动画,并在“ease-in-ease-out”动画和“Sping”动画之间进行选择。
- 将
animation()
修饰符附加到绑定,因此我们可以直接从UI控件为更改设置动画。 - 使用
withAnimation()
创建显式动画。 - 将多个
animation()
修饰符附加到单个视图,以便我们可以控制动画堆栈。 - 使用
DragGesture()
允许用户四处移动视图,然后将其捕捉回到其原始位置。 - 使用SwiftUI的内置转换,并创建自己的转换。
是的,这仅仅是三个项目中的大量新信息,但是因为每个主题都被单独涵盖(“列表如何工作?”),然后又在真实项目的上下文中(“在这里实际使用列表)”),我希望一切都沉浸其中。如果没有,请不要害怕回到前面的章节中来——它们不会走开,所有这些都将帮助您掌握SwiftUI。
在继续之前,我想添加一件事:即使您可能已经开始了解SwiftUI的工作原理,您仍然会发现——也许经常!——很难准确表达您的意思。
动画就是一个典型的例子。对于动画,我们要说的是“使该按钮——在那里的那个按钮——立即旋转”。而且,SwiftUI确实不是为这种命令性的设计而设计的:我们不能仅仅说“使按钮旋转”。
这超出了我之前提到的心理碰撞。您可以了解SwiftUI的工作原理,但仍然空白以了解实现某些事情所需的时间。对于具有先验编程经验的人们来说,这是一个特殊的问题,因为他们习惯了另一种思维方式:他们有数月,数年甚至数十年的肌肉记忆力,很容易解决问题,但前提是他们准确规定一切行为。
请记住,在SwiftUI中,我们所有的视图以及所有动画都必须是我们状态的函数。这意味着我们不告诉按钮旋转,而是将按钮的旋转量附加到某个状态,然后适当修改该状态。通常这会令人沮丧,因为我们知道我们想去哪里,但不知道如何到达那里。
如果这在课程中给您带来打击,那就放松一下——这很正常,而且您并不孤单。如果您经过一两个小时的努力而又无法完全解决问题,请将其放在一边并尝试下一个项目,然后在一周左右的时间内回来。您将了解更多,并进行了更多的练习,再加上崭新的思想永远不会伤害您。
Key points - 关键点
在继续之前,我想更详细地讨论三件事。同样,这实际上只是为了确保您在继续进行之前已经完全理解了某些关键概念,并且希望它可以帮助您了解Swift和SwiftUI在下面的实际工作方式。
ForEach
和List
的范围
正如我已经说过几次,当我们在一个循环中创建视图时,SwiftUI需要了解如何唯一地标识每个项目,以便它可以对来回的数据进行动画处理。这本身并不复杂,但是有一种特殊的用法会把人们拒之门外,而且范围很广。
首先,让我们看一些代码:
ForEach(0..<5) {
Text("Row \($0)")
}
从0到5循环播放,每次打印出一些文本。SwiftUI可以确保每个项目都是唯一的,因为它在一个范围内计数,并且范围没有重复的值。
实际上,如果您查看我们ForEach
背后的SwiftUI代码,您会发现它实际上是这样的:
public init(_ data: Range, @ViewBuilder content: @escaping (Int) -> Content)
视图生成器(实际上是构成视图的东西)将从范围中获得一个整数,并有望发送回一些可以呈现的视图内容。
现在尝试编写以下代码:
ForEach(0...5) {
Text("Row \($0)")
}
从0到5计数,这意味着它将创建六个视图。或至少它会创建六个视图(如果它确实起作用)—— 该代码无法编译。
再次查看ForEach
想要的数据类型:Range
。这是一个整数范围,但它是一个非常特定的范围——还有另一个非常相似的类型,称为ClosedRange
,这就是导致问题的原因。
当我们写0 .. <5
时,我们得到一个Range
,但是当我们写0 ... 5
时,我们得到一个ClosedRange
。即使看起来与我们相似,斯威夫特也认为这两种范围类型是不同的,因此我们无法在ForEach
中使用封闭范围——目前暂时不可能,尽管我希望这会改变。
是什么组成了字符串
从我们的角度来看,很容易将字符串想象成一件相当琐碎的事情:一个字母,然后是另一个字母,然后是第三个,第四个字母,依此类推,也许还有一些标点符号分散在其中。但是实际上,字符串是Swift中一些最复杂的功能,值得花一点时间来了解正在发生的事情。
首先,您可能已经注意到不允许使用以下代码:
let name = "Paul"
let firstLetter = name[0]
试图读取字符串“ Paul”的第一个字母。如果我们要求人员“运行”该代码,他们会说“P”,这很有意义,因为这是第一个字母。
但是,实际上,字符串比单个字母复杂得多:许多表情符号是由多个字符组成的,它们背靠背描述它们所包含的内容。例如,简单的竖起大拇指表情符号具有多种肤色,这是通过基本表情符号(竖起大拇指)然后通过肤色修饰符(从浅到深)实现的。最终是多个单独的字符,但我们看到一个字符:具有特定颜色的竖起表情符号。
如果Swift单独对待这些字符,则阅读第一个字母将读取没有表情的竖起表情符号,而阅读第二个字母将读取没有竖起的表情色——前者可以接受,但不完全符合发送者的预期,而后者会很奇怪。
现在考虑这样的代码:
print(name.count)
这将打印出测试字符串中有多少个字符,这再次看起来很容易。但是,正如我们刚刚看到的,实际上应该将某些单个字符组合在一起以创建组合的含义,这意味着count
不能仅仅返回字符串中有多少个字符。取而代之的是,它需要从第一个字母开始,并计算每个唯一的字母(考虑到所有连接在一起的修饰符)才能得出总数。
速度不是很快,但是可以保证是正确的。如果没有别的,那就是我希望您摆脱的字符串:有时候它们可能会有些棘手,但是Swift代表我们做了很多工作,以确保我们不会偶然出错。这的确意味着在使用简单字符串时会从我们那里获取更多代码,但这也意味着将来我们会自动获得对高级字符串(包括您能想到的任何表情符号)的支持。
展平的应用Bundles
在Word Scramble中,我们在包中查找了start.txt,然后将其加载以供游戏使用。然后我解释说,所有iOS,macOS,tvOS和watchOS应用程序都作为捆绑包出售,这些捆绑包将其二进制文件(已编译的Swift程序),其Info.plist,其资产目录等结合在一起。
我没有提到的一件事是这些捆绑包的构建方式,尤其是我想提及资产目录和松散文件。
首先,资产目录是我们一直在存储要在应用程序中使用的图像的位置,它们不仅仅是组织图片的一种理想方式。实际上,当Xcode构建我们的资产目录时,它会遍历我们所有的图片并针对iOS设备对其进行优化,然后将结果放入可以有效加载的已编译资产目录中。随着资产目录的进一步发展,您会发现它们可以处理矢量资产,颜色,纹理等,它们是多用途的东西!
其次,松散资产适用于我们应用中的所有其他类型的媒体——文本文件,JSON,XML,电影等。如果您有很多这样的文件,则可以在Xcode内进行分组以组织它们,但是在构建时,一切都消失了:所有这些文件都放入一个称为资源目录的目录中。发生这种情况的原因是,当我们要求捆绑包查找“ start.txt”的网址时,它不需要搜索应用捆绑包中的所有目录,而是可以在一个位置查找,因为所有文件都在那里。
这会产生一个有趣的问题,这是您早晚会遇到的问题:由于Xcode项目中所有地方的所有松散文件最终都放置在一个资源目录中,因此您无法在任何地方两次使用相同的资产文件名在您的项目中。不管文件位于哪个组中,或者它们在Xcode项目中看起来有多远都没有关系:如果您的项目中有两个名为start.txt的文件,则构建将失败,因为Xcode无法将它们都放入同一目录。
Challenge - 挑战
在我们进行更复杂的项目之前,重要的是您有大量的时间来停下来并使用已有的资源。因此,今天您有一个完全由您自己制作的新项目,除了下面的一些提示外,我没有其他帮助。你准备好了吗?
您的目标是为孩子们构建一个“娱乐”应用程序,以帮助他们练习乘法表——“7 x 8是多少?”等等。娱乐应用程序的代码具有教育意义,但理想情况下,它们应具有足够的趣味性,以使孩子们喜欢玩。
项目分解:
- 玩家需要选择他们想要练习的乘法表。这可以是按按钮,也可以是“最多...”步进,从1到12。
- 玩家应该能够选择要问多少个问题:5、10、20或“全部”。
- 您应该在他们要求的难度范围内随机产生他们所要求的问题。对于“全部”情况,应生成所有可能的组合。
如果您想完全沿着“教育”路线走下去,那将是一些步进器,一个文本字段和几个按钮。我建议这是一个不错的起点,只是要确保您掌握了基础知识。
一旦有了这些,就取决于您要将应用程序带入“娱乐”路线的程度——您可以完全放弃诸如Stepper
之类的固定控件,而是依靠彩色按钮来获得相同的结果。您可以使用Kenney’s Animal Pack(顺便说一下,这是公共领域!)之类的东西来添加有趣的主题,以使其成为真实的游戏。希望您还会在顶部添加一些动画——它需要吸引9岁及以下的孩子,所以变得明亮,多彩,甚至有点滑稽是一个好主意!
为了解决这一挑战,您需要利用到目前为止在所有项目中学到的技能,但是,如果您从小处着手,并朝着前进的方向努力,那么您将获得最大的成功机会。它的核心不是复杂的应用程序,因此请掌握正确的基础知识并仅在有时间的时候进行扩展。
至少,您应该:
- 首先从Single View App模板开始,然后添加一些状态以确定游戏是否处于活动状态或您是否要求设置。
- 由于该应用程序具有两种截然不同的状态——“我们正在要求用户进行设置”和“游戏正在运行”——您应使用一个
Group
作为您的顶级视图,以便some View
始终获得相同的视图类型返回。
- 尝试将布局拆分为新的SwiftUI视图,而不是将所有内容放入
ContentView
中。 - 向玩家显示他们在游戏结束时正确回答了多少个问题,然后提出让他们再次玩。
我将在下面提供一些提示,但我建议您在阅读之前尝试尽可能多的挑战。
- 您应该在游戏开始时立即生成所有问题,并将其存储为一系列问题。
- 这些问题可能应该是它们自己的Swift结构体:
Question
,用于存储文本和答案。 - 提出问题时,请使用另一个名为
currentQuestion
的状态属性,该属性是指向问题数组中某个位置的整数。 - 您可以使用屏幕上的按钮(例如计算器)或数字键盘文本字段来获得用户输入——随便哪个。
简单地说,这并不是一个很难构建的应用程序。正确掌握这一核心——获得您要做的事情的基本逻辑——然后考虑如何将其变为现实。是的,我知道那部分是有趣的部分,但最终这个应用程序需要有用,与立即尝试所有功能而不是半途而废相比,让核心发挥作用要好得多。
译自 Hacking with iOS: SwiftUI Edition - 里程碑:项目 4 - 6
What you learned
Key points
Challenge
Previous: Animation 项目——挑战 | Hacking with iOS: SwiftUI Edition | Next: SwiftUI:为什么@State只适用于结构体 |
---|
赏我一个赞吧~~~