Hey i got "Thread 1: Fatal error: Index out of range" (in this line selectedId = idArray[indexPath.row] ) error every time i clicked any cell at table view. How i can solve this problem. I think i have problem with my arrays but i cant figure that out. I was do same thing at my last app but i couldnt get any error.
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var tableView: UITableView!
var nameArray = [String]()
var idArray = [UUID?]()
var selectedName = ""
var selectedId : UUID?
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
navigationController?.navigationBar.topItem?.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.add, target: self, action: #selector(addNewPatient))
getData()
}
override func viewWillAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector: #selector(getData) , name: NSNotification.Name(rawValue: "newData"), object: nil)
}
@objc func getData() {
nameArray.removeAll(keepingCapacity: false)
idArray.removeAll(keepingCapacity: false)
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ToName")
fetchRequest.returnsObjectsAsFaults = false
do {
let results = try context.fetch(fetchRequest)
if results.count > 0 {
for result in results as! [NSManagedObject] {
if let name = result.value(forKey: "name") as? String {
self.nameArray.append(name)
}
if let id = result.value(forKey: "id") as? UUID {
self.idArray.append(id)
}
}
}
} catch {
print("error")
}
tableView.reloadData()
}
@objc func addNewPatient() {
selectedName = ""
performSegue(withIdentifier: "toNameVC", sender: nil)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return nameArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
var content = cell.defaultContentConfiguration()
content.text = nameArray[indexPath.row]
// content.secondaryText = "secondary test"
cell.contentConfiguration = content
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toNameVC" {
let destinationVC = segue.destination as! ToNameViewController
destinationVC.choosenName = selectedName
destinationVC.choosenId = selectedId
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedName = nameArray[indexPath.row]
selectedId = idArray[indexPath.row]
performSegue(withIdentifier: "toNameVC", sender: nil)
}
}
This is my toNameViewController.swift page
ToNameViewController.swift
Patient Record App
import UIKit
import CoreData
class ToNameViewController: UIViewController {
@IBOutlet weak var nameLabelText: UITextField!
@IBOutlet weak var tcLabelText: UITextField!
@IBOutlet weak var birthDateLabelText: UITextField!
var choosenName = ""
var choosenId : UUID?
override func viewDidLoad() {
super.viewDidLoad()
if choosenName != "" {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ToName")
let idString = choosenId?.uuidString
fetchRequest.predicate = NSPredicate(format: "id = %@", idString!)
fetchRequest.returnsObjectsAsFaults = false
do {
let results = try context.fetch(fetchRequest)
if results.count > 0 {
for result in results as! [NSManagedObject] {
if let name = result.value(forKey: "name") as? String {
nameLabelText.text = name
}
if let tc = result.value(forKey: "tc") as? Int {
tcLabelText.text = String(tc)
}
if let birth = result.value(forKey: "birth") as? Int {
birthDateLabelText.text = String(birth)
}
}
}
} catch {
print("error")
}
}else {
}
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(hiddenKeyboard))
view?.addGestureRecognizer(gestureRecognizer)
}
@objc func hiddenKeyboard() {
view?.endEditing(true)
}
@IBAction func saveButton(_ sender: Any) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let newPatient = NSEntityDescription.insertNewObject(forEntityName: "ToName", into: context)
newPatient.setValue(nameLabelText.text, forKey: "name")
if let tc = Int(tcLabelText.text!) {
newPatient.setValue(tc, forKey: "tc")
}
if let birth = Int(birthDateLabelText.text!) {
newPatient.setValue(birth, forKey: "birth")
}
do {
try context.save()
print("saved")
} catch {
print("error")
}
NotificationCenter.default.post(name: NSNotification.Name("newData"), object: nil)
self.navigationController?.popViewController(animated: true)
}
}
3
Answers
Inside your
getData
function u append values toidArray
. Butresult.value(forKey: "id")
is optional and values get appended unless it isnil
. So there may be a difference between the count betweennameArray
andidArray
. So if theresult.value(forKey: "id")
is nil append a default value toidArray
.This is a common mistake: Multiple arrays for the data source is extremely error-prone if both arrays are populated with optionals.
Declare a custom struct
Declare the data source
Populate the array (
valueForKey
syntax is outdated)In
numberOfRowsInSection
In
cellForRowAt
And in
didSelectRowAt
You can even pass the
Item
instance to the second view controller rather than the twoselected...
properties.But why not even
This avoids any out of range crash
Add an extension to the Array:
Now use this method whenever you access the element from the array.