更多内容欢迎关注公众号:Swift花园
堆叠按钮
我们即将为我们的app构建基本的 UI 结构,它们包括两个指示用户操作的标签以及三个显示国旗的图片按钮。
首先,打开 Xcode 的 Assets.xcassets,然后把国旗图片拖进去,确保你找到的国旗图片包含 @2x 或者 @3x 的版本。它们是用于处理不同类型的 iPhone 屏幕的两倍分辨率和三倍分辨率的图像。
接下来,我们添加两个属性来存储游戏的数据:一个是存放所有国家图片名字的数组,另一个是代表正确国家的数组索引。
var countries = ["Estonia", "France", "Germany", "Ireland", "Italy", "Nigeria", "Poland", "Russia", "Spain", "UK", "US"]
var correctAnswer = Int.random(in: 0...2)Int.random(in:) 方法随机选择一个整数,在这里刚好适用 – 我们用这个数来决定应当点击哪个国家的国旗。
在 body 方法里,我们需要用一个 VStack 来布局游戏提示:
var body: some View {
VStack {
Text("Tap the flag of")
Text(countries[correctAnswer])
}
}在提示下方,我们会放置可点击的按钮。你可以直接放进同一个VStack,当然也可以创建一个新的 VStack,以便单独控制间距。
前一个存放两个文本视图的VStack 没有间距,但它同国旗的 VStack 之间设置一个30的距离会让视觉效果更好。
因此,在前面的 VStack 默认添加一个ForEach:
ForEach(0 ..< 3) { number in
Button(action: {
// flag was tapped
}) {
Image(self.countries[number])
.renderingMode(.original)
}
}renderingMode(.original) 修改器告诉 SwiftUI 渲染原始图像的像素而不是作为一个按钮重新着色。
现在我们会遇到一个问题:我们的 body 属性正试图传回两个 view,一个 VStack 和 一个ForEach,这是不允许的。 所以我们需要第二个VStack:我们会用一个新的VStack 把ForEach 和原来的 VStack包裹起来,并且设置30的间距。
把代码修改成这样:
var body: some View {
VStack(spacing: 30) {
VStack {
Text("Tap the flag of")
// etc
}
ForEach(0 ..< 3) { number in
// etc
}
}
}两个 VStack 允许我们更精确地掌控位置:外层的 stack 会用 30 的间距来分隔它的子视图,而内层的 stack 则没有间距。
UI 目前看起来还行,但你会发现一些问题。由于某些国旗的图案里有白色,它们会混入白色的背景,并且所有的国旗都被居中到屏幕中间。
为了解决国旗颜色的问题,我们引入一个 ZStack:
var body: some View {
ZStack {
VStack(spacing: 30) {
VStack {
Text("Tap the flag of")
// etc
}
ForEach(0 ..< 3) { number in
// etc
}
}
}
}我们还需要在 VStack 的下面再设置一个背景:
Color.blue.edgesIgnoringSafeArea(.all)edgesIgnoringSafeArea() 修改器确保颜色能够覆盖整个屏幕。
现在我们有了一个暗色的背景,我们需要调整文本的颜色以便它们能从背景中凸显出来:
Text("Tap the flag of")
.foregroundColor(.white)
Text(countries[correctAnswer])
.foregroundColor(.white)最后,我们还要美化一下,以便整个 VStack 居于屏幕上部,只需要在 ForEach 之后加一个 Spacer 视图:
Spacer()用 Alert 显示玩家分数
为了让游戏有趣,我们需要随机安排国旗展示的顺序,在玩家点击国旗时触发 alert 告诉玩家选择是正确还是错误,然后重新洗国旗的顺序。
我们已经设置 correctAnswer 为一个随机数,但是所有的国旗每次都是一样的顺序。为了解决这个问题,我们需要在游戏开始时重新洗 countries 数组,把属性改成这样:
var countries = ["Estonia", "France", "Germany", "Ireland", "Italy", "Nigeria", "Poland", "Russia", "Spain", "UK", "US"].shuffled()如你可见,shuffled() 方法自动帮我们解决了随机性的问题。
现在来到更有趣的部分:当国旗出现时,我们应该怎么做? 用实际的代码来替换 // flag was tapped 这段注释。
最好的方式是运行一个接收整数的方法,检查这个整数是否匹配correctAnswer 属性。
不管回答是否正确,我们想要展示给玩家一个 alert,告诉它们发生了什么,以便玩家可以跟踪游戏进程。因此,我们可以加一个属性来存储 alert 是否显示:
@State private var showingScore = false并且再添加一个用作 alert 标题的属性:
@State private var scoreTitle = ""然后是我们的点击响应方法:
func flagTapped(_ number: Int) {
if number == correctAnswer {
scoreTitle = "Correct"
} else {
scoreTitle = "Wrong"
}
showingScore = true
}// flag was tapped 注释换成下面这句:
self.flagTapped(number)我们已经有 number 参数,它是来自ForEach,所以只需要传进flagTapped()就行。
因为我们展示了 alert,我们需要考虑 alert 消失之后做些什么。
显然游戏不应该结束,我们应当写一个askQuestion()方法继续发问,通过重新洗牌国旗然后选择一个新的答案:
func askQuestion() {
countries.shuffle()
correctAnswer = Int.random(in: 0...2)
}上面的代码无法编译通过,希望你已经发现问题所在:我们正试图修改视图里没有标记@State的属性,这是不允许的。因此,回到 countries和 correctAnswer 的声明, 加上 @State private,就像这样:
@State private var countries = ["Estonia", "France", "Germany", "Ireland", "Italy", "Nigeria", "Poland", "Russia", "Spain", "UK", "US"].shuffled()
@State private var correctAnswer = Int.random(in: 0...2)现在我们已经可以展示 alert 了,需要做三件事:
- 用
alert()修改器,在showingScore为 true 时展示 alert。 - 用
scoreTitle设置 alert 的标题。 - Dismiss 按钮点击时调用
askQuestion()。
把最后这段代码放在 ZStack上:
.alert(isPresented: $showingScore) {
Alert(title: Text(scoreTitle), message: Text("Your score is ???"), dismissButton: .default(Text("Continue")) {
self.askQuestion()
})
}是的,分数的计算还没有搞定,我们将在下一部分解决。
我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~
