首页 iOS.& Swift Books iOS.Apprentice

4
网点 由Eli Ganim撰写

你’ve built the user interface for 靶心 而且你知道如何找到滑块的当前位置。已经敲了一些待办事项列表的一些项目。本章从待办事项列表中处理其他一些项目,并涵盖以下内容:

  • 改善滑块:将初始滑块值(在代码中)设置为故事板中设置的任何值,而不是假设初始值。
  • 生成随机数:通过游戏生成随机数用作目标。
  • 为游戏添加轮:添加启动新一轮游戏的能力。
  • 显示目标值:在屏幕上显示生成的目标号码。
  • 计算得分的分数:根据他们将滑块定位到目标值的密切相关,确定给玩家奖励多少点。

改善滑块

你 completed storing the value of the slider into a variable and showing it via an alert. That’s great, but you can still improve on it a little.

What if you decide to set the initial value of the slider in the storyboard to something other than 50 — say, 1 or 100? Then currentValue would be wrong again because the app always assumes it will be 50 at the start. You’d have to remember to also fix the code to give currentValue a new initial value.

把它带到我 - 那种东西很难记住,特别是当项目变得更大时,你有几十个视图控制器担心,或者当你没有看几周的代码时。

获取初始滑块值

To fix this issue once and for all, you’re going to do some work inside the viewDidLoad() method in ViewController.swift.。该方法目前如下所示:

override func viewDidLoad() {
  super.viewDidLoad()
  // Do any additional setup after loading the view.
}

The viewDidLoad() message is sent by UIKit immediately after the view controller loads its user interface from the storyboard file. At this point, the view controller isn’t visible yet, so this is a good place to set instance variables to their proper initial values.

➤ Change viewDidLoad() to the following:

override func viewDidLoad() {
  super.viewDidLoad()
  currentValue = lroundf(slider.value)
}

The idea is that you take whatever value is set on the slider in the storyboard (whether it is 50, 1, 100 or anything else) and use that as the initial value of currentValue.

Recall that you need to round off the number, because currentValue is an Int and integers cannot take decimal (or fractional) numbers.

不幸的是,Xcode甚至在尝试运行应用程序之前立即抱怨这些更改。

关于缺少标识符的Xcode错误消息
关于缺少标识符的Xcode错误消息

笔记:Xcode试图有用,并在您输入时分析了错误的错误。有时,当您完成正在制作的更改时,您可能会看到临时警告和错误消息将消失。

这些消息不要太吓倒;它们只是短暂的,而代码处于助焊状态。

The above happens because viewDidLoad() does not know of anything named 滑块.

Then why did this work earlier, in 滑块Moved()? Let’s take a look at that method again:

@IBAction func sliderMoved(_ slider: UISlider) {
  currentValue = lroundf(slider.value)
}

Here, you do the exact same thing: You round off 滑块.value and put it into currentValue. So why does it work here but not in viewDidLoad()?

The difference is that, in the code above, 滑块 is a 范围 of the 滑块Moved() method. Parameters are the things inside the parentheses following a method’s name. In this case, there’s a single parameter named 滑块, which refers to the UISlider object that sent this action message.

Action methods can have a parameter that refers to the UI control that triggered the method. This is convenient when you wish to refer to that object in the method, just as you did here (the object in question being the UISlider).

When the user moves the slider, the UISlider object basically says, “Hey, View controller! I’m a slider object and I just got moved. By the way, here’s my phone number so you can get in touch with me.”

The 滑块 范围 contains this “phone number,” but it is only valid for the duration of this particular method.

In other words, 滑块 is 当地的;你不能在别的地方使用它。

当地人

当您第一次学习变量时,提到每个变量都有一定的寿命,称为它 范围。变量的范围取决于您定义该变量的程序中的位置。

SWIFT有三个可能的范围水平:

  1. 全球范围:这些对象存在于应用程序的持续时间内,可从任何位置访问。

  2. 实例范围: This is for variables such as currentValue. These objects are alive for as long as the object that owns them stays alive.

  3. 本地范围: Objects with a local scope, such as the 滑块 范围 of 滑块Moved(), only exist for the duration of that method. As soon as the execution of the program leaves this method, the local objects are no longer accessible.

Let’s look at the top part of showAlert():

