SIGABRT when selecting rows in TableViewController - swift

I have a problem:
When I select the 2nd cell of my tableView, I get a SIGABRT. But when I select the first one, everything works just fine.
It must have to do with my TableViewController Class, because "SET ROWS IN SECTION" (from my print statement) will be printed 3 times, when I select the 2nd cell, but 3 * 4 = 12 times, when I select the first one...Oh... and I get this error:
GONNA SET THE ROWS IN SECTION
2
SET SECTION 2 TO 1
GONNA SET THE ROWS IN SECTION
0
SET SECTION 0 TO 1
GONNA SET THE ROWS IN SECTION
1
SET SECTION 1 TO 3
2016-07-04 12:43:14.407 Sessions[1229:629535] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'
*** First throw call stack:
(0x1822e6db0 0x18194bf80 0x1821c7098 0x187b7d5ac 0x187933f1c 0x18756d444 0x18752aff8 0x1878e4b88 0x187529fc4 0x187529d5c 0x187529b68 0x187794a20 0x18752fac8 0x18743fac8 0x187465350 0x18743ed40 0x182c18cc0 0x18743ebc4 0x18744c678 0x18763edbc 0x18743fac8 0x18743f7ac 0x18743ed40 0x182c18cc0 0x18743ebc4 0x18744c678 0x1876df8c0 0x1874535b4 0x18763e8d4 0x18775e334 0x18763e44c 0x1875f8818 0x187503e40 0x187503b1c 0x187503a84 0x1874401e4 0x184dd2994 0x184dcd5d0 0x184dcd490 0x184dccac0 0x184dcc820 0x1874365f8 0x18229c728 0x18229a4cc 0x18229a8fc 0x1821c4c50 0x183aac088 0x1874ae088 0x1000f8b54 0x181d628b8)
libc++abi.dylib: terminating with uncaught exception of type NSException
"SET SECTION 2 TO 1" means, that section 2 has 1 cell...
This is my class:
import UIKit
class SessionsTableViewController: UITableViewController {
let header = ["Mediatoren", "Medianden", "Konfliktverlauf"]
var medianden: Array<String> = []
var mediators: Array<String> = []
// ----------------
// MARK : IBACTIONS
// ----------------
(I just deleted these...)
// ---------------------
// MARK : OVERRIDE FUNCS
// ---------------------
override func viewDidLoad() {super.viewDidLoad() }
override func didReceiveMemoryWarning(){ super.didReceiveMemoryWarning()}
override func viewWillAppear(animated: Bool) {
print(sessions.workingOnSession)
medianden.append((sessions.workingOnSession["Name1"] as? String)!)
medianden.append((sessions.workingOnSession["Name2"] as? String)!)
medianden.append((sessions.workingOnSession["Name3"] as? String)!)
medianden.append((sessions.workingOnSession["Name4"] as? String)!)
//print(medianden)
for _ in 0...3 {
if medianden.last == "" {
medianden.removeLast()
}
}
print(medianden)
mediators.append((sessions.workingOnSession["MID"] as? String)!)
mediators.append((sessions.workingOnSession["MID2"] as? String)!)
//print(mediators)
for _ in 0...1 {
if mediators.last == "" {
mediators.removeLast()
}
}
print(mediators)
}
// ---------------------
// MARK : FUNCS
// ---------------------
(Deleted these too...)
// -----------------
// MARK : TABLE VIEW
// -----------------
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.header[section]
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 3
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("SET ROWS IN SECTION")
print(section)
if section == 0 {
print("SET SECTION 0 TO \(mediators.count)")
return mediators.count
} else if section == 1 {
print("SET SECTION 1 TO \(medianden.count)")
return medianden.count
} else if section == 2 {
print("SET SECTION 2 TO 1")
return 1
}
return 0
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
print("MAKE MY CELLS...")
let cell = UITableViewCell()
if indexPath.section == 0 {
cell.detailTextLabel?.text = mediators[indexPath.row]
return cell
} else if indexPath.section == 1 {
cell.textLabel!.text = medianden[indexPath.row]
return cell
} else if indexPath.section == 2 {
let textCell = FSTextViewTableViewCell()
return textCell
//textCell.textView.text = sessions.workingOnSession["Description"] as? String
}
return cell
}
}
thanks to everyone!!! :-) and sorry for my bad english...
Fabian

