I stumbled across this strange behavior and have no clue how to solve it.
I have a Picker
in which the user can chose to generate either 5, 10, or 20 random numbers. The possible numbers are later stored in an array and the selection is a State var:
let possibleAmountOfNumbers = [5, 10, 20]
@State private var amountOfNumbers = 10
@State private var arrayOfNumbers: [Int] = []
Then I have a button
, which calls a function
to generate random numbers. Those numbers are appended to an array
of Int
. I make sure that no number is duplicated and that the numbers are sorted. Every time the button is clicked, the array is emptied and then the random numbers are added. I pass in the parameter amountOfNumbers
from the picker
how many random numbers shall be generated.
Nothing fancy till now.
Then I want to display the numbers in a List
.
Section {
if !arrayOfNumbers.isEmpty {
ForEach(0..<amountOfNumbers) { index in
Text(String(arrayOfNumbers[index]))
}
}
}
This part of the code is not working correctly.
Xcode tells me with a yellow warning the following:
Non-constant range: argument must be an integer literal
What is happening:
-
If the default selection of the picker (which is 10) is not altered and I press the Button, 10 random numbers are displayed. If I press the button again, 10 newly generated numbers are displayed. This is, what I’m expecting.
-
If I change the picker before I pressed the button, either to 5 or 20, and then press the button, either 5 or 20 numbers are displayed. If I press the button again, either 5 or 20 newly generated numbers are displayed. This is what I’m expecting.
-
If I press the button, 10 numbers are displayed. If I change the picker to 20 and press the button again, only 10 newly generated numbers are displayed. This is NOT what I’m expecting.
-
If I press the button, 10 numbers are displayed. If I change the picker to 5 and press the button, the app crashes and I get an out of range error message in the ForEach loop.
Note:
The State variable amountOfNumbers
is correctly updated. If I change the Picker from 10 to 5, I can see that the variable is indeed 5.
Still, in above cases when I change the picker after I already pressed the button, the ForEach tries to loop over the FORMER value of the picker. If the new number is smaller, I get the out of range error message. If the new number is higher, it will not loop over the whole array.
Question:
It seems that the ForEach is not the correct way to display the numbers in the List view. Unfortunately, I did not find a working solution and hope you can help me out.
The complete code is:
//
// ContentView.swift
// ForEachAndPicker
//
// Created by xy on 10.01.23.
//
import SwiftUI
struct ContentView: View {
// User can select how many numbers are displayed, out of 5, 10, or 20. Default: 10
let possibleAmountOfNumbers = [5, 10, 20]
@State private var amountOfNumbers = 10
@State private var arrayOfNumbers: [Int] = []
var body: some View {
List {
// Section 1: Picker to chose amount of numbers
Section(header: Text("Chose amount of random numbers")) {
Picker("Amount of numbers", selection: $amountOfNumbers) {
ForEach (possibleAmountOfNumbers, id: \.self) {
Text("\($0)")
}
}
.pickerStyle(.segmented)
}
// Section 2: Button to generate random numbers
Section {
HStack {
Spacer()
Button("Generate Numbers") {
arrayOfNumbers = generateRandomNumbers(amountOfNumbers: amountOfNumbers)
}
.buttonStyle(.borderedProminent)
Spacer()
}
}
// Section 3: If numbers are available, display them
Section {
if !arrayOfNumbers.isEmpty {
ForEach(0..<amountOfNumbers) { index in
Text(String(arrayOfNumbers[index]))
}
}
}
}
}
// Generate random numbers
func generateRandomNumbers(amountOfNumbers: Int) -> [Int] {
// Delete array
arrayOfNumbers = []
var interimArray: [Int] = []
while interimArray.count < amountOfNumbers {
let randomInteger = Int.random(in: 1...100)
if !interimArray.contains(randomInteger) {
interimArray.append(randomInteger)
}
}
arrayOfNumbers = interimArray.sorted()
return arrayOfNumbers
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}