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.