Search code examples
iosobjective-cuitableviewcellsections

Index out of bounds when trying to delete a row (cell) from a section and update table (Xcode,Objective-c)


I have a tableview grouped by sections, and sorted by dates, every section is basically a day, every row is an appointment/event on that day. I get my events from an nsmutablearray, and they have a "startDate" property by which i sort the table. I'm having trouble trying to delete a row from a sections.

-If i have 1 section with 5 rows , it's ok i can delete each of them individually and the last one will also delete the section.

-If i have more than 1 section , when i try to delete the last row in a section, it crashes.

Here is my code:

 @interface AgendaViewController ()
@property (nonatomic,strong) NSString *path;
@property (strong, nonatomic) NSMutableDictionary *sections;
@property (strong, nonatomic) NSMutableArray *sortedDays;
@property (strong, nonatomic) NSDateFormatter *sectionDateFormatter;
@property (strong, nonatomic) NSDateFormatter *cellDateFormatter;
@end

@implementation AgendaViewController
@synthesize events = _events;
-(IBAction)menuButtonTapped:(id)sender{
    [self.slidingViewController anchorTopViewToRightAnimated:YES];

}
-(IBAction)unwindToAgenda:(UIStoryboardSegue *)segue{
    AddAgendaEntryViewController *source = [segue sourceViewController];
    AgendaEntry *event = source.agendaEntry;
    if (event){
        [self.events addObject:event];
        [self saveList];
        [self createSections];
        [self.tableView reloadData];
    }

}

- (void)saveList
{
    [NSKeyedArchiver archiveRootObject:self.events toFile:self.path];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    self.path = [self.path stringByAppendingPathComponent:@"TodoList.txt"];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:self.path]) {
        self.events = [[NSMutableArray alloc] init];
    } else {
        self.events = [[NSKeyedUnarchiver unarchiveObjectWithFile:self.path] mutableCopy];
    }

    [self setMenuGesture];
    [self fetchEvents];


    [self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleSingleLine];
    self.tableView.separatorColor = [UIColor colorWithRed:224.0/255.0 green:224.0/255.0 blue:224.0/255.0 alpha:1.0f];

    [self createSections];
        //date formatteri

        self.sectionDateFormatter = [[NSDateFormatter alloc] init];
        [self.sectionDateFormatter setDateStyle:NSDateFormatterLongStyle];
        [self.sectionDateFormatter setTimeStyle:NSDateFormatterNoStyle];

        self.cellDateFormatter = [[NSDateFormatter alloc] init];
        [self.cellDateFormatter setDateStyle:NSDateFormatterNoStyle];
        [self.cellDateFormatter setTimeStyle:NSDateFormatterShortStyle];

    }


-(void)createSections{
    self.sections = nil;
    self.sections = [[NSMutableDictionary alloc]init];
    for (AgendaEntry *entry in self.events){
        NSDate *dateRepresentingThisDay = [self dateAtBeginningOfDayForDate:entry.startDate];
        NSMutableArray *eventsOnThisDay = [self.sections objectForKey:dateRepresentingThisDay];
        if(eventsOnThisDay == nil){
            eventsOnThisDay = [NSMutableArray array];
            [self.sections setObject:eventsOnThisDay forKey:dateRepresentingThisDay];

        }
        [eventsOnThisDay addObject:entry ];

        NSArray *unsortedDays =  [self.sections allKeys];

        self.sortedDays = [[unsortedDays sortedArrayUsingSelector:@selector(compare:)] mutableCopy];}

        NSLog(@"NUMBER OF SECTIONS IN TABLEVIEW: %lu",(unsigned long)[self.sections count]);
   // [self.tableView reloadData];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [self.sections count];
}







- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];

}


-(void)fetchEvents{
    //Apel catre webserver pentru eventuri.
}

-(void)setEvents:(NSMutableArray *)events{
    _events = events;
    [self.tableView reloadData];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - Table view data source

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSDate *dateRepresentingThisDay = [self.sortedDays objectAtIndex:section];
    NSArray *eventsOnThisDay = [self.sections objectForKey:dateRepresentingThisDay];
    return [eventsOnThisDay count];
}