The error message is pretty clear.
uncaught exception 'NSRangeException',
reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'
So you have an array with elements [0..1] inclusive and you're accessing element [2]. There are only so many arrays in your program: mediators, medianden, self.header. Try doing some more debugging to see which one is at fault.
It seems like you are assuming that mediators has 2 elements and mediaden has 4, and this is hard-coded. It's better to not sprinkle these dependencies throughout the code; instead, set the size once, and make the remainder of the code work independently of the size (with for loops etc).

Related

how to remove the cell from uitableview cell

Im trying to dynamically arranging table view when user select "type 3". It works when user select "type 3", "type 3-1" would be added in the tableview. However the program crashed when user select other than type3-1. I dont know how can I execute the "rows.remove(at:2)" before the override function is called. Any suggestion would appreciate!
class GuestViewController: UITableViewController {
var rows:[[[String:Any]]] = [[["type":RowType.DetailContent,
"subType":DCType.DCRightContent,
"name":CPFFields.CID,
"content":"9637"],
["type":RowType.DetailContent,
"subType":DCType.DCInput,
"name":CPFFields.VISIA]],
[["type":RowType.DetailTextView,
"CPFType":CPFFields.UV,
"title":CPFFields.preferenceTitle]],
[["type":RowType.DetailContent,
"subType":DCType.DCSelection,
"name":CPFFields.Phototherapy,
"title":CPFFields.anestheticTitle],
["type":RowType.DetailTextView,
"CPFType":CPFFields.Phototherapy,
"title":CPFFields.preferenceTitle]],
]
var isNewGuestSelected : Bool = false
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rows[section].count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = rows[indexPath.section][indexPath.row]
let type = item["type"] as! RowType
if type == RowType.DetailContent
{
let cell = tableView.dequeueReusableCell(withIdentifier: "DetailNameCell", for: indexPath) as! DetailContentCell
let cpfType = item["name"] as? CPFFields ?? .Customer
cell.name.text = CPFFields.localizedString(from: cpfType)
if let field = item["title"] as? CPFFields
{
cell.name.text = CPFFields.localizedString(from: field)
}
cell.moreSlectionLeftSpace = true
var content:String? = ""
cell.type = cpfType
switch cpfType {
case .CID:
content = (profile?.birthDate.dateFromDateString?.stringForPaitentId ?? "") + (profile?.name ?? "")
case .CT:
content = ""
if let profile = profile
{
content = CPFCustomerType.localizedString(from: profile.type)
//New Guest
if(content == CPFCustomerType.type1.rawValue){
rows[0].insert(["type":RowType.DetailContent,
"subType":DCType.DCRightContent,
"name":CPFFields.CID,
"content":"9637"], at: 1)
isNewGuestSelected = true
} else{
if isNewGuestSelected == true{
rows[0].remove(at: 1)
isNewGuestSelected = false
}
}
}
let subType = item["subType"] as! DCType
cell.setcontentType(type: subType, content: content)
return cell
}
I expected not to see "rows[0][2]" after running "rows[0].remove(at:1)".
However the log is printing
rows[0][0]
rows[0][1]
rows[0][2]
then
it crashed at "let item = rows[indexPath.section][indexPath.row]"
because it is out of range
You are modifying your content while rendering, thus after numberOfRows:inSection: was called. Therefore the tableView is trying to access an element that no longer exists, since you removed it.
Your cycle:
→ number of rows 4
→ removed item, contents now has 3 items
→ cell for item 0
→ cell for item 1
→ cell for item 2
- cell for item 3 → crash
Consider replacing the logic you have here outside of the cellForRow method, and doing these operations before you reload your tableView.
You should use the tableView:cellForRow:atIndexPath strictly for dequeueing your cells and configuring them; not for modifying the underlying data store since funky things like you're experiencing now can happen.
If you provide a bit more context I can probably tell you where to place your code to fix this issue.
Actually, the solution is quite simple. I just added tableView.reloadData() after removing the array, and the UI can then be updated.
if isNewGuestSelected == true{
rows[0].remove(at: 1)
isNewGuestSelected = false
tableView.reloadData()
}

Search Bar crashing app when inputting characters

I have a UITableView that is populating locations and a Search Bar set as the header of that UITableView.
Whenever certain characters are entered, or a certain amount of characters are entered, the app crashes, giving me no error code.
Sometimes the app crashes after inputting one character, maybe 2 characters, maybe 3, or maybe 4. There seems to be no apparent reason behind the crashing.
The search function properly searches and populates the filtered results, but for no apparent reason, crashes if a seemingly arbitrary amount of characters are inputted.
I have tried using the exception breakpoint tool already, and it is providing me with no new information. I think it has something to do with if there are no search results.
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search Locations..."
navigationItem.hidesSearchBarWhenScrolling = false
searchController.hidesNavigationBarDuringPresentation = false
locationTableView.tableHeaderView = searchController.searchBar
searchController.searchBar.sizeToFit()
searchController.searchBar.showsCancelButton = false
searchController.searchBar.barTintColor = UIColor.white
filteredData = locationList
// Sets this view controller as presenting view controller for the search interface
definesPresentationContext = true
locationList = createArray()
// Reload the table
let range = NSMakeRange(0, self.locationTableView.numberOfSections)
let sections = NSIndexSet(indexesIn: range)
self.locationTableView.reloadSections(sections as IndexSet, with: .fade)
}
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchController.searchBar.text!)
}
func searchBarIsEmpty() -> Bool {
// Returns true if the text is empty or nil
return searchController.searchBar.text?.isEmpty ?? true
}
func filterContentForSearchText(_ searchText: String) {
filteredData = locationList.filter({( locationName : Location) -> Bool in
return locationName.locationName.lowercased().contains(searchText.lowercased())
})
let range = NSMakeRange(0, self.locationTableView.numberOfSections)
let sections = NSIndexSet(indexesIn: range)
self.locationTableView.reloadSections(sections as IndexSet, with: .fade)
}
func isFiltering() -> Bool {
return searchController.isActive && !searchBarIsEmpty()
}
func locationTableView(_ locationTableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering() {
return filteredData.count
}
return locationList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let locationCell = locationTableView.dequeueReusableCell(withIdentifier: "locationCell", for: indexPath) as! locationCell
let location: Location
if isFiltering() {
location = filteredData[indexPath.row]
} else {
location = locationList[indexPath.row]
}
locationCell.setLocation(location: location)
return locationCell
}
The expected result is that the UITableView should populate with filtered results. Instead, it populates them and crashes if too many characters are inputted (usually 1-4 characters).
EDIT 1: I have found through debugging the error:
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
appears on Line 2 on this block of code:
if isFiltering() {
location = filteredData[indexPath.row]
} else {
location = locationList[indexPath.row]
}
EDIT 2: This is the tutorial I used.
https://www.raywenderlich.com/472-uisearchcontroller-tutorial-getting-started
Seems like you are expecting the tableView to provide YOU with the number of sections... it is supposed to be driven by your own datasource.
Since you are not providing a numberOfSections in your data source I'm assuming it is 1. If all of your rows are in 1 section, all of the nifty reloading you are doing could be greatly simplified.
I suggest you read up on UITableView dataSource protocol at https://developer.apple.com/documentation/uikit/uitableviewdatasource
Reviewing the tutorial you are reading, it seems it is using a reloadData() which forces the tableView to ignore previous number of rows and reload its content with a new number of rows. And based on your findings so far, I would assume that is part of the root cause, with the tableview wrongly assuming a pre-determined number of rows and attempting to retrieve cells that are no longer within range.

