Search code examples
iphoneobjective-ciosuitableviewmbprogresshud

alternate between 2 UITableViews application freeze


I am working with a ViewController, that delegates & datasources 2 different tableviews. according to what the user wishes to see (toggling happens with 'touchesBegan' for certain areas in the view.

The viewcontroller is one of 3 subcontrollers of a tabbed application.

The alternation between the tableviews works just fin. i get the right data, layout, etc and i can change between them as often as i want to.

the first tableview does not contain difficult data so it is loaded in a few milliseconds.

the second tableview2 contains data that takes about 2-3 seconds to load (depending on the amount of entities in coredata). while this data loads & the tableview2 redraws i show a MBProgressHUD. this works as well.

Problem: if i interact with the tabbarcontroller while table2 is loading & the hud is spinning, the app 'freezes' the hud runs infinitly long and any userinteractino is disabled. as well the clicked tab would not open.

CODE: touchesBegan FUnction

    -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{


    if (isLoadingAllMonth) {
        return;
    }
    [[MBProgressHUD HUDForView:self.view]removeFromSuperview];


    UITouch *touch = [[event allTouches]anyObject];
    int viewTag = touch.view.tag;                           // 1 for thisMonth, 2 for allMonth


    if (viewTag == 1) {
        [allMonthButtonView setAlpha:.8];
        [thisMonthButtonView setAlpha:1];
        if (allMonthIsActive == NO) {
            return;
        }
        else{
            [self reloadThisMonth];
            [allMonthTable removeFromSuperview];
            [self.view addSubview:thisMonthTable];

            allMonthIsActive = NO;
        }
    }

    else if(viewTag == 2){

        if (!allMonthIsActive) {
            allMonthIsActive = YES;



            if (isLoadingAllMonth) {
                return;
            }

            [self.view addSubview:HUD];
            [allMonthTable setFrame:CGRectMake(4, 64, 312, 343)];
            [allMonthTable setBackgroundColor:[self grayColor]];
            [self.view addSubview:allMonthTable];

            HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
                isLoadingAllMonth = YES;

                [self reloadAllMonth];

                dispatch_async(dispatch_get_main_queue(), ^(void) {

                    [self.allMonthTable reloadData];            // Or reload tableView
                    [HUD hide:YES];
                });

            });
        }
    }

}

CODE: reloadAllMonth

-(void)reloadAllMonth{
    UIFont *titleFont = [UIFont fontWithName:@"Cochin" size:14.0];
    UIFont *detailFont = [UIFont fontWithName:@"Cochin" size:18.0];
    [[MBProgressHUD HUDForView:self.view] setLabelFont:titleFont];
    [[MBProgressHUD HUDForView:self.view] setDetailsLabelFont:detailFont];

    [[MBProgressHUD HUDForView:self.view] setLabelText:@"Please wait"];


    [[MBProgressHUD HUDForView:self.view] setDetailsLabelText:@"Cleaning Cache"];
    if (allMonthData.count != 0) {
        for (NSMutableArray *arr in allMonthData) {
            [arr removeAllObjects];
        }

        [allMonthData removeAllObjects]; 

        for (NSMutableArray *arr in allMonthDataNumbers) {
            [arr removeAllObjects];
        }

        [allMonthDataNumbers removeAllObjects]; 

    }

    NSDate *rootDate = [NSDate dateWithTimeIntervalSinceReferenceDate:(10*365*24*60*60)];
    int rootMonth = [[dataHandler getMonthNumber:rootDate] intValue];

    NSMutableArray *allExp = [[NSMutableArray alloc]init];
    NSNumber *currentMonth = [dataHandler getMonthNumber:[NSDate date]];
    NSMutableArray *temp = [[NSMutableArray alloc]init ];
    NSNumber *tempMonth = [NSNumber numberWithInt:(currentMonth.intValue+1)];

    [temp removeAllObjects];
    [[MBProgressHUD HUDForView:self.view] setDetailsLabelText:@"Updating Data..."];

    while (tempMonth.intValue > rootMonth) {
        tempMonth = [NSNumber numberWithInt:(tempMonth.intValue-1)];
        temp = [NSMutableArray arrayWithArray:[dataHandler fetchAllExpensesForMonth:tempMonth]] ;
        if (temp.count != 0) {
            [allExp addObject:temp];
        }

    }
    allMonthData = allExp;

    if (!allMonthDataNumbers) {
        allMonthDataNumbers = [[NSMutableArray alloc]init];

    }
    [[MBProgressHUD HUDForView:self.view] setDetailsLabelText:@"Updating Balances..."];

    for (NSArray *current in allMonthData) {
        Expense *exp = [current objectAtIndex:0];
        NSNumber *monthNumber = exp.month;
        double budget = 0;
        double spent = 0;
        double balance = 0;
        int count = 0;
        double avgDayBal = 0;

        for (Expense *exp in current) {                   // iterate this month
            if (exp.expenseType.boolValue == 0) {                 // all day type expensees
                spent = spent+exp.value.doubleValue;
                count ++;
            }
            else if (exp.expenseType.boolValue == 1) {
                budget = budget+exp.value.doubleValue;
            }
        }
        balance = budget+spent;
        avgDayBal = balance/[dataHandler numberOfDaysInMonthForMonth:monthNumber];

        NSMutableArray *temp = [[NSMutableArray alloc]init];
        [temp addObject:monthNumber];
        [temp addObject:[NSNumber numberWithDouble:budget ]];
        [temp addObject:[NSNumber numberWithDouble:spent ]];
        [temp addObject:[NSNumber numberWithDouble:balance ]];
        [temp addObject:[NSNumber numberWithInt:count ]];
        [temp addObject:[NSNumber numberWithDouble:avgDayBal ]];

        [allMonthDataNumbers addObject:temp];
    }
    NSNumber *day = [dataHandler getDayNumber:[NSDate date] ];
    [[allMonthDataNumbers lastObject] addObject:day];

    NSLog(@"We have %d month", [allMonthDataNumbers count]);

    [[MBProgressHUD HUDForView:self.view] setDetailsLabelText:@"Updating Interface..."];


    [allMonthTable reloadData];
    isLoadingAllMonth = NO;

}