@IBAction func showAlert() {
  let message = "The value of the slider is: \(currentValue)"

  let alert = UIAlertController(title: "Hello, World",
                              message: message,
                       preferredStyle: .alert)

  let action = UIAlertAction(title: "OK", style: .default,
                           handler: nil)
  . . .

Because the message, alert, and action objects are created inside the method, they have local scope. They only come into existence when the showAlert() action is performed and they cease to exist when the action is done.

As soon as the showAlert() method completes, i.e., when there are no more statements for it to execute, the computer destroys the message, alert, and action objects and their storage space is cleared out.

The currentValue variable, however, lives on forever… or at least for as long as the ViewController does, which is until the user terminates the app. This type of variable is named an 实例变量,因为它的范围与它所属的对象实例的范围相同。

换句话说,如果要在一个操作事件到下一个操作事件,则使用实例变量使用实例变量。

设置出口

所以,通过这种新获得的变量和他们的范围知识,你如何解决遇到的错误?

The solution is to store a reference to the slider as a new instance variable, just like you did for currentValue. Except that this time, the data type of the variable is not Int, but UISlider. And you’re not using a regular instance variable but a special one called an 出口.

➤添加以下行 ViewController.swift.:

@IBOutlet weak var slider: UISlider!

It doesn’t really matter where this line goes, just as long as it is somewhere inside the brackets for class ViewController. It’s common to put outlets with the other instance variables — at the top of the class implementation.

This line tells Interface Builder that you now have a variable named 滑块 that can be connected to a UISlider object. Just as Interface Builder likes to call methods 行动,它调用这些变量 网点. Interface Builder doesn’t see any of your other variables, only the ones marked with @IBOutlet.

Don’t worry about weak or the exclamation point for now. Why these are necessary will be explained later on. For now, just remember that a variable for an outlet needs to be declared as @IBOutlet weak var and has an exclamation point at the end. (Sometimes you’ll see a question mark instead; all this hocus pocus will be explained in due time.)

Once you add the 滑块 variable, you’ll notice that the Xcode error goes away. Does that mean that you can run your app now? Try it and see what happens.

应用程序崩溃,以类似于以下内容的错误崩溃:

插座未连接时应用程序崩溃
插座未连接时应用程序崩溃

所以发生了什么事?

请记住,出口必须是 连接的 to something in the storyboard. You defined the variable, but you didn’t actually set up the connection yet. So, when the app ran and viewDidLoad() was called, it tried to find the matching connection in the storyboard and could not — and crashed.

让我们现在在故事板上设置连接。

➤打开故事板。抓住 控制 然后点击 滑块。但是,不要拖任何地方 - 应该弹出一个菜单,它显示此滑块的所有连接。 (而不是控制单击,您也可以右键单击一次。)

此弹出菜单与Connections Inspector完全相同。它只是一种替代方法。

➤单击旁边的打开圆圈 新的参考插座 并拖累 查看控制器:

将滑块连接到出口
将滑块连接到出口

➤在出现的弹出窗口中,选择 滑块.

This is the outlet that you just added. You have successfully connected the slider object from the storyboard to the view controller’s 滑块 outlet.

Now that you have done all this set up work, you can refer to the slider object from anywhere inside the view controller using the 滑块 variable.

With these changes in place, it no longer matters what you choose for the initial value of the slider in Interface Builder. When the app starts, currentValue will always correspond to that setting.

➤运行应用程序并立即按下击中我!按钮。它正确地说:“滑块的价值是:50。”停止应用程序,进入接口构建器并将滑块的初始值更改为其他内容 - 例如,25.再次运行应用程序,然后按按钮。警报现在应该读取25。

笔记:更改滑块值 - 或者在任何接口构建器字段中的值 - 更改时记得在字段中退出。如果您进行了更改,但您的光标仍仍然存在,则更改可能不会生效。这是可以经常绊倒的东西。

当您完成播放时,将滑块的起始位置恢复到50。

锻炼: Give currentValue an initial value of 0 again. Its initial value is no longer important — it will be overwritten in viewDidLoad() anyway — but Swift demands that all variables always have some value and 0 is as good as any.

评论

你’ve probably noticed the green text that begins with // a few times now. As I explained earlier briefly, these are comments. You can write any text you want after the // symbol as the compiler will ignore such lines from the // to the end of the line completely.

// I am a comment! You can type anything here.

Anything between the /* and */ markers is considered a comment as well. The difference between // and /* */ is that the former only works on a single line, while the latter can span multiple lines.

/*
   I am a comment as well!
   I can span multiple lines.
 */

The /* */ comments are often used to temporarily disable whole sections of source code, usually when you’re trying to hunt down a pesky bug, a practice known as “commenting out”. You can use the cmd- / 键盘快捷键评论/取消注释当前所选的行,或者如果您没有选择,则当前行。

评论行的最佳用途是解释您的代码如何工作。写得良好的源代码是不言自明的,但有时额外的澄清是有用的。向谁解释?对自己,主要是。

除非你有一个大象的记忆,否则你可能会忘记你的代码在六个月后看待你的代码如何工作。使用注释来慢慢慢跑。

生成随机数

你 still have quite a way to go before the game is playable. So, let’s get on with the next item on the list: generating a random number and displaying it on the screen.

当您制作游戏时,随机数字提出了很多因素,因为游戏经常需要有一些不可预测性的元素。您无法真正获得计算机生成真正随机和不可预测的数字,但您可以使用 伪随机发电机 吐出至少似乎随机的数字。

在生成随机值之前,您需要一个存储它的地方。

➤在顶部添加新变量 ViewController.swift.,其他变量:

var targetValue = 0

你 might wonder why we didn’t specify the type of the targetValue variable, similar to what we’d done earlier for currentValue. This is because Swift is able to 推断 the type of variables if it has enough information to work with. Here, for example, you initialize targetValue with 0 and, since 0 is an integer value, the compiler knows that targetValue will be of type Int.

It should be clear why you made targetValue an instance variable: You want to calculate the random number in one place – like in viewDidLoad() — and then remember it until the user taps the button in showAlert() when you have to check this value against the user selection.

接下来,您需要生成随机数。这样做的好地方就是游戏开始时。

➤添加以下行 viewDidLoad() in ViewController.swift.:

targetValue = Int.random(in: 1...100)

The complete viewDidLoad() should now look like this:

override func viewDidLoad() {
  super.viewDidLoad()
  currentValue = lroundf(slider.value)
  targetValue = Int.random(in: 1...100)
}

What are you doing here? You call the random() function built into Int to get an arbitrary integer (whole number) between 1 and 100. The 1...100 part is known as a 关闭范围 wherein you specify a starting value and an ending value to specify a range of numbers. The ... part indicates that you want the range to include the closing value (100), but if you wanted a range without the final value, then you would specify the range as 1..<100 and would get only values from 1 to 99.

全清?向前!

显示随机数

➤ Change showAlert() to the following:

@IBAction func showAlert() {
  let message = "The value of the slider is: \(currentValue)" +
                "\nThe target value is: \(targetValue)"

  let alert = . . .
}

提示: Whenever you see . . . in a source code listing, this is a shorthand for: This part didn’t change. Don’t go replacing the existing code with actual ellipsis!

你’ve simply added the random number, which is now stored in targetValue, to the message string. This should look familiar to you by now: The \(targetValue) placeholder is replaced by the actual random number.

The \n character sequence is new. It means that you want to insert a special “new line” character at that point, which will break up the text into two lines so the message is a little easier to read. The + is also new but is simply used here to combine two strings. We could just as easily have written it as a single long string, but it might not have looked as good to the reader.

➤运行应用程序并尝试一下!

警报显示了新行上的目标值
警报显示了新行上的目标值

笔记: Earlier, you used the + operator to add two numbers together (just like how it works in math) but, here, you’re also using + to glue different bits of text into one big string.

Swift allows the use of the same operator for different tasks, depending on the data types involved. If you have two integers, + adds them up. But with two strings, + concatenates, or combines, them into a longer string.

编程语言通常根据上下文使用不同目的的相同符号。毕竟,只有这么多的符号来绕过!

在游戏中添加圆形

If you press the Hit Me! button a few times, you’ll notice that the random number never changes. I’m afraid the game won’t be much fun that way. This happens because you generate the random number in viewDidLoad() and never again afterwards.

The viewDidLoad() method is only called once when the view controller is created during app startup. The item on the to-do list actually said: “Generate a random number 在每一轮的开始时“。让我们谈谈这场比赛的圆形手段。

当游戏开始时,播放器的分数为0,圆形数量为1.您将滑块中途设置为1,并计算随机数。然后你等待玩家按下击中我!按钮。一旦这样做,圆角。

您可以计算此回合的点并将其添加到总分。然后你递增圆数字并开始下一轮。您再次将滑块重置为中途位置并计算新的随机数。冲洗,重复。

开始一个新的回合

每当你发现自己沿着那条线的想法,“在应用程序中的这一点我们必须这样做,那么为它创建一个新方法是有意义的。这种方法将在自己包含的单位单位中捕获该功能。

➤请记住,添加以下新方法 ViewController.swift.:

func startNewRound() {
  targetValue = Int.random(in: 1...100)
  currentValue = 50
  slider.value = Float(currentValue)
}

It doesn’t really matter where you put the code, as long as it is inside the ViewController implementation (within the class curly brackets), so that the compiler knows it belongs to the ViewController object.

It’s not very different from what you did before, except that you moved the logic for setting up a new round into its own method, startNewRound(). The advantage of doing this is that you can execute this logic from more than one place in your code.

使用新方法

First, you’ll call this new method from viewDidLoad() to set up everything for the very first round. Recall that viewDidLoad() happens just once when the app starts up, so this is a great place to begin the first round.

➤ Change viewDidLoad() to:

override func viewDidLoad() {
  super.viewDidLoad()
  startNewRound()  // Replace previous code with this
}

笔记 that you’ve removed some of the existing statements from viewDidLoad() and replaced them with just the call to startNewRound().

你 will also call startNewRound() after the player pressed the Hit Me! button, from within showAlert().

➤ Make the following change to showAlert():

@IBAction func showAlert() {
  . . .

  startNewRound()
}

The call to startNewRound() goes at the very end, right after present(alert, …).

Until now, the methods from the view controller have been invoked for you by UIKit when something happened: viewDidLoad() is performed when the app loads, showAlert() is performed when the player taps the button, 滑块Moved() when the player drags the slider, and so on. This is the event-driven model we talked about earlier.

也可以直接呼叫方法,这是您在此处的作用。您正在将对象中的一个方法发送消息到同一对象中的另一个方法。

In this case, the view controller sends the startNewRound() message to itself in order to set up the new round. Program execution will then switch to that method and execute its statements one-by-one. When there are no more statements in the method, it returns to the calling method and continues with that — either viewDidLoad(), if this is the first time, or showAlert() for every round after.

以不同方式调用方法

有时,您可能会看到这样的方法调用,如下所示:

self.startNewRound()

That does the exact same thing as startNewRound() without self. in front. Recall you read that the view controller sends the message to itself. Well, that’s exactly what self means.

要在对象上调用方法,您通常会写入:

receiver.methodName(parameters)

The receiver is the object you’re sending the message to. If you’re sending the message to yourself, then the receiver is self. But because sending messages to self is very common, you can also leave this special keyword out for many cases.

This isn’t exactly the first time you’ve called methods. addAction() is a method on UIAlertController and present() is a method that all view controllers have, including yours.

当您编写SWIFT应用程序时,您正在进行大量的是对象的方法,因为您应用程序中对象的通信方式。

使用方法的优点

It’s very helpful to put the “new round” logic into its own method. If you didn’t, the code for viewDidLoad() and showAlert() would look like this:

override func viewDidLoad() {
  super.viewDidLoad()

  targetValue = Int.random(in: 1...100)
  currentValue = 50
  slider.value = Float(currentValue)
}

@IBAction func showAlert() {
  . . .

  targetValue = Int.random(in: 1...100)
  currentValue = 50
  slider.value = Float(currentValue)
}

你能看到这里发生了什么吗?在两个地方复制相同的功能。当然,它只是三行代码,但通常,您重复的代码可能会更大。

如果您决定改变此逻辑(如您的意愿),该怎么办?那么你将不得不在两个地方进行同样的变化。

如果您最近写过这段代码,您可能会记得这样做,并且在内存中仍然是新鲜的,但是,如果您必须在路上几个星期的变化,那么您只需在一个上更新它放置并忘记另一个。

代码复制是错误的重要来源。因此,如果您需要在两个不同的地方做同样的事情,请考虑为其制作新方法而不是重复代码。

命名方法

该方法的名称也有助于使其清楚地表明它应该做什么。你能瞥一眼以下是什么吗?

targetValue = Int.random(in: 1...100)
currentValue = 50
slider.value = Float(currentValue)

你 probably have to reason your way through it: “It is calculating a new random number and then resets the position of the slider, so I guess it must be the start of a new round.”

有些程序员将使用注释来记录发生的事情(并且您也可以这样做),但是,在我看来,以下比上述代码块更清晰,具有解释性评论:

startNewRound()

This line practically spells out for you what it will do. And if you want to know the specifics of what goes on in a new round, you can always look up the startNewRound() method implementation.

写好的源代码为自己说话!

➤运行应用程序并验证它在按钮上的每次抽头后计算1到100之间的新随机数。

你 should also have noticed that, after each round, the slider resets to the halfway position. That happens because startNewRound() sets currentValue to 50 and then tells the slider to go to that position. That is the opposite of what you did before (you used to read the slider’s position and put it into currentValue), but it would work better in the game if you start from the same position in each round.

锻炼:只要有趣,修改代码,使滑块不会在新一轮的开始时重置到中途位置。

类型转换

By the way, you may have been wondering what Float(…) does in this line:

滑块.value = Float(currentValue)

Swift是A. 强烈打字 language, meaning that it is really picky about the shapes that you can put into the boxes. For example, if a variable is an Int, you cannot put a Float, or a non-whole number, into it, and vice versa.

The value of a UISlider happens to be a Float — you’ve seen this when you printed out the value of the slider — but currentValue is an Int. So the following won’t work:

滑块.value = currentValue

The compiler considers this an error. Some programming languages are happy to convert the Int into a Float for you, but Swift wants you to be explicit about such conversions.

When you say Float(currentValue), the compiler takes the integer number that’s stored in currentValue and converts it into a new Float value that it can pass on to the UISlider.

因为Swift是关于这种类型的事情比大多数其他编程语言更严格,所以它通常是对语言的新人混淆的源泉。不幸的是,Swift的错误消息并不总是很清楚代码的一部分是错误的,还是为什么。

只要记住,如果您收到错误消息,“无法为类型”的值“键入”其他东西“,”那么您可能正在尝试混合不兼容的数据类型。解决方案是明确地将一种类型转换为另一个 - 如果允许转换,当然是 - 正如您在此处完成的那样。

显示目标值

Great, you figured out how to calculate the random number and how to store it in an instance variable, targetValue, so that you can access it later.

现在,您将在屏幕上显示该目标号码。没有它,玩家不会知道瞄准什么,这将使游戏无法获胜。

设置故事板

When you set up the storyboard, you added a label for the target value (top-right corner). The trick is to put the value from the targetValue variable into this label. To do that, you need to accomplish two things:

  1. 为标签创建一个插座,以便您可以发送消息。
  2. 为标签新文本显示。

This will be very similar to what you did with the slider. Recall that you added an @IBOutlet variable so you could reference the slider anywhere from within the view controller. Using this outlet variable you could ask the slider for its value, through 滑块.value. You’ll do the same thing for the label.

➤in. ViewController.swift.,在其他出口声明下方添加以下行:

@IBOutlet weak var targetLabel: UILabel!

➤in. main.storyboard.,单击以选择正确的标签 - 在最顶部的标签上表示“100”。

➤去 连接检查员 并拖动 新的参考插座 在中央场景中查看控制器顶部的黄色圆圈。

你 could also drag to the 查看控制器 在文档大纲中 - 在接口构建器中有很多方法可以做同样的事情。

将目标值标签连接到其插座
将目标值标签连接到其插座

➤选择 targetlabel. 从弹出窗口,并进行连接。

通过代码显示目标值

➤ Add the following method below startNewRound() in ViewController.swift.:

func updateLabels() {
  targetLabel.text = String(targetValue)
}

你’re putting this logic in a separate method because it’s something you might use from different places.

该方法的名称使其清除它所做的:它更新标签的内容。目前它只是设置单个标签的文本,但后来您将添加代码来更新其他标签(总分,圆数字)。

The code inside UPD.ateLabels() should have no surprises for you, although you may wonder why you cannot simply do:

targetlabel..text = targetValue

再次答案是,您无法将一个数据类型的值放入另一个类型的变量 - 方形挂钩不会进入圆孔。

The targetlabel. 出口 references a UILabel object. The UILabel object has a text property, which is a String object. So, you can only put String values into text, but targetValue is an Int. A direct assignment won’t fly because an Int and a String are two very different types.

So, you have to convert the Int into a String, and that is what String(targetValue) does. It’s similar to what you’ve done before with Float(…).

Just in case you were wondering, you could also convert targetValue to a String by using string interpolation, like you’ve done before:

targetlabel..text = "\(targetValue)"

你使用的方法是一种味道。任何一种方法都会正常工作。

Notice that UPD.ateLabels() is a regular method — it is not attached to any UI controls as an action — so it won’t do anything until you actually call it. You can tell because it doesn’t say @IBAction before func.

动作方法与正常方法

那么动作方法和常规方法之间有什么区别?

答:没有。

An action method is really just the same as any other method. The only special thing is the @IBAction attribute, which allows Interface Builder to see the method so you can connect it to your buttons, sliders and so on.

Other methods, such as viewDidLoad(), don’t have the @IBAction specifier. This is good because all kinds of mayhem would occur if you hooked these up to your buttons.

这是动作方法的简单形式:

@IBAction func showAlert()

你 can also ask for a reference to the object that triggered this action, via a parameter:

@IBAction func sliderMoved(_ slider: UISlider)
@IBAction func buttonTapped(_ button: UIButton)

但以下方法不能用作来自Interface Builder的操作:

func updateLabels()

That’s because it is not marked as @IBAction and as a result, Interface Builder can’t see it. To use UPD.ateLabels(), you will have to call it yourself.

调用方法

The logical place to call UPD.ateLabels() would be after each call to startNewRound(), because that is where you calculate the new target value. So, you could always add a call to UPD.ateLabels() in viewDidLoad() and showAlert(), but there’s another way, too!

What is this other way, you ask? Well, if UPD.ateLabels() is always (or at least in your current code) called after startNewRound(), why not call UPD.ateLabels() directly from startNewRound() itself? That way, instead of having two calls in two separate places, you can have a single call.

➤ Change startNewRound() to:

func startNewRound() {
    targetValue = Int.random(in: 1...100)
    currentValue = 50
    slider.value = Float(currentValue)
    updateLabels()  // Add this line
}

你 should be able to type just the first few letters of the method name, like UPD.,Xcode将向您展示与您键入的内容匹配的建议列表。

进入 (或者 标签)接受建议(如果您在右侧项目 - 或滚动列表以查找合适的项目,然后按Enter)

Xcode自动完成提供建议
Xcode自动完成提供建议

值得注意的是,您不必开始键入您从开始的方法(或属性)名称 - Xcode使用模糊搜索并键入“Datel”或“Label”应该帮助您找到“UpdatElabels”就像容易地。

➤运行应用程序,您实际上会看到屏幕上的随机值。这应该让它更容易瞄准。

右上角的标签现在显示了随机值
右上角的标签现在显示了随机值

计算得分的分数

现在您拥有目标值(随机数)和读取滑块的位置的方式,您可以计算播放器评分的点数。滑块越靠近目标,播放器的点越多。

➤ Make this change to showAlert():

@IBAction func showAlert() {
  let difference = abs(targetValue - currentValue)
  let points = 100 - difference

  let message = "你 scored \(points) points"
  . . .
}

显示总分

在此游戏中,您希望在屏幕上显示玩家的总分。每一轮之后,该应用程序应将新分数的点添加到总数,然后更新分数标签。

存储总分

因为游戏需要保持长时间的总分,所以您需要一个实例变量。

➤ Add a new score 实例变量 to ViewController.swift.:

class ViewController: UIViewController {

  var currentValue: Int = 0
  var targetValue = 0
  var score = 0              // add this line

Again, you make use of type inference to not specify a type for score.

发现变量的推断类型
发现变量的推断类型

更新总分

Now, showAlert() can be amended to update this score variable.

➤进行以下更改:

@IBAction func showAlert() {
  let difference = abs(targetValue - currentValue)
  let points = 100 - difference

  score += points        // add this line

  let message = "你 scored \(points) points"
  . . .
}

这里没什么令人震惊的。您刚刚添加了以下行:

score += points

这增加了用户在本轮中得分的要点至总分数。你也可以像这样写作:

score = score + points

显示得分

To display your current score, you’re going to do the same thing that you did for the target label: hook up the score label to an outlet and put the score value into the label’s text property.

锻炼:看看你是否可以自己做上面的事情。您之前已经在目标值标签之前完成了这些事情,因此您应该能够重复分数标签的这些步骤。

完毕?您应该添加此行 ViewController.swift.:

@IBOutlet weak var scoreLabel: UILabel!

Then, you connect the relevant label on the storyboard (the one that says 999999) to the new scoreLabel outlet.

不确定如何连接出口?有几种方法可以从用户界面对象与视图控制器的插座进行连接:

  • 控制 - 单击对象以获取上下文敏感的弹出菜单。然后,从新的引用插座拖动到查看控制器(您使用滑块执行此操作)。
  • 转到标签的连接检查器。从新的引用插座拖动到查看控制器(您使用目标标签执行此操作)。
  • 控制阻力 将控制器视图到标签(立即尝试尝试) - 以另一个方式执行此操作,从标签控制拖动以查看控制器,无法正常工作。

Great, that gives you a scoreLabel 出口 that you can use to display the score. Now, where in the code can you do that? In UPD.ateLabels(), of course.

➤返回 ViewController.swift., change UPD.ateLabels() to the following:

func updateLabels() {
  targetLabel.text = String(targetValue)
  scoreLabel.text = String(score)     // add this line
}

Nothing new, here. You convert the score — which is an Int — into a String and then pass that string to the label’s text property. In response to that, the label will redraw itself with the new score.

➤运行应用程序并验证此轮的点在点击按钮时添加到总分标签。

分数标签跟踪玩家的总分
分数标签跟踪玩家的总分

再一圈......

谈到回合,每次玩家开始新的回合时也必须增加圆形数字。

锻炼:跟踪当前的轮数(从1开始),并在新的回合开始时递增。在相应的标签中显示当前的圆形数字。我可能会把你扔进这里的深处,但如果你到目前为止你能够遵循指示,那么你已经看到了你需要拉出这个的所有碎片。祝你好运!

如果您猜到您必须添加另一个实例变量,那么您就是对的。您应该添加以下行(或类似的内容) ViewController.swift.:

var round = 0

如果您包含数据类型的名称,也可以是OK,即使不严格必要:

var round: Int = 0

此外,添加标签的插座:

@IBOutlet weak var roundLabel: UILabel!

如前所述,您应该将标签连接到Interface Builder中的此插座。

笔记:别忘了制作这些连接。

忘记在接口建设者中进行连接是一个经常发生的错误,特别是由您真正的错误。

我一直在我身上碰巧我让一个按钮的插座并编写代码来处理该按钮上的水龙头,但是,当我运行应用程序时,它不起作用。通常,它需要我几分钟,有些头部划伤以意识到我忘了将按钮连接到插座或动作方法。

你 can tap on the button all you want but, unless that connection exists, your code will not respond.

Finally, UPD.ateLabels() should be modified like this:

func updateLabels() {
  targetLabel.text = String(targetValue)
  scoreLabel.text = String(score)
  roundLabel.text = String(round)    // add this line
}

Did you also figure out where to increment the round variable?

I’d say the startNewRound() method is a pretty good place. After all, you call this method whenever you start a new round. It makes sense to increment the round counter there.

➤ Change startNewRound() to:

func startNewRound() {
  round += 1           // add this line
  targetValue = ...
}

笔记 that, when you declared the round instance variable, you gave it a default value of 0. Therefore, when the app starts up, round is initially 0. When you call startNewRound() for the very first time, it adds 1 to this initial value and, as a result, the first round is properly counted as round 1.

运行应用程序并尝试一下。圆形柜台应在按下打击时更新!按钮。

圆形标签计数已播放多少轮
圆形标签计数已播放多少轮

你’re making great progress; well done!

你 can find the project files for the app up to this point under 04 - 插座 在源代码文件夹中。

有一个技术问题?想报告一个错误吗? 您可以向官方书籍论坛中的书籍作者提出问题和报告错误 这里.

有反馈分享在线阅读体验吗? 如果您有关于UI,UX,突出显示或我们在线阅读器的其他功能的反馈,您可以将其发送到设计团队,其中表格如下所示:

© 2021 Razeware LLC