Home > Enterprise >  How can i Solve "Thread 1: Fatal error: Index out of range" error
How can i Solve "Thread 1: Fatal error: Index out of range" error

Time:09-29

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

  • Related