简体   繁体   中英

cellForRowAtIndexPath not called after reloadData in AppDelegate

I've been searching for a few hours now and I haven't found any solutions for my problem despite the abundance of similar posts. I'm creating an app that (at the moment to start) is continuously updating the users location using startMonitoringSignificantLocationChanges . I have a custom viewcontroller LocationsViewController which is simply a tableview meant to display each location the user travels to. In my didUpdateLocations :

- (void)locationManager:(CLLocationManager *)manager
     didUpdateLocations:(NSArray *)locations
{
    _currentLocation = _locationManager.location;
    [_locations addObject:_currentLocation];
    [_locationsViewController.tableView reloadData];
}

And then in the numberOfRowsInSection method in the LocationsViewController, I have the code:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    AppDelegate * delegate = [AppDelegate sharedInstance];
    NSLog(@"%lu", (unsigned long)[delegate.locations count]);
    return [delegate.locations count];
}

Now I have the LocationsViewController instantiated in the AppDelegate like so:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    sharedInstance              = self;
    _datahub                    = [[Datahub alloc] init];
    _locations                  = [[NSMutableArray alloc] init];
    _locationManager            = [[CLLocationManager alloc] init];
    _locationManager.delegate   = self;

    UIStoryboard * storyboard   = [UIStoryboard storyboardWithName:@"Main" bundle:nil];

    _locationsViewController    = (LocationsViewController*) [storyboard instantiateViewControllerWithIdentifier:@"locationsViewController"];
    _timePickViewController     =  (TimePickViewController*) [storyboard instantiateViewControllerWithIdentifier:@"timePickViewController"];


    if ([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
        [_locationManager requestAlwaysAuthorization];
    }

    [_locationManager startMonitoringSignificantLocationChanges];
    return YES;
}

In LocationsViewController 's numberOfRowsInSection method, the count of delegate.locations is 0 when the LocationsViewController is first instantiated. However after didUpdateLocations is called, the array delegate.locations is populated with 1 location. The reloadData method is then called and when numberOfRowsInSection is called again it returns 1. If the method returns 1 then cellForRowAtIndexPath should be called right after; However for some reason, it isn't getting called. I was wondering if anyone knew, 1. What the problem could be with my code (I don't think it has to do with any formatting or tableview setup since I used storyboards), I have my dataSource and delegate both set to self.tableView , I also tried implementing the heightForRowAtIndexPath method. And whatever I do, I have no luck. Upon calling reloadData all of the correct methods are called except for cellForRowAtIndexPath . Any suggestions or leads to what could be wrong would be great. Here's some more code if it'll help, Thanks: LocationsViewController.m

@implementation LocationsViewController
//*******************************************************************************************************************************
- (void)viewDidLoad {
    [super viewDidLoad];
    self.tableView.delegate            = self;
    self.tableView.dataSource          = self;


}
//*******************************************************************************************************************************
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
//*******************************************************************************************************************************
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 30.0f;
}
//*******************************************************************************************************************************
#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}
//*******************************************************************************************************************************
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    AppDelegate * delegate = [AppDelegate sharedInstance];
    NSLog(@"%lu", (unsigned long)[delegate.locations count]);
    return [delegate.locations count];
}
//*******************************************************************************************************************************
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    //Cell setup
    AppDelegate * delegate = [AppDelegate sharedInstance];

    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
                                      reuseIdentifier:CellIdentifier];
    }

    //Edit cell
    CLGeocoder *ceo = [[CLGeocoder alloc]init];

    CLLocation * location = delegate.locations[indexPath.row];

    [ceo reverseGeocodeLocation:location
              completionHandler:^(NSArray * placemarks, NSError *error) {
                  CLPlacemark *placemark = [placemarks objectAtIndex:0];
                  //String to hold address
                  cell.textLabel.text = [NSString stringWithFormat:@"%F, %F", location.coordinate.latitude, location.coordinate.longitude];
                  cell.detailTextLabel.text = [NSString stringWithFormat:@"%@, %@", placemark.locality, placemark.administrativeArea];
              }
     ];

    return cell;
}

AppDelegate.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    sharedInstance              = self;
    _datahub                    = [[Datahub alloc] init];
    _locations                  = [[NSMutableArray alloc] init];
    _locationManager            = [[CLLocationManager alloc] init];
    _locationManager.delegate   = self;

    UIStoryboard * storyboard   = [UIStoryboard storyboardWithName:@"Main" bundle:nil];

    _locationsViewController    = (LocationsViewController*) [storyboard instantiateViewControllerWithIdentifier:@"locationsViewController"];
    _timePickViewController     =  (TimePickViewController*) [storyboard instantiateViewControllerWithIdentifier:@"timePickViewController"];


    if ([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
        [_locationManager requestAlwaysAuthorization];
    }

    [_locationManager startMonitoringSignificantLocationChanges];
    return YES;
}
//*******************************************************************************************************************************
- (void)locationManager:(CLLocationManager *)manager
     didUpdateLocations:(NSArray *)locations
{
    _currentLocation = _locationManager.location;
    [_locations addObject:_currentLocation];
    [_locationsViewController.tableView reloadData];
}

@rdelmar's comment is the core of the problem. In your app delegate you're instantiating a LocationsViewController and then never doing anything with it. That view controller will never appear on the screen, and will never do anything.

In general it's bad to make the app delegate manage anything other than initial setup and handling app delegate methods. You should either make your view controller create a location manager object or set up a separate location services object to do that.

It's also a very bad idea to reach into a view controller and manipulate it's view objects directly. If you need a view controller to update one of it's views then you should add a method that the outside object calls, and that method would change the view as needed.

Making the view controller manage the location manager itself is by far the simplest solution, although it's got limitations. That approach really only works well if only that view controller cares about location services.

If you have other objects (view controllers, etc) in your app that care about location information you should create a separate object for managing location updates. In that case you should set it up with a delegate, or give it a location update block to call when updates are available. If you want multiple view controllers in your app to be notified at the same time you might want to use the notification manager to send out custom location update notifacations.

You might want to make your location services object a singleton. That way your view controller(s) can fetch the single instance of the location services object and set themselves as deleate/set their handler block as the active handler block when they are frontmost.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM