NSTableView crashs when changing data with selected row - objective-c

In my cocoa app i have 2 NSTableViews. The AppDelegate is the datasource for both of them. I want to change the data of the second TableView depending on the selected row of the first TableView. For that i use an NSDictionary. I add an array for every entry in the data array of Table1.
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView{
if([table1 selectedRow] != -1 && [[aTableView identifier] isEqualToString:#"table2"]){
return [[dict objectForKey:[arrForTable1 objectAtIndex:[table1 selectedRow]]] count];
}
if([[aTableView identifier] isEqualToString:#"table1"]) return [arrForTable1 count];
return 0;
}
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex{
if([table1 selectedRow] != -1 && [[aTableView identifier] isEqualToString:#"table2"]){
return [[dict objectForKey:[arrForTable1 objectAtIndex:[table1 selectedRow]]] objectAtIndex:rowIndex];
}
if([[aTableView identifier] isEqualToString:#"table1"]){
return [arrForTable1 objectAtIndex:rowIndex];
}
return nil;
}
This code crashes when in table2 a row is selected and after that a switch in the table1 to a row with an empty array.
How can i fix it? I tried to [table2 deselectAll:self] in the - selectionShouldChangeInTableView: method but it doesn't work. It works to trigger the deselectAll: method with an button and that was ok.

Sounds like you might need to call [table2 reloadData] when your selection in table1 changes.

Related

Objective C- numberOfRowsInSection method is not called properly in UITableView

In my table view, I have 7 section. Each section count is 3 initially.
When I launch my app, numberOfRowsInSection method is calling from last section count(6) but data are displayed in all sections properly.
When I reload my table after insert event numberOfRowsInSection is calling again from 6 not from 0 so how to resolve it?
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSMutableArray *arrayOfItems = [workScheduleArray objectAtIndex:section];
return arrayOfItems.count;
}
-(void)insertNewDutyTimeRow:(UIButton *)addNewRowBtn atIndexPath:(NSIndexPath *)addNewDutyIndex
{
insertNewRow = [[NSMutableArray alloc]init];
[insertNewRow addObject:#"Start Time"];
[insertNewRow addObject:#"End time"];
[insertNewRow addObject:#"Service type"];
newArray = [workScheduleArray objectAtIndex:addNewDutyIndex.section];
[newArray insertObject:insertNewRow atIndex:addNewDutyIndex.row+1];
NSLog(#"WorkSchedule array:%#",workScheduleArray[addNewDutyIndex.section]);
[self.personalTable reloadData];
}
Your numberOfRowsInSection is generic, it returns the same number for all your sections. it begins by section 0 and ends by your last section (in your case its 6). So this is why you must specify all the cases. For example you can do this:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
switch (section) {
case 0:
NSMutableArray *arrayOfItems = [workScheduleArray objectAtIndex:section];
return arrayOfItems.count;
{...}
}
}

Selecting Objects from NSArray for further deleting with an IBAction

So I am trying to select objects from my array, to be able to delete them when I do an IBAction. I tried:
checking if the item is Selected:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (self.editEnabled) {
RDNote *selectedNote = [self.notes objectAtIndex:indexPath.row];
if (selectedNote.isSelected) {
selectedNote.selected = NO;
for (NSIndexPath *indexPathFromArray in self.indexPathsOfSelectedCells) {
if (indexPathFromArray.row == indexPath.row) {
[self.mutableCopy removeObject:indexPathFromArray];
}
}
} else {
selectedNote.selected = YES;
[self.indexPathsOfSelectedCells addObject:indexPath];
}
[self.collectionView reloadData];
IBAction:
- (IBAction)didTapTrashBarButton:(id)sender {
NSMutableArray *mutableNotes = [NSMutableArray arrayWithArray:self.notes];
for (NSIndexPath *indexPath in self.indexPathsOfSelectedCells) {
[mutableNotes removeObjectAtIndex:indexPath.row];
}
self.notes = [NSArray arrayWithArray:mutableNotes];
[self.collectionView performBatchUpdates:^{
[self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithArray:self.indexPathsOfSelectedCells]];
} completion:^(BOOL finished) {
self.indexPathsOfSelectedCells = nil;
[self activateEditMode:NO];
[self saveDataToFile:self.notes];
}];
}
But I am having issues with indexes e.g.:(some times shows me the Error that Object index 2 is not between [0..1]), and there are bugs while selecting multiple Object and Deleting them.
Please help me with an advice for some other method that I can use, a Code will be perfect! Thanks!
This problem arrises because:
Let say you have five objects in array 1,2,3,4,5
you are running a loop for removing object based on indexpath which are selected rows. Now your indexpath contains 1st row and 3rd row.
while executing it for the first time you will remove object 1. Now 2,3,4,5 will left in the array. Now second time your indexpath.row is 3rd. It will remove third object which is 4 but in actual array it was 3.
Your code is crashing sometimes because if you have selected first row and last row. In this case i have selected 1 and 5. Now my indexpaths array will say I have to delect objectsAtIndexes 1 and 5.
While executing the loop I'll remove object at Index 1. Now i will be left with 2,3,4,5. On second iteration it will say remove objectAtIndex 5 where as Index 5 doesn't exist because now we have 4 elements in array.
In such cases best way of doing this is try removing elements in array from the end like remove 5th element first and then go for other one. Run your loop in reverse order.
NSInteger i = [self.indexPathsOfSelectedCells count]-1;
while (i > 0){
NSIndexPath *indexPath = [self.indexPathsOfSelectedCells objectAtIndex:i];
[mutableNotes removeObjectAtIndex:indexPath.row];
i--;
}

dynamic UIPickerView race condition?

I have a two level UIPickerView which means if user selects the first component, the second component will update its data.
The data should be look like this:
self.type = #[#"fruit", #"airlines"];
self.data = #[#[#"Apple", #"Orange"], #[#"Delta", #"United", #"American"]];
datasource and delegate method:
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 2;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
if(component == 0){
return self.data.count;
}else if(component == 1){
NSInteger row1 = [pickerView selectedRowInComponent:0];
return [self.data[row1] count];
}
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
if(component == 0){
[pickerView reloadComponent:1];
}
NSInteger row1 = [pickerView selectedRowInComponent:0];
NSInteger row2 = [pickerView selectedRowInComponent:1];
// here may crash, some time
NSLog(#"data: %#", self.data[row1][row2]);
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{
if(component == 0){
return self.type[row];
}else if(component == 1){
NSInteger row1 = [pickerView selectedRowInComponent:0];
// here may also crash
return self.data[row1][row];
}
}
There are several places which may cause crashes(NSRangeException) in the above code as commented. However, they rarely happened. I haven't gotten this crash yet, but my users had according to Crashlytics report.
I add some codes to validate row1 and row2 before accessing self.data[row1][row2] and send to my server if fail. I found that the value of [pickerView selectedRowInComponent:0] may be incorrect some time. For example, when user changes the first component from "fruit" to "airlines" and select some item in second component, the value of selectedRowInComponent:0 may still be 0(index of "fruit").
I guess this is caused by race condition but how can I solve this?
It will crash if a component represents an array with three members (airlines) AND the selected row in that component is the last member and then you flip the component over to represent the two member array (fruit). The pickerview has retained this variable (selectedrowincomponent:x) and now wants to represent member #2 on a two member array (has members #0-1 only) -> NSRangeException. You need to alter the selectedRow in that component before you flip it to a different array with reload ..
I personally don't like using one picker to do two things, it's messy IMHO just write a different class to be delegate/dataSource for each, one can be a subclass of the other, then lazy load em as needed

TitleForHeaderInSection throwing EXC_BAD_ACCESS

I am trying to create a generic UITableView in my iPhone app.
I have a UITableView which populates the data using an array via a SELECT query loop.
I add the data into my array and populate the array in cellForRowAtIndexPath:.
I get the section header using that array and by using a sort method, I put the section headers in Array1.
I would like to have titleForHeaderInSection: work by having section 0 be a static header name and sections 1 and later become generic, meaning the header name will come from Array1.
I am not sure how can I create that logic since the app always throws EXC_BAD_ACCESS with the code below.
My logic: I keep the count of the array in an int and see if the value is greater than 0. If it is, I add the section header for and objectAtIndex:0, otherwise I use the static one. But when the count gets to 2, for section 2 and objectAtIndex:1, it breaks and throws EXC_BAD_ACCESS.
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
int value = [[self Array1] count];
if(section == 0)
return #"Countries";
if (value > 0) {
if (section == value){
return [[self Array1] objectAtIndex:section - 1];
}
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
int count = [[self Array1] count];
return count + 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
int value = [[self Array1] count];
if (section == 0) {
return [self.Array count];
}
if (value > 0) {
if (section == [[self Array1] count]) {
NSString *initialLetter = [[self Array1] objectAtIndex:section - 1];
// get the array of elements that begin with that letter
NSArray *elementsWithInitialLetter = [self elementsWithInitialLetter:initialLetter];
// return the count
return [elementsWithInitialLetter count];
}
}
}
Looks like you're just missing a retain on the iVar backing the Array1 method. declare the array as a property:
#property (nonatomic, retain) NSArray* datastore;
Then cache the value you're referring to in the Array1 method in this method (probably in viewDidLoad).
self.datastore = [self Array1];
Then replace all remaining references to [self Array1] with self.datastore. Build run and see if it still crashes. (Don't forget to set self.datastore = nil in your dealloc!

NSOutlineView indentation issue

I'm using an NSOutlineView object to represent a file structure and am finding that it will not correctly indent any children which are expandable, though it will indent children that aren't.
Here's a picture to show what I mean:
In this example, "AnotherFolder" is a child of "Folder2" yet it does not indent in line with the other indented files. Curiously enough, the child "AnotherFile.java" of "AnotherFolder" does indent correctly (2 levels in).
I have tried setting properties such as "indentationFollowsCells" to no avail. This seems as though it should be very simple but I can't solve it.
Thanks!
Edit: Some extra information upon request:
I am using the NSOutlineViewDataSource protocol for the implementation, here is the code related to that:
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
return item;
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
NSMutableDictionary* dict;
if(item == nil) {
dict = fileTree;
} else {
dict = [((MyFile*) item) children];
}
NSArray* keys = [dict allKeys];
NSArray* sorted = [keys sortedArrayUsingSelector:#selector(compare:)];
NSString* key = [sorted objectAtIndex:index];
return [dict objectForKey:key];
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
return [[item children] count] > 0;
}
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
if(item == nil) {
return [fileTree count];
}
return [[item children] count];
}
Try changing your outline view from a Source outline view to a normal one.
I ran into this right now, and found it a bit strange that nine years after this post, the problem still persists.
This behaviour is baked into the Source style: the first row of the standard content is aligned with the header cell rather than indented, so everything is shifted over by one level.
If you use header cells, you want this behaviour, and everything is fine. If you don't want to use header cells, not using a SourceList is your only option.