探索如何通过创建一个掷虚拟骰子的应用程序来使用 @State
属性和按钮更新应用程序的用户界面。添加功能以增加或减少屏幕上骰子的数量,以便玩不同类型的游戏。
用状态更新用户界面
步骤 1
在 Xcode 中创建一个名为 DiceRoller 的 iOS 应用项目。
![A screenshot showing the Welcome to Xcode window. Below the icon and welcome message are three buttons: Create New Project, Clone Git Repository, and Open Existing Project, with the first button highlighted.](https://docs-assets.developer.apple.com/published/5f993d89636dbf8a23490c8c3d49f46a/common--xcode-welcome-window@2x.png)
步骤 2
创建一个名为 DiceView 的 SwiftUI 视图文件。
![A screenshot of the file save dialog, with the text, DiceView, in the Save As text field.](https://docs-assets.developer.apple.com/published/91f23717b17df9a0e70c5109bac7eeb2/buttons-and-state--dice-view@2x.png)
步骤 3
将主体代码替换为骰子的图像。
实验
SF Symbols 为骰子的六个面提供了图像。更改图像名称末尾的数字以查看每一个。
步骤 4
向视图添加一个属性,以表示骰子上的点数。
您使用赋值运算符=
为属性赋予默认值为1
。
struct DiceView: View {
var numberOfPips: Int = 1
var body: some View {
Image(systemName: "die.face.1")
步骤 5
该属性被标记为关键字 var
,这意味着您可以为其分配新值。此视图是动态的;当人们掷骰子时,您将更改该属性的值。
当您为结构的属性分配默认值时,在初始化器中并不是必需的。这就是为什么您不必在预览中更改Dice
实例以包含number
。
![A labeled diagram of the code, var numberOfPips colon Int equals 1. The text, var, is labeled Non-constant. The text, numberOfPips, is labeled Name. The text, Int, is labeled Type. The text, 1, is labeled Default value.](https://docs-assets.developer.apple.com/published/469046c942f6ac23336d8806b63734fa/buttons-and-state--property-declaration-diagram@2x.png)
步骤 6
使用字符串插值来显示骰子图像,使用您新属性的值。
实验
将number
的默认值更改为查看相应的骰子图像。
步骤 7
使用修饰符来增加图像的大小。.resizable
修饰符告诉图像它可以拉伸以填充任何可用空间。您不希望骰子填充所有可用空间,因此通过设置其框架大小来限制图像。
您通常使用 .font
修饰符将 SF Symbols 的大小与其周围内容匹配。在这种情况下,您将图像作为纯图形内容使用,因此使用 .resizable
和 .frame
是可以的。
var body: some View {
Image(systemName: "die.face.\(numberOfPips)")
.resizable()
.frame(width: 100, height: 100)
}
}
//
// DiceView.swift
// DiceRoller
//
//
//
import SwiftUI
struct DiceView: View {
var body: some View {
Image(systemName: "die.face.1")
}
}
#Preview {
DiceView()
}
![A screenshot showing the preview with a single small dice image in the center.](https://docs-assets.developer.apple.com/published/e36c0f690cb4852319d1151659935f55/buttons-and-state--die-image@2x.png)
步骤 1
将图像嵌入到一个 VStack
中,以便您可以在其下方添加按钮。
var body: some View {
VStack {
Image(systemName: "die.face.\(numberOfPips)")
.resizable()
步骤 2
在VStack
内部,在Image
及其修饰符下,开始输入Button
。代码补全会为您提供几个建议;选择带有title
和action
的选项。
如果你看到 title
而不是 title
,请选择那个初始化器。
![A screenshot showing a list of code completion suggestions for Button initializers. The second initializer, Button, title, action, is selected.](https://docs-assets.developer.apple.com/published/dfc7b2b7d8c4ae03c87374275c8022fd/buttons-and-state--button-autocompletion@2x.png)
步骤 3
将第一个占位符替换为字符串 "Roll"
。
.resizable()
.frame(width: 100, height: 100)
Button("Roll", action: () -> Void)
}
}
步骤 4
第二个参数,action: () -> Void
,需要一个闭包,当人们点击按钮时执行代码。按 Tab 选择占位符,然后按 Return。
Xcode 完全移除了 action
参数,并用一对大括号中的闭包替代。大括号内有一个 code
占位符,您将在其中编写按钮的代码。
步骤 5
将最后一个占位符替换为代码,以选择骰子的随机点数。
Int
从括号内的范围中选择一个随机整数;代码 1...6
创建一个从 1 到 6 的整数范围。
注意
这是另一个使用赋值运算符的示例。这一次,每次代码运行时,您都在更新number
的值。
您的代码中有一个错误,您将在下一部分通过将 number
更改为 @State
属性来修复它。
![A screenshot showing a list of code completion suggestions for Button initializers. The second initializer, Button, title, action, is selected.](https://docs-assets.developer.apple.com/published/dfc7b2b7d8c4ae03c87374275c8022fd/buttons-and-state--button-autocompletion@2x.png)
步骤 1
将number
设置为@State
属性。然后点击“Roll”按钮几次,检查图像是否变化。
视图状态由视图拥有。您始终将状态属性标记为 private
,以便其他视图无法干扰它们的值。
步骤 2
为按钮添加边框,以使其与图像区分开。
步骤 3
要使旧骰子图像过渡到新图像时淡出,请使用with
来动画化变化。
添加 with
指示 SwiftUI 动画任何在其代码中发生的状态变化。它使用尾随闭包,类似于 Button
的工作方式。
//
// DiceView.swift
// DiceRoller
//
//
//
import SwiftUI
struct DiceView: View {
private var numberOfPips: Int = 1
var body: some View {
VStack {
Image(systemName: "die.face.\(numberOfPips)")
.resizable()
.frame(width: 100, height: 100)
Button("Roll") {
numberOfPips = Int.random(in: 1...6)
}
.buttonStyle(.bordered)
}
}
}
#Preview {
DiceView()
}
![A screenshot showing the preview with a single large dice image in the center, with a bordered button labeled Roll below it.](https://docs-assets.developer.apple.com/published/b1988b45eb023a3ec177c1cc62fd86c0/buttons-and-state--bordered-button@2x.png)
创建一个动态骰子显示
通过创建另一个属性来添加选择骰子数量的功能。用@State
标记该属性可以确保在骰子数量变化时界面更新。
步骤 1
在Content
中,用一个标题替换VStack
的内容。
您可以像在视图中使用修饰符一样,在Font
类型上使用修饰符。
实验
将其他Font
修饰符链接到.large
以查看效果。
如果你愿意,可以将修饰符放在单独的行上:
.font(.largeTitle
.lowercaseSmallCaps()
.bold()
)
var body: some View {
VStack {
Text("Dice Roller")
.font(.largeTitle.lowercaseSmallCaps())
}
.padding()
步骤 2
添加一个HStack
,其中包含三个Dice
实例。尝试掷每个骰子。
Text("Dice Roller")
.font(.largeTitle.lowercaseSmallCaps())
HStack {
DiceView()
DiceView()
DiceView()
}
}
.padding()
步骤 3
要能够显示任意数量的骰子,请使用 For
视图。通过使用从 1 到 3 的范围重复 Dice
三次。手动输入代码;For
为许多不同的用途提供代码补全选项。
For
视图是动态的;它根据输入计算其子视图,这些输入可能会随着应用程序的状态而变化。您可以使用 1...3
创建一个范围,就像您在 Int
中所做的那样。For
视图为范围内的每个值创建一个 Dice
。
步骤 4
1...3
范围是静态的。为了使其适应任意数量的骰子,您将再次使用视图状态。添加一个状态属性以表示骰子的数量。
struct ContentView: View {
private var numberOfDice: Int = 1
var body: some View {
VStack {
步骤 5
使用新属性使范围动态。
实验
尝试更改number
的默认值,以查看界面如何变化。
步骤 6
在For
视图和HStack
下方,添加两个按钮以增加和减少骰子的数量。
查看每个按钮闭包内的代码。在 Swift 中,您可以使用 +=
和 –=
来增加或减少属性的当前值。在这种情况下,您是在增加或减少 1。
实验
尝试一下按钮以确保它们正常工作。
}
}
HStack {
Button("Remove Dice") {
numberOfDice -= 1
}
Button("Add Dice") {
numberOfDice += 1
}
}
.padding()
}
.padding()
步骤 7
将骰子的数量减少到一个,然后点击移除骰子按钮。预览崩溃,因为范围 1...0
是无效的。
![A screenshot showing the preview, which has a large red X icon and the message, Preview Crashed.](https://docs-assets.developer.apple.com/published/4ce53f3e000006239f4c8cddbeeda969/buttons-and-state--preview-crash@2x.png)
步骤 8
为了防止人们在只有一个 Dice
时点击“移除骰子”按钮,您可以 禁用 该按钮以防止崩溃,并给人们一个按钮无响应的视觉提示。当 number
的值为 1 时,使用 .disabled
修饰符来禁用“移除骰子”按钮。
您使用 ==
运算符来检查两个数字是否相等。此比较的结果是一个 Bool
,它有两个值之一:true
或 false
。当 number
为 true
时,修饰符会禁用按钮。
步骤 9
骰子的图像固定为 100x100 点,因此屏幕上只能显示三个骰子图像。使用 .disabled
修饰符与添加骰子按钮一起,防止人们拥有超过三个骰子。
步骤 10
使用with
动画化骰子数量的变化。
//
// ContentView.swift
// DiceRoller
//
//
//
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("Dice Roller")
.font(.largeTitle.lowercaseSmallCaps())
}
.padding()
}
}
#Preview {
ContentView()
}
![A screenshot showing the text, Dice Roller, centered horizontally and vertically.](https://docs-assets.developer.apple.com/published/0f245a7b3fa6cc504e52783fe723a1a1/buttons-and-state--contentView-title@2x.png)
调整界面以支持更多骰子
使用灵活的宽度和高度,使骰子图像能够根据屏幕上的骰子数量动态调整大小。
步骤 1
将骰子限制增加到五个,然后点击添加骰子,直到有五个骰子。
一个HStack
视图总是会给其子视图提供所请求的大小,即使它需要超出屏幕的边界。
步骤 2
将Content
预览固定,然后切换到Dice
。使用灵活的框架以允许骰子图像缩小,并点击添加骰子,直到再次有五个骰子。
Image(systemName: "die.face.\(numberOfPips)")
.resizable()
.frame(maxWidth: 100, maxHeight: 100)
Button("Roll") {
步骤 3
现在骰子图像具有灵活的宽度和高度,HStack
可以将它们缩小以适应屏幕。但是当有四个或五个骰子时,由于HStack
上下没有任何限制其高度的内容,它们会在垂直方向上被拉伸。为防止这种情况,将骰子图像的纵横比设置为 1,然后点击添加骰子,直到再次有五个骰子。
1:1 或正方形的宽高比具有相等的宽度和高度。.fit
内容模式意味着如果图像的宽高比与可用空间不同,它将缩小到较小的轴,并在另一侧留出空白。
.resizable()
.frame(maxWidth: 100, maxHeight: 100)
.aspectRatio(1, contentMode: .fit)
Button("Roll") {
//
// ContentView.swift
// DiceRoller
//
//
//
import SwiftUI
struct ContentView: View {
private var numberOfDice: Int = 1
var body: some View {
VStack {
Text("Dice Roller")
.font(.largeTitle.lowercaseSmallCaps())
HStack {
ForEach(1...numberOfDice, id: \.description) { _ in
DiceView()
}
}
HStack {
Button("Remove Dice") {
withAnimation {
numberOfDice -= 1
}
}
.disabled(numberOfDice == 1)
Button("Add Dice") {
withAnimation {
numberOfDice += 1
}
}
.disabled(numberOfDice == 5)
}
.padding()
}
.padding()
}
}
#Preview {
ContentView()
}
![A screenshot showing a vertical stack. At the top is the text, Dice Roller. Below is a horizontal stack of five DiceViews. The leftmost and rightmost views extend beyond the edges of the screen. Below the dice is a horizontal stack of two buttons: Remove Dice and Add Dice. The Add Dice button is disabled.](https://docs-assets.developer.apple.com/published/22f414d1386610b904f4de35fa21253a/buttons-and-state--five-dice@2x.png)
在按钮标签中使用图像
自定义添加和移除骰子按钮以显示图像而不是文本。
步骤 1
在Content
中,为添加骰子按钮添加另一个参数以添加图像。
按钮显示的视图称为其标签。在许多情况下,您会使用图像和文本的组合作为按钮标签。
.disabled(numberOfDice == 1)
Button("Add Dice", systemImage: "plus.circle.fill") {
withAnimation {
numberOfDice += 1
步骤 2
对于这个按钮,一张图片足以告诉人们它的功能。但按钮应该始终有一个文本标签——无论是否可见——以便那些依赖于 VoiceOver 等功能的人可以使用。使用 .label
修饰符隐藏按钮文本。
尽管修饰符影响两个按钮,但“移除骰子”按钮仍然有文本标签,因为它没有图标可显示。
步骤 3
使用标题字体增大按钮大小。
步骤 4
在“移除骰子”按钮上添加一张图片。
HStack {
Button("Remove Dice", systemImage: "minus.circle.fill") {
withAnimation {
numberOfDice -= 1
//
// ContentView.swift
// DiceRoller
//
//
//
import SwiftUI
struct ContentView: View {
private var numberOfDice: Int = 1
var body: some View {
VStack {
Text("Dice Roller")
.font(.largeTitle.lowercaseSmallCaps())
HStack {
ForEach(1...numberOfDice, id: \.description) { _ in
DiceView()
}
}
HStack {
Button("Remove Dice") {
withAnimation {
numberOfDice -= 1
}
}
.disabled(numberOfDice == 1)
Button("Add Dice", systemImage: "plus.circle.fill") {
withAnimation {
numberOfDice += 1
}
}
.disabled(numberOfDice == 5)
}
.padding()
}
.padding()
}
}
#Preview {
ContentView()
}
![A screenshot showing a vertical stack. At the top is the text, Dice Roller. Below is one DiceView; then a horizontal stack of two buttons: Remove Dice and Add Dice. The Add Dice button's label has an icon of a plus sign in a filled circle, followed by its text.](https://docs-assets.developer.apple.com/published/e8ab7134bf0147580c6ae9c5bab1a88e/buttons-and-state--add-button-image@2x.png)
改善您的应用程序设计
为按钮和骰子设置样式,并为您的应用程序设置背景颜色。
步骤 1
添加一个名为 App Background 的自定义背景颜色集。
要了解如何做到这一点,请查看设计界面中的这一部分。
提示
仔细选择您的颜色集名称。一些名称,如 Background 和 Blue,与现有的系统定义颜色冲突。
![A screenshot showing the Color section of the inspector. The values of red, green, and blue are 0.208, 0.478, and 0.613.](https://docs-assets.developer.apple.com/published/3bca897a05f33eac1f12f92fb5a7be60/buttons-and-state--background-colorset@2x.png)
步骤 2
使用自定义颜色作为Content
的背景。
步骤 3
在VStack
上使用.tint
修饰符,以对应用程序应用全局白色色调。
.tint
修饰符仅影响依赖于强调色的视图——通常是按钮和切换控件等。视图以不同的方式使用色调。例如,请注意,滚动按钮获得了微妙的白色背景。
步骤 4
将文本视图的颜色更改为白色,使用.foreground
。
Text("Dice Roller")
.font(.largeTitle.lowercaseSmallCaps())
.foregroundStyle(.white)
HStack {
步骤 5
要使背景扩展以填充屏幕,请使用框架。
将.infinity
传递给max
和max
参数,以便框架可以在两个方向上尽可能扩展。
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.appBackground)
.tint(.white)
步骤 6
经典骰子是纯白色的,带有黑色的点。要使骰子面为实心,请打开 DiceView 文件并使用骰子符号名称的填充变体。
var body: some View {
VStack {
Image(systemName: "die.face.\(numberOfPips).fill")
.resizable()
.frame(maxWidth: 100, maxHeight: 100)
步骤 7
将骰子的前景色设置为白色。请注意,这将骰子变为白色,但点数保持透明。
.frame(maxWidth: 100, maxHeight: 100)
.aspectRatio(1, contentMode: .fit)
.foregroundStyle(.white)
Button("Roll") {
步骤 8
要将点变为黑色,请使用两种前景色:一种主色和一种副色。使用.foreground
修饰符将主色设置为黑色,副色设置为白色。
您正在看到 SF Symbol palette 渲染模式的效果。对于填充的骰子符号,点数采用主色,而其余部分则采用次色。
注意
一些 SF 符号是以层次结构组成的,具有可以采用主色、次色和第三色的不同元素。有四种 SF 符号渲染模式,您可以使用 .symbol
修饰符手动设置。您可以在 人机界面指南 中阅读更多关于渲染模式和 SF 符号的内容。
.frame(maxWidth: 100, maxHeight: 100)
.aspectRatio(1, contentMode: .fit)
.foregroundStyle(.black, .white)
Button("Roll") {
![A screenshot showing the Color section of the inspector. The values of red, green, and blue are 0.208, 0.478, and 0.613.](https://docs-assets.developer.apple.com/published/3bca897a05f33eac1f12f92fb5a7be60/buttons-and-state--background-colorset@2x.png)
问题 1 的 3
选择关于视图状态的正确陈述。
总结:按钮和状态
Buttons, state, and closures are key components of SwiftUI. They enable your app to respond to user actions, which lets you create powerful and expressive interfaces.
![Illustration consisting of a collage of items, including a magnifying glass, a weather icon, shapes in a layout, and a button.](https://docs-assets.developer.apple.com/published/77d4699a25189106b6b5ef508478cdbe/foundations--4.2next.png)