问题描述
在练习一个小游戏时,发现ForEach报如下错误:
Non-constant range: argument must be an integer literal
报错代码为:
ForEach(0..<QNum3Count) { num in
经排查发现报错的原因为ForEach的范围参数必须是常量和确定的值,我的ForEach中QNum3Count是一个计算属性,只有在运算时才能确定,因此编译器无法在编译时推断这个范围,因此产生该报错并造成游戏崩溃。
var QNum3Count: Int {
return Int(String(QNum3).count)
}
因此,我应该将QNum3Count变量由计算属性改为具体的值。
@State private var QNum3Count: Int = 1 // 乘法1h和乘法2的个数
并将QNum3Count的计算放到onAppear的函数中。
func RandomQue() {
QNum1 = Int.random(in: 1..<MulRange+1)
QNum2 = Int.random(in: 1..<MulRange+1)
QNum3Count = Int(String(QNum3).count)
userAnswer = Array(repeating: "", count: QNum3Count)
}
.onAppear {
RandomQue()
}
问题延续
修改代码后,发现崩溃并没有解决。即使我运行“Clean Build Folder..”或强退Xcode。
经排查发现,这里崩溃的原因是另一个ForEach存在报错,报错代码如下:
ForEach(0..<QNum3Count,id: \.self) { index in
TextField("", text: Binding(
get: {
userAnswer[index]
}, set: { newValue in
if newValue.count <= 1 && newValue.allSatisfy({$0.isNumber}) {
userAnswer[index] = newValue
}
}
))
.keyboardType(.decimalPad)
.frame(width: 60, height: 80)
.multilineTextAlignment(.center) // 多行文本对齐
.font(.system(size: 50))
.foregroundColor(Color.white)
}
在这里报错的原因为index超出userAnswer数组的范围,导致数组访问越界。因此,需要给上面的TextField加一个判断,当index大于或等于userAnswer[index]时,返回””空。
TextField("", text: Binding(
get: {
if index < userAnswer.count {
return userAnswer[index]
} else {
return ""
}
}, set: { newValue in
if newValue.count <= 1 && newValue.allSatisfy({$0.isNumber}) && index < userAnswer.count {
userAnswer[index] = newValue
}
}
))
问题总结,本次实际上是两个问题,特别注意的是,当数组存在访问越界的情况,Xcode的预览效果都会存在报错或模拟器闪退的情况。
完整代码
struct Game: View {
@Binding var mulTableNum: Int // 跳转视图
@Binding var MulRange: Int // 乘法表范围
@Binding var MulNum: Int // 题目数量
@Binding var QNum: Int // 当前题目数
@State private var QNum1: Int = 1 // 乘法1变量
@State private var QNum2: Int = 1 // 乘法2变量
@State private var QNum3Count: Int = 1 // 乘法1h和乘法2的个数
var QNum3: Int {
return QNum1 * QNum2
}
@State private var userAnswer = [String]()
var title: String = "乘法测试"
func RandomQue() {
QNum1 = Int.random(in: 1..<MulRange+1)
QNum2 = Int.random(in: 1..<MulRange+1)
QNum3Count = Int(String(QNum3).count)
userAnswer = Array(repeating: "", count: QNum3Count)
}
func getFullAnswer() -> String {
return userAnswer.joined()
}
var body: some View {
NavigationStack {
VStack {
Spacer()
.frame(height: 50)
Text("共有 \(MulNum) 道题,当前为第 \(QNum) 道题")
.font(.title2)
Spacer()
.frame(height: 50)
HStack {
Text("\(QNum1)")
.font(.largeTitle)
.frame(width: 60, height: 80)
.foregroundColor(Color.white)
.background(Color.blue)
Text("*")
.font(.system(size: 60))
Text("\(QNum2)")
.font(.largeTitle)
.frame(width: 60, height: 80)
.foregroundColor(Color.white)
.background(Color.blue)
Text("=")
.font(.system(size: 60))
Text("?")
.font(.largeTitle)
.frame(width: 60, height: 80)
.foregroundColor(Color.white)
.background(Color.blue)
}
Spacer()
.frame(height: 60)
Text("请输入你的答案")
.font(.system(size: 30))
ZStack {
HStack {
ForEach(0..<QNum3Count,id: \.self) { num in
Text("")
.font(.largeTitle)
.frame(width: 60,height: 80)
.foregroundColor(Color.white)
.background(Color.blue)
if num != QNum3Count {
Spacer()
.frame(width: 20)
}
}
}
HStack {
ForEach(0..<QNum3Count,id: \.self) { index in
TextField("", text: Binding(
get: {
if index < userAnswer.count {
return userAnswer[index]
} else {
return ""
}
}, set: { newValue in
if newValue.count <= 1 && newValue.allSatisfy({$0.isNumber}) && index < userAnswer.count {
userAnswer[index] = newValue
}
}
))
.keyboardType(.decimalPad)
.frame(width: 60, height: 80)
.multilineTextAlignment(.center) // 多行文本对齐
.font(.system(size: 50))
.foregroundColor(Color.white)
}
}
}
Button("提交答案") {
let answer = getFullAnswer()
if Int(answer) == QNum3 {
print("恭喜你答对了")
} else {
print("很抱歉,答错了")
}
RandomQue()
}
Spacer()
}
.navigationTitle(title)
.navigationBarItems(
leading: Button(action: {
mulTableNum = 1
}, label: {
Image(systemName: "arrow.backward")
.font(.title)
.foregroundColor(Color.black)
})
)
.onAppear {
RandomQue()
}
}
}
}