When to try open new controller with my tableview and progress bar I have "Index out of range".
I fetch data from Firestore Database and the idea is that when I tap button "next" I want to update progress bar and reload table view with new values. Number of rows must be equal number of sets, but each exercise has different amount of sets, so I want to change number of rows dynamically.
class StartViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var titleValue: String = ""
var dayOfWorkout: String = ""
var exerciseNumber = 0
var models: [DataCell2] = []
let user = Auth.auth().currentUser
var db = Firestore.firestore()
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var exerciseLabel: UILabel!
@IBOutlet weak var progressView: UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
progressView.transform = progressView.transform.scaledBy(x: 1, y: 3)
tableView.delegate = self
tableView.dataSource = self
retrieveWorkouts()
}
@IBAction func nextButtonPressed(_ sender: UIButton) {
nextExercise()
Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(updateUI), userInfo: nil, repeats: false)
tableView.reloadData()
}
@objc func updateUI() {
progressView.progress = getProgress()
}
func getProgress() -> Float {
return Float(exerciseNumber) / Float(models.count)
}
func nextExercise() {
if exerciseNumber + 1 < models.count {
exerciseNumber += 1
} else {
exerciseNumber = 0
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
**//HERE IS ERROR**
return models[exerciseNumber].sets!
}
And methods to fetch data from firestore
func retrieveWorkouts() {
db.collection("users").document("(user!.uid)").collection("WorkoutsName").document("(titleValue)").collection("Exercises").order(by: "Number").getDocuments { (querySnapshot, error) in
if let error = error{
print("(error.localizedDescription)")
}else {
if let snapshotDocuments = querySnapshot?.documents {
for doc in snapshotDocuments {
let data = doc.data()
if let numberDb = data["Number"] as? Int, let exerciseDb = data["Exercise"] as? String, let kgDb = data["kg"] as? Int, let setsDb = data["Sets"] as? Int, let repsDb = data["Reps"] as? Int, let workoutName = data["workoutName"] as? String {
print("data is (data)")
let newModel = DataCell2(Number: numberDb, Exercise: exerciseDb, kg: kgDb, sets: setsDb, reps: repsDb, workoutName: workoutName)
self.models.append(newModel)
self.tableView.reloadData()
}
}
}
}
}
}
2
Answers
You are basing the number of rows on
models[exerciseNumber].sets!
and you are setting the exercise number in
However you have a logic error in here: you are checking the exercise number is less than the models.count, and if so increasing it. This will lead to exerciseNumber == models.count, and as arrays are zero-indexed this will be beyond the end of the array and give the out-of-range error.
An easy fix would be to change the method to:
There are smarter ways of doing this, but this will work for now.
You have a timing issue. The view will try to create the tableView when it loads. At that point the models array will be empty as the async firebase method won’t have run and updated it. But you initialise exerciseNumber to 0.
This means that the
numberOfRowsInSection
delegate method will try to accessmodels[0
] but as the array is empty at this point this doesn’t exist and causes the index out of range crash.You can fix this by a bit of defensive programming:
You could also add a second guard or a precondition to ensure that exerciseNumber < models.count. While from the bit of code you have posted this should not be necessary, it’s never a bad idea to programme defensively.