/*- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    NSDate *dateRepresentingThisDay = [self.sortedDays objectAtIndex:section];
    NSString *title = [self.sectionDateFormatter stringFromDate:dateRepresentingThisDay];
    return title;
}*/
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger) section{

    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, 15)];
    UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(5, 5, self.view.frame.size.width, 15)];
    lbl.backgroundColor = [UIColor clearColor];
    lbl.textAlignment = NSTextAlignmentCenter;
    lbl.font = [UIFont systemFontOfSize:12];

    NSDate *dateRepresentingThisDay = [self.sortedDays objectAtIndex:section];
    NSString *title = [self.sectionDateFormatter stringFromDate:dateRepresentingThisDay];
    lbl.text = title;

    [view addSubview:lbl];
    [view setBackgroundColor:[UIColor colorWithRed:166/255.0 green:177/255.0 blue:186/255.0 alpha:0.3]]; //your background color...
    return view;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Event Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    // Configure the cell..
    //AgendaEntry *event = [self.events objectAtIndex:indexPath.row];

    NSDate *dateRepresentingThisDay = [self.sortedDays objectAtIndex:indexPath.section];
    NSArray *eventsOnThisDay = [self.sections objectForKey:dateRepresentingThisDay];
    AgendaEntry *event = [eventsOnThisDay objectAtIndex:indexPath.row];

  UIImageView *mappin = (UIImageView *)[cell viewWithTag:4] ;
    mappin.image  = [UIImage imageNamed:@"map_pin"];

    UILabel *companyName = (UILabel *)[cell viewWithTag:1];
    companyName.text = event.companyName;

    UILabel *eventType = (UILabel *) [cell viewWithTag:3];
    eventType.text = event.eventType;

    UILabel *dateOfEvent = (UILabel *) [cell viewWithTag:2];

    dateOfEvent.text = [self.cellDateFormatter stringFromDate:event.startDate];
    return cell;
}


- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        // Delete the row from the data source

        [self.events removeObjectAtIndex:indexPath.row];
        [self createSections];

        [self saveList];

        [tableView beginUpdates];
        NSDate *dateRepresentingThisDay = [self.sortedDays objectAtIndex:indexPath.section];
        NSArray *eventsOnThisDay = [self.sections objectForKey:dateRepresentingThisDay];
        if ([eventsOnThisDay count] > 0){

        [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
        }else{
        [tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section]
                 withRowAnimation:UITableViewRowAnimationFade];


}
        [tableView endUpdates];
    }
    [self.tableView reloadData];
}

-(NSMutableArray *)sortedDays{
    if (!_sortedDays) _sortedDays = [[NSMutableArray alloc]init];
    return _sortedDays;
}


-(void)setMenuGesture{
    id<ECSlidingViewControllerDelegate> transition = self.zoomAnimationController ;
    self.slidingViewController.delegate = transition;
    self.slidingViewController.topViewAnchoredGesture = ECSlidingViewControllerAnchoredGestureTapping | ECSlidingViewControllerAnchoredGesturePanning;
    self.slidingViewController.customAnchoredGestures = @[];

  //  [self.navigationController.view addGestureRecognizer:self.slidingViewController.panGesture];
}

- (ZoomAnimationController *)zoomAnimationController {
    if (_zoomAnimationController) return _zoomAnimationController;

    _zoomAnimationController = [[ZoomAnimationController alloc] init];

    return _zoomAnimationController;
}

-(NSMutableArray *)events{
    [self sortEvents];
    return _events;
}
/*- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 30;
}*/

- (NSDate *)dateAtBeginningOfDayForDate:(NSDate *)inputDate
{
    // Use the user's current calendar and time zone
    NSCalendar *calendar = [NSCalendar currentCalendar];
    NSTimeZone *timeZone = [NSTimeZone systemTimeZone];
    [calendar setTimeZone:timeZone];

    // Selectively convert the date components (year, month, day) of the input date
    NSDateComponents *dateComps = [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:inputDate];

    // Set the time components manually
    [dateComps setHour:0];
    [dateComps setMinute:0];
    [dateComps setSecond:0];

    // Convert back
    NSDate *beginningOfDay = [calendar dateFromComponents:dateComps];
    return beginningOfDay;
}


-(void)sortEvents{
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"startDate" ascending:TRUE];
    [_events sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];

}
@end

The error i get:

Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 1 beyond bounds [0 .. 0]'`


Solution

  • It seems like the logic in deleteForRowAtIndexPath causes the issue. What you are doing is just deleting from self.events based on indexPath.row. But your self.events and the eventsOnThisDay array may not be holding same index when you have more than 1 section.

    I recommend you to delete the item in same way as you are loading it to tableView. For example, in deleteRowAtIndexPath,

    NSDate *dateRepresentingThisDay = [self.sortedDays objectAtIndex:indexPath.section];
    NSArray *eventsOnThisDay = [self.sections objectForKey:dateRepresentingThisDay];
    [eventsOnThisDay removeObjectAtIndex:indexPath.row];
    //If there are no more events on this day, then just delete the section also.
    if([eventsOnThisDay count] == 0){
       [self.sections removeObjectForKey:dateRepresentingThisDay];
       [self.sortedDays removeObjectAtIndex:indexPath.section];
    }
    [self.tableView reloadData];
    //You should also update your self.events list also accordingly.
    

    Hope this helps.