CODE: reloadThisMonth

-(void)reloadThisMonth{

    [dataHandler updateData];

    if (!tableData) {
        tableData = [[NSMutableArray alloc]initWithCapacity:31];
    } 

    for (NSMutableArray *temp in tableData) {
        [temp removeAllObjects];
    }
    [tableData removeAllObjects];

    for (int j = 0; j < 31; j++) {          //fill with 31 empty mutuable arrays
        NSMutableArray *tempArray = [[NSMutableArray alloc]init];
        [tableData addObject:tempArray];
    }

    for (Expense *exp in dataHandler.allMonthExpenses) {
        if (exp.expenseType.boolValue == 0) {
            [[tableData objectAtIndex:(exp.day.intValue-1)]addObject:exp];
        }
    }
    int countDayExp = 0;
    for (NSMutableArray *arr in tableData) {
        countDayExp = countDayExp + arr.count;
    }
    if (countDayExp == 0) {
        hasDayExpenses = NO;
    }
    else{
        hasDayExpenses = YES;
    }

    [thisMonthTable reloadData];
    [thisMonthTable setBackgroundColor:[self grayColor]];

}

does anyone see where i went wrong? or what else cound be the problem? both tables show fine. if i dont interact with the app while loading the second view everything works perfectly. any ideas?

update:

apparently two threads collide when grapping the same fetchroutine at the same time - here is a screenshot of a paused debug state while the app is 'hung'. if i open another tab that needs the same fetching routine the app hangs. if i debug & pause in the hung state it shows me a line in the fetching routine. its the first time i work with threads - i would really appreciate some input on how to avoid this collision :/

threads colliding


Solution

  • I had a similar problem with MBProgressHUD while running tasks in another thread. The way I solved it was to use a *HUD property everywhere instead of a local variable. It looks like your using a local variable in some places and a instance variable in others.

    @property (strong, nonatomic) MBProgressHUD *HUD;
    

    Then use it like:

    HUD = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
    HUD.delegate = self;
    

    Execute your code in another thread, Then call the main thread using GCD to hide it and remove the HUD:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
    
    //Your Code to execute in the background
    
          dispatch_async(dispatch_get_main_queue(), ^(void) {
                 //Code here to update stuff on main thread
                 //Example: hide hud or change hud label
                        HUD.labelText = @"Foo Done, Bar started"; //change label
                        [HUD hide:YES];                          // or hide HUD
                        [self.tableView reloadData];            // Or reload tableView
                    });
    
    });
    

    And the MBProgressHUD Delegate:

    - (void)hudWasHidden:(MBProgressHUD *)hud {
        // Remove HUD from screen when the HUD was hidded
        [HUD removeFromSuperview];
        HUD = nil;
    }
    

    The method:

    [HUD showWhileExecuting:@selector(reloadAllMonth) onTarget:self withObject:nil animated:YES];
    

    is the same as where I ran into the most trouble. It looks like the HUD doesn't know that your view was changed and thinks the selector is not finished and keeps running blocking the main thread. So I got rid of that method and used the instance Variable *HUD and GCD, retrieving the main thread whenever I needed to update the HUD's label or hide/remove it as in the above code.