Hacking with iOS: SwiftUI Edition - 猜国旗项目

猜国旗项目简介

在第二个SwiftUI项目中,我们将构建一个猜谜游戏,帮助用户学习世界上许多旗帜中的一些。

这个项目仍然会很好和容易,但给了我一个机会向您介绍整个新的SwiftUI功能范围:stacksbuttonsimagesalertsasset 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: 猜国旗项目 挑战

赏我一个赞吧~~~

你可能感兴趣的:(Hacking with iOS: SwiftUI Edition - 猜国旗项目)