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)
}
}
CodePudding user response:
Inside your getData
function u append values to idArray
. But result.value(forKey: "id")
is optional and values get appended unless it is nil
. So there may be a difference between the count between nameArray
and idArray
. So if the result.value(forKey: "id")
is nil append a default value to idArray
.
@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)
//change here like this
if let id = result.value(forKey: "id") as? UUID {
self.idArray.append(id)
}else{
self.idArray.append("your_default_value")//default value or nil
}
}
}
} catch {
print("error")
}
tableView.reloadData()
}
CodePudding user response:
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
struct Item {
let name: String
let id: UUID?
}
Declare the data source
var items = [Item]()
Populate the array (valueForKey
syntax is outdated)
let fetchRequest = NSFetchRequest<ToName>(entityName: "ToName")
fetchRequest.returnsObjectsAsFaults = false
do {
let results = try context.fetch(fetchRequest)
for result in results {
if let name = result.name {
self.items.append(Item(name: name, id: result.id))
}
}
tableView.reloadData()
} catch {
print(error)
}
In numberOfRowsInSection
return items.count
In cellForRowAt
content.text = items[indexPath.row].name
And in didSelectRowAt
let item = items[indexPath.row]
selectedName = item.name
selectedId = item.id
You can even pass the Item
instance to the second view controller rather than the two selected...
properties.
But why not even
var toNames = [ToName]()
This avoids any out of range crash