skip to Main Content

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


  1. 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()
            
        }
    
    Login or Signup to reply.
  2. 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

    Login or Signup to reply.
  3. Add an extension to the Array:

    extension Array {
        func object(at index: Int) -> Element? { 
            if index < count {
                return self[index]
            } else {
                return nil
            }
        }
    }
    

    Now use this method whenever you access the element from the array.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search