Read the value of an attribute of a Core Data entity

I'm trying to read the value of an attribute (called 'name') of an entity (called 'List'), stored in a Core Data database. The issue that I have is that it says that the value of that attribute is nil, which should be a String.
My code for retrieving all List-entities is as follows:
container?.performBackgroundTask { [weak self] context in
self?.wordLists = try! List.all(in: context)
DispatchQueue.main.async(execute: {
print("Main queue available, reloading tableview.")
self?.wordListSelector.reloadData()
})
}
class func all(in context: NSManagedObjectContext) throws -> [List] {
let listRequest: NSFetchRequest<List> = List.fetchRequest()
do {
let list = try context.fetch(listRequest)
print(list)
return list
} catch {
print("error")
throw error
}
}
This prints:
[<__Words.List: 0x6000000937e0> (entity: List; id: 0xd00000000004000c <x-coredata://999D0158-64BD-44FD-A0B1-AB4EC03B9386/List/p1> ; data: <fault>), <__Words.List: 0x600000093a60> (entity: List; id: 0xd00000000008000c <x-coredata://999D0158-64BD-44FD-A0B1-AB4EC03B9386/List/p2> ; data: <fault>), <__Words.List: 0x600000093ab0> (entity: List; id: 0xd0000000000c000c <x-coredata://999D0158-64BD-44FD-A0B1-AB4EC03B9386/List/p3> ; data: <fault>)]
Which shows me that there should be 3 lists in the database, which is as expected.
I have created a variable like this:
var wordLists: [List] = [] {
didSet {
print("Detected wordList update, waiting for main queue.")
DispatchQueue.main.async(execute: {
print("Main queue available, reloading tableview.")
self.wordListSelector.reloadData()
})
}
}
This variable holds the List-entities that I retrieved by calling that all() function which I mentioned previously.
The following two methods will fill my TableView:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("numberOfRowsInSection: \(wordLists.count).")
return wordLists.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "CategoryCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
print("cellForRowAt: \(indexPath.row) has name \(wordLists[indexPath.row].name).")
cell.textLabel?.text = wordLists[indexPath.row].name
return cell
}
This prints the following:
Detected wordList update, waiting for main queue.
Main queue available, reloading tableview.
numberOfRowsInSection: 3.
cellForRowAt: 0 has name nil.
cellForRowAt: 1 has name nil.
cellForRowAt: 2 has name nil.
Why is the name nil? Is this because the data is still 'faulted'? By viewing topics online I thought that Core Data automaticly unfaulted its data when you try to access it. What am I doing wrong?
Edit:
If I change the didset to the following:
var wordLists: [List] = [] {
didSet {
print("Wordlist was updated.")
for wordList in wordLists {
print(wordList)
print(wordList.name)
}
}
}
It does print the names (Optional("nameofitem1")). In the cellForRowAt it still prints 'nil'.
What you do looks like a good job for the NSFetchedResultsController. That will also help with the threading you might mix up as suggested in the comments. There is really good documentation on the NSFetchedResultsController

Invalid update: invalid number of items in section 0.

Recently I got the following error:
Fatal Exception: NSInternalInconsistencyException
Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (13) must
be equal to the number of items contained in that section before the
update (12), plus or minus the number of items inserted or deleted
from that section (0 inserted, 0 deleted) and plus or minus the number
of items moved into or out of that section (0 moved in, 0 moved out).
The error occurs in the following code in my tvOS client:
let removedIndexPaths = removedIndexes.map({ IndexPath(row: $0, section: 0) })
let addedIndexPaths = addedIndexes.map({ IndexPath(row: $0, section: 0) })
let updatedIndexPaths = updatedIndexes.map({ IndexPath(row: $0, section: 0) })
self.collectionView?.performBatchUpdates({
self.collectionView?.deleteItems(at: removedIndexPaths)
self.collectionView?.insertItems(at: addedIndexPaths)
}, completion: { _ in
guard let collectionView = self.collectionView else {
return
}
for indexPath in updatedIndexPaths {
if let myCell = collectionView.cellForItem(at: indexPath) as? MyCollectionViewCell {
let item = self.dataManager.items[indexPath.row]
myCell.updateUI(item)
}
}
let collectionViewLayout = self.collectionViewLayoutForNumberOfItems(self.dataManager.items.count)
if collectionViewLayout.itemSize != self.collectionFlowLayout.itemSize {
collectionView.setCollectionViewLayout(collectionViewLayout, animated: false)
}
})
I am only using one section in my collection view:
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
I have checked out couple of posts on the same topic, but they have not solved my problem, my guess is that the problem is in the following two lines, but I am not sure:
self.collectionView?.deleteItems(at: removedIndexPaths)
self.collectionView?.insertItems(at: addedIndexPaths)
Please help.
The call to insertItems(at:) and deleteItems(at:) must be accompanied with change in the datasource as well.
So, before calling these APIs, you would want to change your datasource, i.e. add objects into it before calling insertItems and remove objects from it before calling deleteItems
Found a very nice article about UICollectionView invalid number of items crash problem - https://fangpenlin.com/posts/2016/04/29/uicollectionview-invalid-number-of-items-crash-issue/
The item count returned by collectionView(_:numberOfItemsInSection:) should be sync with the updates made inside the closure. With this idea in mind, it’s easy to solve, just add a property as the item count and update it inside performBatchUpdates closure
func updateItems(updates: [ItemUpdate]) {
collectionView.performBatchUpdates({
for update in updates {
switch update {
case .Add(let index):
collectionView.insertItemsAtIndexPaths([NSIndexPath(forItem: index, inSection: 0)])
itemCount += 1
case .Delete(let index):
collectionView.deleteItemsAtIndexPaths([NSIndexPath(forItem: index, inSection: 0)])
itemCount -= 1
}
}
}, completion: nil)
}
and for the collectionView(_:numberOfItemsInSection:), instead of returning items.count, we return the property which is manually maintained by performBatchUpdates closure.
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return itemCount
}

