SwiftUI学习笔记4-按钮
本节内容创建和自定义按钮使用闭包执行代码尾随闭包语法使用State属性更新视图使用ForEach创建动态数量的视图。创建具有状态视图并学习如何使用按钮使应用程序具有交互性。用State属性和按钮创建虚拟骰子通常建议将state声明为 private 或 fileprivate。这样做的原因是封装性将状态变量限制在声明的结构体或视图中可以防止外部组件意外修改这个状态从而更好地维护数据的一致性。明确性使用 private 可以提高代码的可读性指示该状态是内部使用的不应从其他地方访问。SF Symbols提供了骰子的所有面.resizable修饰符表示图像可以伸展以填充任何空间.使图像能够根据指定的宽度和高度进行缩放。这意味着当你给图像指定一个框架如 .frame(width: 100, height: 100)时图像将根据这个框架来调整其大小编辑了一个骰子和按钮让骰子的图片随着数字随机改变而改变。// DiceView.swift // DiceRoller // // Created by sakiko on 2026/4/23. // import SwiftUI struct DiceView: View { var body: some View { VStack { //包装在VStack里方便添加按钮 var numberOfPips : Int 1 //表示骰子点数 Image(systemName: die.face.\\(numberOfPips)) .resizable() .frame(width: 100, height: 100) Button(Roll) { //添加按钮按钮名字叫Roll numberOfPips Int.random(in: 1...6)//控制numberOfPip为随机生成 } } } } #Preview { DiceView() }在用户点击 Roll 按钮且numberOfPips属性变化时更新图像。StateSwiftUI不会监控应用中的每个属性变化所以需要使用State来标记属性告诉它要随时更新UI在State里定义要变化的属性就可以实现实时更新UI的效果点击Roll骰子随机变化struct DiceView: View { State private var numberOfPips: Int 1//表示骰子点数 var body: some View { VStack { //包装在VStack里方便添加按钮 Image(systemName: die.face.\\(numberOfPips)) .resizable() .frame(width: 100, height: 100) Button(Roll) { //添加按钮按钮名字叫Roll numberOfPips Int.random(in: 1...6)//控制numberOfPip为随机生成 } } } }使用.buttonStyle(.bordered)来为按钮添加边框使用withAnimation来动画化让图像切换更加平滑。动态显示骰子.largeTitle.有很多形式其中一个是lowercaseSmallCaps这里有很多不同的对齐方式等有用到的需求时候再看吧VStack { Text(Dice Roller) .font(.largeTitle.lowercaseSmallCaps()通过添加实例的方式添加元素到控制显示的页面// // ContentView.swift // DiceRoller // // Created by sakiko on 2026/4/23. // import SwiftUI struct ContentView: View { var body: some View { VStack { Text(Dice Roller) .font(.largeTitle.lowercaseSmallCaps()) HStack { DiceView()//通过添加实例的方式来添加显示的元素 DiceView() DiceView() } } .padding() } } #Preview { ContentView() }ForEach的使用显示任意数量的骰子ForEach在数量不定的时候使用它可以规定显示多少个实例最下面表示要显示的实例。struct ContentView: View { var body: some View { VStack { Text(Dice Roller) .font(.largeTitle.lowercaseSmallCaps()) HStack { ForEach(1...3, id: \\.description) { _ in DiceView()} } } .padding() } } #Preview { ContentView() }为骰子数量添加属性并用带有闭包的按钮控制骰子的增加和减少HStack { Button(移除骰子) { numberOfDice - 1 //这是一个闭包控制骰子总数 } Button(添加骰子) { numberOfDice 1//同上 } }因为没有限制在值为1时禁用功能所以在移除到0的时候会炸。添加禁用功能.disabled(numberOfDice 1)本质是利用布尔值当 1的时候修饰符会禁用按钮。// // ContentView.swift // DiceRoller // // Created by sakiko on 2026/4/23. // import SwiftUI struct ContentView: View { State 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(移除骰子) { withAnimation{ numberOfDice - 1 //这是一个闭包控制骰子总数 }//.disabled控制禁用 } .disabled(numberOfDice 1) Button(添加骰子) { withAnimation{//动画化增加骰子 numberOfDice 1//同上 } } .disabled(numberOfDice 3) } .buttonStyle(.bordered) .padding() } } #Preview { ContentView() }效果如下调整界面使用更多骰子当有四个或五个骰子时它们会垂直拉伸因为HStack上下没有东西来限制其高度。为防止这种情况将骰子图片的宽高比设置为 1这是修改前VStack { //包装在VStack里方便添加按钮 Image(systemName: die.face.\\(numberOfPips)) .resizable() .frame(width: 100, height: 100)//使用.resizable和maxwidth修改后.aspectRatio(1, contentMode: .fit)表示如果图像的宽高比和可用空间不一致会缩小到比较小的轴。并在另一边留下空白空间。VStack { //包装在VStack里方便添加按钮 Image(systemName: die.face.\\(numberOfPips)) .resizable() .frame(maxWidth: 100, maxHeight: 100) .aspectRatio(1, contentMode: .fit)左边为未添加的状态右边为添加后的状态在按钮标签中使用图像按钮必须要有文本标签即使它未必显示成对应的内容比如显示成图片为Button添加参数来添加显示在按钮上的图片HStack { Button(移除骰子, systemImage: minus.circle.fill) { withAnimation{ numberOfDice - 1 //这是一个闭包控制骰子总数 }//.disabled控制禁用 } .disabled(numberOfDice 1) //为骰子button添加另一个参数以添加图像,上面也是这样 Button(添加骰子, systemImage: plus.circle.fill) { withAnimation{//动画化增加骰子 numberOfDice 1//同上 } }使用.labelStyle(.iconOnly)修饰符来隐藏按钮文本.buttonStyle(.bordered) .padding() .labelStyle(.iconOnly)//隐藏按钮文本 .font(.title)//增加按钮大小设置背景颜色与样式.padding() .background(Color(AppBackground))//为ContentView body里全部内容的背景 .tint(.white).tint修饰符仅影响依赖强调色的视图,设置成白色利用.foregroundStyle(.white) 将文本前景色设置为白色,效果如下使用frame将背景扩展这是上一课的内容。将.infinity参数传递给maxWidth和maxHeight让框架在两个方向尽可能扩展使用填充变体改变图片的前景色来改变图片样式。可以使用两种前景色一种主色一种次色DiceView.swiftstruct DiceView: View { State private var numberOfPips: Int 1//表示骰子点数 var body: some View { VStack { //包装在VStack里方便添加按钮 Image(systemName: die.face.\\(numberOfPips).fill)//.fill是填充变体的意思 .resizable() .frame(maxWidth: 100, maxHeight: 100) .aspectRatio(1, contentMode: .fit) .foregroundStyle(.black, .white)//将骰子前景色设置为白色有些 SF Symbols 是按层级组合的包含可以呈现主色、次色和三级色的不同元素。有四种 SF Symbol 渲染模式你可以使用.symbolRenderingMode修饰符手动设置。关于渲染模式以及更多关于 SF Symbols 的信息效果如下Button的模式Button(AddOne){ counterValue 1 }练习将 Roll 按钮的按钮边框形状更改为胶囊形。使用.symbolRenderingMode自定义移除骰子和添加骰子按钮。I不要在骰子图像和其下方的 Roll 按钮下方放置图像而是修改DiceView以便人们可以直接点击骰子本身来随机化它。struct DiceView: View { State private var numberOfPips: Int 1//表示骰子点数 var body: some View { VStack { //包装在VStack里方便添加按钮 Image(systemName: die.face.\\(numberOfPips).fill)//.fill是填充变体的意思 .resizable() .frame(maxWidth: 100, maxHeight: 100) .aspectRatio(1, contentMode: .fit) .foregroundStyle(.black, .white)//将骰子前景色设置为白色 Button(摇一摇) { //添加按钮按钮名字叫Roll withAnimation{//使图像变换平滑化,这本质是尾随闭包 numberOfPips Int.random(in: 1...6)//控制numberOfPip为随机生成 } }.frame(width: 100, height: 32) //在Button的外面创建一个框架框柱它 .cornerRadius(16) .buttonStyle(.bordered).frame(width: 100, height: 32) //在Button的外面创建一个框架框柱它.cornerRadius(16)创建这个框架的圆角.buttonStyle(.bordered) 将按钮的样式设置为“边框样式”.symbolRenderingMode主要有两个模式.monochrome以单一颜色渲染符号通常用来使符号与背景颜色对比明显适合于简洁的设计。.hierarchical使用层次化的颜色渲染符号适合用于强调某个层级关系显示更多的细节。HStack { Button(移除骰子, systemImage: minus.circle.fill) { withAnimation{ numberOfDice - 1 //这是一个闭包控制骰子总数 }//.disabled控制禁用 } .disabled(numberOfDice 1) .symbolRenderingMode(.hierarchical) //为骰子button添加另一个参数以添加图像,上面也是这样 Button(添加骰子, systemImage: plus.circle.fill) { withAnimation {//动画化增加骰子 numberOfDice 1//同上 }3.怎么在按钮里写图片这个还不会。标记一下现在是把按钮放的很大但是图片还是原来的大小struct DiceView: View { State private var numberOfPips: Int 1//表示骰子点数 var body: some View { VStack { //包装在VStack里方便添加按钮 Button(摇一摇, systemImage: die.face.\\(numberOfPips).fill)//.fill是填充变体的意思 {withAnimation{//使图像变换平滑化,这本质是尾随闭包 numberOfPips Int.random(in: 1...6)//控制numberOfPip为随机生成 } } .labelStyle(.iconOnly) .frame(width: 200, height: 200) .aspectRatio(1, contentMode: .fit) .foregroundStyle(.black, .white) .font(.title) .aspectRatio(contentMode: .fill) } } }只能这样了…