猜国旗项目简介
在第二个SwiftUI项目中,我们将构建一个猜谜游戏,帮助用户学习世界上许多旗帜中的一些。
这个项目仍然会很好和容易,但给了我一个机会向您介绍整个新的SwiftUI功能范围:stacks
、buttons
、images
、alerts
、asset catalogs
等。
我们的第一个应用程序使用了完全标准的iOS外观和感觉,但在这里,我们将做一些更定制的东西,以便您可以看到在SwiftUI中它是多么容易。
您需要下载此项目的一些文件,您可以从GitHub下载这些文件https://github.com/twostraws/HackingWithSwift,查看文件的SwiftUI部分。
PS:不想下载整个项目的小伙伴看这里
1. 打开终端
2. cd 到你想保存的文件夹 比如 cd Desktop/
3. 输入
svn checkout https://github.com/twostraws/HackingWithSwift/trunk/SwiftUI/project2
4. 回车等待
一旦你有了这些,继续在Xcode中创建一个名为GuessTheFlag的新单视图应用程序模板。和之前一样,我们将从构建应用程序所需的各种SwiftUI技术的概述开始,所以让我们进入它…
使用堆栈排列按钮
我们将通过构建基本的UI结构来启动我们的应用程序,这将会是两个标签告诉用户该做什么,然后是三个显示三个世界国家的国旗按钮。
首先,找到这个项目的资源并将它们拖到您的资源目录中。这意味着在Xcode中打开Assets.xcapets,然后从project2文件文件夹中拖入标记图像。你会注意到这些图片是以他们的国家命名的,还有@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:)
方法会自动选择一个随机数,这在这里是完美的——我们将使用它来决定应该点击哪个国家的国旗。
在我们的身体里,我们需要把我们的游戏提示放在一个垂直的堆栈中,所以让我们从这个开始:
var body: some View {
VStack {
Text("Tap the flag of")
Text(countries[correctAnswer])
}
}
下面我们想有我们的可点击标志按钮,虽然我们可以把它们添加到同一个VStack
,我们实际上可以创建第二个VStack
,以便我们有更多的控制间距。
我们刚刚在上面创建的VStack
包含两个文本视图,并且没有间隔,但是如果国旗之间有30个间隔点,将会看起来更好。
所以,首先将这个ForEach
循环直接添加到我们刚刚创建的VStack
的末尾下面:
ForEach(0 ..< 3) { number in
Button(action: {
// flag was tapped
}) {
Image(self.countries[number])
.renderingMode(.original)
}
}
renderingMode(.original)
修饰语告诉SwiftUI渲染原始图像像素,而不是尝试将其重新着色为按钮。
现在我们遇到了一个问题:我们的body
属性试图发送回两个视图,一个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
}
}
}
有两个这样的垂直堆栈可以让我们更精确地定位:外部堆栈将其视图间隔30个点,而内部堆栈没有间隔。
这足以让您对我们的用户界面有一个基本的了解,而且您已经看到它看起来不太好了——一些标志中有白色,它们与背景融为一体,所有标志都垂直居中在屏幕上。
稍后我们会回来对UI进行润色,但现在让我们使用一种蓝色的背景色,以便更容易看到标志。因为这意味着在我们的外部VStack
后面放置一些东西,所以我们也需要使用ZStack
。是的,我们会在一个ZStack
中的另一个VStack
中有一个VStack
,这是非常正常的。
首先在外部VStack
周围放置一个ZStack
,如下所示:
var body: some View {
ZStack {
// previous VStack code
}
}
现在把这个放在ZStack
里面,然后紧接着放VStack
:
Color.blue.edgesIgnoringSafeArea(.all)
edgesIgnoringSafeArea()
修饰符确保颜色直接进入屏幕边缘。
既然我们有了较深的背景色,我们应该给文本一些较亮的颜色,以便它更突出:
Text("Tap the flag of")
.foregroundColor(.white)
Text(countries[correctAnswer])
.foregroundColor(.white)
我们将要做的最后一个更改,至少现在是将外部VStack
中的所有内容向上推,这样UI就位于屏幕顶部。这与直接在ForEach
结束后添加间隔视图一样简单:
Spacer()
显示用户的分数
为了让这个游戏更有趣,我们需要随机化旗子的显示顺序,在点击旗子时触发一个警告告诉他们是对是错,然后重新排列旗子。
我们已经将correctAnswer
设置为一个随机整数,但是标志总是以相同的顺序开始。要修复此问题,我们需要在游戏开始时洗牌countries
数组,请将属性修改为:
var countries = ["Estonia", "France", "Germany", "Ireland", "Italy", "Nigeria", "Poland", "Russia", "Spain", "UK", "US"].shuffled()
如您所见,shuffled()
方法自动为我们处理数组顺序的随机化。
更有趣的是:当一面旗帜被点击时,我们该怎么办?我们需要用一些代码来替换//flag was tapped comment
,这些代码决定它们是否点击了正确的标志,最好的方法是使用一个新方法来接受按钮的整数并检查它是否与correctAnswer
属性匹配。
无论他们是否正确,我们都希望向用户显示一个Alert
,告诉他们发生了什么,以便他们可以跟踪他们的进度。因此,添加此属性以存储Alert
是否显示:
@State private var showingScore = false
并添加此属性以存储将在Alert
中显示的标题:
@State private var scoreTitle = ""
因此,无论我们编写什么方法,都将接受点击的按钮的编号,将其与正确答案进行比较,然后设置这两个新属性,这样我们就可以显示有意义的警报。
直接在body
属性之后添加:
func flagTapped(_ number: Int) {
if number == correctAnswer {
scoreTitle = "Correct"
} else {
scoreTitle = "Wrong"
}
showingScore = true
}
我们现在可以将//flag was tapped comment
替换为以下内容:
self.flagTapped(number)
我们已经有了number
,因为它是ForEach
给我们的,所以这只是把它传递给flagtated()
的问题。
在显示Alert
之前,我们需要考虑Alert
消失时会发生什么。显然游戏不应该结束,否则整个游戏马上就结束了。
相反,我们将编写一个askQuestion()
方法,通过调整国家并选择一个新的正确答案来重置游戏:
func askQuestion() {
countries.shuffle()
correctAnswer = Int.random(in: 0...2)
}
这段代码不会编译,希望您能很快明白原因:我们正在尝试更改未标记@State
的视图属性,这是不允许的。所以,去那些声明了国家和正确答案的地方,把@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
了。这需要:
1.使用alert()
修饰符,以便在showingScore
为true时显示警报。
2.展示我们设置的scoreTitle
。
3.有一个在点击调用askQuestion()
时的关闭按钮。
所以,把这个放在body
属性中ZStack
的末尾:
.alert(isPresented: $showingScore) {
Alert(title: Text(scoreTitle), message: Text("Your score is ???"), dismissButton: .default(Text("Continue")) {
self.askQuestion()
})
}
是的,有三个问号的地方应该有一个分数值——你很快就会完成那部分!
设计我们的国旗样式
我们的游戏现在可以了,虽然看起来不太好。幸运的是,我们可以对我们的设计做一些小调整,使整个事情看起来更好。
首先,让我们用从蓝色到黑色的线性渐变替换纯蓝背景色,这样可以确保即使旗子有相似的蓝色条纹,它在背景上也仍然很突出。
所以,找到这一行:
Color.blue.edgesIgnoringSafeArea(.all)
换成这个:
LinearGradient(gradient: Gradient(colors: [.blue, .black]), startPoint: .top, endPoint: .bottom)
.edgesIgnoringSafeArea(.all)
它仍然忽略安全区域,确保背景覆盖所有屏幕。
现在让我们把这个国家的名字——他们需要猜测的部分——变成屏幕上最显眼的一段文字。我们可以使用 font()
修饰符来实现这一点,它允许我们从iOS上的一个内置字体大小中进行选择,但是我们可以将fontwweight()
添加到它中,使文本更加粗。
将这两个修饰符直接放在Text(countries[correctAnswer])
视图后面:
.font(.largeTitle)
.fontWeight(.black)
.largeTitle
是iOS提供的最大的内置字体大小,并根据用户对字体的设置自动放大或缩小,这一功能称为动态类型(Dynamic Type)。
最后,让我们把那些国旗的图像变得更生动一些。SwiftUI为我们提供了许多修改器来影响视图的显示方式,我们将在这里使用三个:一个用于更改国旗的形状,一个用于在国旗周围添加边框,另一个用于添加阴影。
Swift中有五种内置形状:矩形Rectangle
、圆角矩形RoundedRectangle
、圆形Circle
、胶囊Capsule
和椭圆Ellipse
。我们将在这里使用胶囊:它确保最短边的角是完全圆形的,而最长边保持直线-它看起来很适合按钮。使我们的图像胶囊形状与添加.clipShape(capsule())
修改器一样简单,如下所示:
.clipShape(Capsule())
至于在图像周围绘制边框,这是使用overlay()
修饰符完成的。这让我们可以在旗帜上绘制另一个视图,在我们的例子中,它是一个在其边缘有一个黑色笔划的胶囊。因此,在clipShape()
之后添加此修饰符:
.overlay(Capsule().stroke(Color.black, lineWidth: 1))
最后,我们想在每面旗帜周围应用阴影效果,使它们真正从背景中脱颖而出。这是使用shadow()
完成的,它接受阴影的颜色、半径、X和Y偏移,但是如果跳过X和Y,则假定它们为0。所以,在前两个下面添加最后一个修饰符:
.shadow(color: .black, radius: 2)
因此,我们完成的国旗如下:
Image(self.countries[number])
.renderingMode(.original)
.clipShape(Capsule())
.overlay(Capsule().stroke(Color.black, lineWidth: 1))
.shadow(color: .black, radius: 2)
SwiftUI有很多修改器,可以帮助我们调整字体和图像的渲染方式。它们都只做一件事,所以就像你在上面看到的那样,把它们堆起来是很常见的。
译自 Hacking with iOS: SwiftUI Edition - Guess the Flag
Stacking up buttons
Guess the Flag: Introduction
Showing the player’s score with an alert
Styling our flags
Previous: 显示用户的分数 | Hacking with iOS: SwiftUI Edition | Next: 猜国旗项目 挑战 |
---|
赏我一个赞吧~~~