How do I select column and row from a NSTableView?

I am writing a NSTableView based view. And I try to use:
let selectedRowNumber = tableViewMesos.selectedRow and
let selectedColumnNumber = tableViewMesos.selectedColumn to identify the textField that is in the cell View that I want to save into my array:
taulaDeConsumsMesos[selectedColumnNumber].filesConsum![selectedRowNumber] = sender.doubleValue
But a problem happens here, because the return value of selectedColumnNumber is -1 which is the value of non-selected Column for any row selected of any column.
But the selectedRowNumberreturns the index of the row properly.
So the program crashes when finishing the edition of a textfield in a cell. Because the selectedColumnNumber is -1 out of the limits of the array. See the code below to identify where this happens exactly.
Could you help me? I lost too many hours on this..
I'm new on OSX programing I hope you could help me.
The app layout
Here is the completed code:
class BalancGlobalViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate{
#IBOutlet weak var tableViewMesos: NSTableView!
let mesos = ColumnaDeConsumsMes() // contains an array of doubles called .filesConsum that represent the rows that I want to show
let consum = ColumnaDeConsumsMes()
var taulaDeConsumsMesos = [ColumnaDeConsumsMes]() // array of arrays that represent the columns with rows in it that will be the tableView data
func setUpMesos(){ // init table columns
mesos.filesConsum = [1, 2, 3, 4, 5, 6, 7, 8 , 9, 10, 11, 12, nil]
consum.filesConsum = [100, 100, 100, nil, nil, nil, nil, nil , nil, nil, nil, nil, nil]
taulaDeConsumsMesos.append(mesos)
taulaDeConsumsMesos.append(consum)
}
func setUpGradient(){
...not important here
}
override func viewDidLoad() {
super.viewDidLoad()
setUpMesos()
tableViewMesos.setDelegate(self)
tableViewMesos.setDataSource(self)
setUpGradient()
}
override func viewDidLayout() {
super.viewDidLayout()
setUpGradient()
}
#IBAction func alEditarUnTextFieldDeConsum(sender: NSTextField) { // this is the IBAction of the TextField of the Consum Column (see the image)
let selectedRowNumber = tableViewMesos.selectedRow
let selectedColumnNumber = tableViewMesos.selectedColumn
print("SelectedRowNumber! = \(selectedRowNumber)")
print("SelectedColumnNumber! = \(selectedColumnNumber)")
// the selectedColumnNumber return always -1 except when clicking the header of the column, then return 0 or 1 depending on the column. But it should return 0 even when the user edit a row of the index 0 column !!!
let numero = tableViewMesos.numberOfColumns
print("numero de columnes: \(numero)") // returns 2
if selectedRowNumber != -1 { //-1 is returned when no row is selected in the TableView
taulaDeConsumsMesos[selectedColumnNumber].filesConsum![selectedRowNumber] = sender.doubleValue //try to save here the value edited by the user on my array of arrays of doubles
//here the program crashes! selectedColumnNumber = -1
print("Sender! = \(sender.doubleValue)") // the sender returns the information correctly
//print("SelectedRowNumber! = \(selectedRowNumber)") // this works too!
reloadTaula()
}
}
func tableViewSelectionDidChange(notification: NSNotification) {
print("columna: \(tableViewMesos.selectedColumn)")
print("row: \(tableViewMesos.selectedRow)")
print("tag: \(tableViewMesos.selectedTag())")
}
// the tableViewMesos.selectedColumn return always -1 except when clicking the header of the colum, then return 0 or 1 depending on the column. But i should return 0 even when I edit a row of the index 0 column !!!
func reloadTaula() {
tableViewMesos.reloadData()
}
// MARK: - Datasource
func numberOfRowsInTableView(tableView: NSTableView) -> Int {
if (tableView.identifier == "TaulaMesos") {
return 13
}
}
// MARK: - Delegate
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
var valorCela: Double
var cellIdentifier:String=""
if tableColumn == tableView.tableColumns[0] {
if taulaDeConsumsMesos[0].filesConsum![row] == nil {
return nil
}else {
valorCela = taulaDeConsumsMesos[0].filesConsum![row]!
cellIdentifier = "MesCellID"
}
} else {
if taulaDeConsumsMesos[1].filesConsum![row] == nil {
return nil
}else {
valorCela = taulaDeConsumsMesos[1].filesConsum![row]!
cellIdentifier = "ConsumCellID"
}
}
if let cell = tableView.makeViewWithIdentifier(cellIdentifier, owner: nil ) as? NSTableCellView {
cell.textField?.doubleValue = valorCela
return cell
}
return nil
}
}
Table views do not support selecting individual cells. Both styles of table view support selecting rows. Old-style NSCell-based table views support selecting columns, but modern view-based table views no longer support selecting columns. But it would never have been the case that both selectedRow and selectedColumn were valid at the same time.
From the 10.7 AppKit release notes, where view-based table views were introduced:
Note that the View Based TableView does not support column selection.
To get the row and column of the cell whose text field sent the action, you should use tableViewMesos.rowForView(sender) and tableViewMesos.columnForView(sender). Note, especially, that there's nothing guaranteeing that the text field that's sending the action method is in a selected row. So, don't think of it as selected. It's just the one that's been manipulated by the user.