前言:
实战demo:控制随机生成1~6的数字完成1个至多个的骰子类效果,使用到的技术点:@State
,Button
,resizable
,aspectRatio
,.labelStyle
,disabled
,通过更新@State
的值自动更新视图(与React state相似)
摇骰子最终效果:
创建DiceRoller项目 和 DiceView 页面
通过XCode
创建项目:DiceRoller
(点击查看xcode创建项目)
创建siwftUI页面:DiceView
(点击查看xcode创建siwftUI页面)
在 DiceView
页面添加的骰子icon
// DiceView.swift
import SwiftUI struct DiceView: View {
var body: some View {
- Text("Hello, World!")
+ Image(systemName: "die.face.1")
}
}
#Preview {
DiceView()
}
这里使用到了Xcode 集成 SF Symbols
字体图标(点击查看有哪些具体图标),其中die.face.x
x可以为1~6对应骰子点数样式可以在XCode
中查看对应文档
修改DiceView
样式变大且通过 var numberOfPips
变量替换 systemName
结尾的数字来控制点数对应的字体图标样式
// DiceView.swift
import SwiftUI struct DiceView: View {
+ var numberOfPips: Int = 1
var body: some View {
- Image(systemName: "die.face.1")
+ Image(systemName: "die.face.\(numberOfPips)")
+ .resizable()
+ .frame(width: 100, height: 100)
}
}
#Preview {
DiceView()
}
其中 resizable
会让image
填充到整个页面的空白,再通过frame
规定长宽来达到放大image
图标的效果
@State控制UI显示的骰子点数
首先需要在点数icon竖直下方添加个Button
,使用VStack
使得 点数icon 与 Button
竖直排列,和控制numberOfPips的值
// DiceView.swift
import SwiftUI struct DiceView: View {
var numberOfPips: Int = 1
var body: some View {
+ VStack{
...
+ Button("Roll") {
+ numberOfPips = Int.random(in: 1...6)
+ }
+ }
}
}
#Preview {
DiceView()
}
这时候XCode
会报错:Cannot assign to property: 'self' is immutable
由于var numberOfPips
是一个长量不能被赋值,但我又想通过改变numberOfPips
达到骰子点数变化,这时候就需要使用 @State:既可以动态赋值又可以触发UI update 视图(前端React state近似)
修改 numberOfPips
为 @State
属性和样式调整
// DiceView.swift
import SwiftUI struct DiceView: View {
- var numberOfPips: Int = 1
+ @State private var numberOfPips: Int = 1
var body: some View {
VStack{
...
Button("Roll") {
numberOfPips = Int.random(in: 1...6)
}
+ .buttonStyle(.bordered)
}
}
}
#Preview {
DiceView()
}
简单的摇骰子功能就实现了,但是icon显示的有点生硬,使用withAnimation
(点击查看文档)加点动画过度下
// DiceView.swift
...
Button("Roll") {
+ withAnimation {
numberOfPips = Int.random(in: 1...6)
+ }
}
这样简单的一个骰子就完成了,接下阐述设置多个骰子
ContentView 入口页动态设置骰子数量
上一步骤介绍了@State
控制点数,在ContentView
中也可以使用@State
控制骰子个数,切换到ContentView.siwft
文件中默认引用三个
//ContentView.siwft
import SwiftUI
struct ContentView:View {
@State private var numberOfDice: Int = 3
var body: some View {
VStack {
Text("Dice Roller")
.font(.largeTitle.lowercaseSmallCaps())
HStack {
ForEach(1...numberOfDice, id: \.description) {
_ in DiceView()
}
}
}
.padding()
}
}
#Preview {
ContentView()
}
其中:ForEach
(具体文档)为方便numberOfDice
动态的变化而遍历 DiceView
来控制骰子数量
接下来添加Button
触发变化numberOfDice
达到控制骰子数量
//ContentView.siwft
...
VStack {
Text("Dice Roller")
.font(.largeTitle.lowercaseSmallCaps())
HStack {
ForEach(1...numberOfDice, id: \.description) {
_ in DiceView()
}
}
+ HStack {
+ Button("减少骰子") {
+ numberOfDice -= 1
+ }
+ .buttonStyle(.bordered)
+ Button("添加骰子") {
+ numberOfDice += 1 }
+ }
+ .buttonStyle(.bordered)
+ .padding()
+ }
}
Button disabled 控制最小值
当一直numberOfDice
减到0时Preview
崩溃,因为ForEach
遍历范围从1~0 是错误的
这时候需要判断当numberOfDice == 1
button就不能点击
//ContentView.siwft
...
HStack {
Button("减少骰子") {
numberOfDice -= 1
}
.buttonStyle(.bordered)
+ .disabled(numberOfDice == 1)
...
}
aspectRatio 适配较多骰子样式
当numberOfDice
大于4个后样式开始变形
这里需要修改 DiceView
中 Image
设置为灵活的最大高度和宽度frame
,和保持宽高比1:1 aspectRatio
(文档地址)
// DiceView
Image(systemName: "die.face.\(numberOfPips)")
.resizable()
+ .frame(maxWidth: 100, maxHeight: 100)
+ .aspectRatio(1, contentMode: .fit)
可以添加多个而不会变形:
Button样式控制
使用+
和 -
icon 替换减少骰子
和 添加骰子
看起来会更简约,其中labelStyle
只显示标题
icon 名称和对应样式可以查看文章第一步提到的Xcode
中icon
查询
//ContentView.siwft
...
HStack {
Button("减少骰子"
+ , systemImage: "minus.circle.fill") {
numberOfDice -= 1
}
- .buttonStyle(.bordered)
+ .disabled(numberOfDice == 1)
Button("添加骰子"
+ , systemImage: "plus.circle.fill") {
numberOfDice -= 1
}
- .buttonStyle(.bordered)
}
.padding()
+ .labelStyle(.iconOnly)
+ .font(.title)
...
整体样式调整
首页添加个 appBackground
app 背景颜色(查看如何添加主体颜色),添加成功后修改 ContentView
样式
//ContentView.siwft
...
var body: some View {
VStack {
...
}
}
.padding()
+ .background(.appBackground)
...
先把Vstack
高宽使用infinity
撑满全屏,和.tint
设置下视图和图像颜色
//ContentView.siwft
...
var body: some View {
VStack {
...
}
}
.padding()
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.appBackground)
+ .tint(.white)
...
文本:Dice Roller 也设置成白色
//ContentView.siwft
...
var body: some View {
VStack {
Text("Dice Roller")
.font(.largeTitle.lowercaseSmallCaps())
+ .foregroundStyle(.white)
}
}
...
现在骰子的颜色也统一成白色调 , 切换到 DiceView.swift
代码中,骰子图标换为填充的图标(die.face.x.fill
)
// DiceView
Image(systemName:
- "die.face.\(numberOfPips)"
+ "die.face.\(numberOfPips).fill"
)
.resizable()
.frame(maxWidth: 100, maxHeight: 100)
+ .foregroundStyle(.black, .white)
最终效果:
总结
本章主要是通过 @State
控制定义的变量,当变量改变后UI视图层也会更新改变,达到随机生成变量而改变骰子点数的效果,也拓展了方法:
aspectRatio
视图比例显示withAnimation
动画过度ForEach
遍历数组Button
中icon的使用和disabled
按钮失效labelStyle iconOnly
按钮只显示icon