How to setup a MapViewController as Datasource to

2019-08-30 09:52发布

问题:

My app has a tabbarcontroller with a UIViewController (FirstViewController-calling it mapVC) with a mapview and a UITableViewController (SecondViewController-calling it tableVC). The app fetches data from web and puts it into CD-db and each VC executes a fetch to the db. Each entity is named Holiday (dont ask) and it has a lat and long property.

Here is the UITabBarController subclass which attempts to set the mapVC as datasource to tableVC:

- (void)viewDidLoad{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    FirstViewController *mapVC;
    SecondViewController *tableVC;

    for(UIViewController *anyVC in self.viewControllers)
    {
        if([anyVC.class isKindOfClass:[SecondViewController class]]){
            tableVC = (SecondViewController *)anyVC;
        } else if ([anyVC.class isKindOfClass:[FirstViewController class]]){
            mapVC = (FirstViewController *)anyVC;
        }
    }

    tableVC.tableView.dataSource = mapVC;
    tableVC.tableView.delegate = mapVC;
}

Here are the relevant parts of mapVC:

#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
#import "SecondViewController.h"

#define METERS_PER_MILE 2609.344

@interface FirstViewController : UIViewController <MKMapViewDelegate, UITableViewDataSource, UITableViewDelegate>{
    BOOL _doneInitialZoom;

}

@property (strong, nonatomic) IBOutlet MKMapView *_mapView;
@property (strong, nonatomic) IBOutlet UIBarButtonItem *refreshButton;
@property (nonatomic, strong) NSString *entityName;
@property (strong, nonatomic) CLLocation *userLocation;

- (IBAction)refreshTapped:(id)sender;
-(void)showDetailView;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
@end

and its implementation:

#import "FirstViewController.h"
#import "Holiday.h"
#import "MyLocation.h"
#import "SDCoreDataController.h"
#import "MyTabBarController.h"
#import "TableViewCell.h"

- (void)loadRecordsFromCoreData {
    [self.managedObjectContext performBlockAndWait:^{
        [self.managedObjectContext reset];
        NSError *error = nil;
        NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:self.entityName];
        [request setSortDescriptors:[NSArray arrayWithObject:
                                     [NSSortDescriptor sortDescriptorWithKey:@"date" ascending:YES]]];
        self.farSiman = [self.managedObjectContext executeFetchRequest:request error:&error];

    }];
    NSLog(@"self.farSiman on launch = %@", self.farSiman);
}

- (void)plotStorePositions:(NSString *)responseString {

    for (id<MKAnnotation> annotation in _mapView.annotations) {
        [_mapView removeAnnotation:annotation];
    }

    NSLog(@"Dictionary is %@", self.farSiman);

    for (Holiday * holidayObject in self.farSiman) {

        NSString * latitude = holidayObject.latitude;
        NSString * longitude = holidayObject.longitude;
        NSString * storeDescription = holidayObject.name;
        NSString * address = holidayObject.address;


        CLLocationCoordinate2D coordinate;
        coordinate.latitude = latitude.doubleValue;
        coordinate.longitude = longitude.doubleValue;
        MyLocation *annotation = [[MyLocation alloc] initWithName:storeDescription address:address coordinate:coordinate distance:0];

        //
        CLLocation *pinLocation = [[CLLocation alloc] initWithLatitude:annotation.coordinate.latitude longitude:annotation.coordinate.longitude];
        //[(MyLocation*)[view annotation] coordinate].latitude longitude:[(MyLocation*)[view annotation] coordinate].longitude]];

        self.userLocation = [[CLLocation alloc] initWithLatitude:self._mapView.userLocation.coordinate.latitude longitude:self._mapView.userLocation.coordinate.longitude];
        NSLog(@"PLOT>>userLocation is %@", userLocation);

        CLLocationDistance calculatedDistance = [pinLocation distanceFromLocation:self.userLocation];
        annotation.distance = calculatedDistance/1000;
        NSLog(@"PLOT>>Distance to pin %4.0f", annotation.distance);
        //

        [_mapView addAnnotation:annotation];

    }

}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return [self.farSiman count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    TableViewCell *cell = nil;

    // Check to see whether the normal table or search results table is being displayed and set the Candy object from the appropriate array
    NSLog(@"Already in CFRAIP");

    static NSString *CellIdentifier = @"HolidayCell";
    if (cell == nil) {
        cell = [[TableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
        //cell.accessoryType=UITableViewCellAccessoryDetailDisclosureButton;
    }
    cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    Holiday *holiday = [self.farSiman objectAtIndex:indexPath.row];
    cell.nameLabel.text = holiday.name;
    //cell.dateLabel.text = holiday.latitude;

    cell.dateLabel.text = [[self calculateDistanceForLat:[holiday.latitude doubleValue] Long:[holiday.longitude doubleValue]] stringValue];

    if (holiday.image != nil) {
        UIImage *image = [UIImage imageWithData:holiday.image];
        cell.imageView.image = image;
    } else {
        cell.imageView.image = nil;
    }
    return cell;
}

// Add new method above refreshTapped
- (NSNumber*)calculateDistanceForLat:(double)lati Long:(double)longi {
    double distancia;
    CLLocation *pinLocation = [[CLLocation alloc] initWithLatitude:lati longitude:longi];
    CLLocationDistance calculatedDistance = [pinLocation distanceFromLocation:self.userLocation];

    //test locations
    NSLog(@"pinLocations is %@, userLocation is %@", pinLocation, self.userLocation);

    distancia = calculatedDistance/1000;
    return [NSNumber numberWithDouble:distancia];
}

The plotStoreLocations method is the only method call from a UIButton in the mapVC toolbar.

As for tableVC (SecondViewController)

#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>

@interface SecondViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UISearchDisplayDelegate>

@property (nonatomic, strong) NSArray *dates;
@property (nonatomic, strong) NSString *entityName;
@property (strong, nonatomic) IBOutlet UIBarButtonItem *refreshButton;

@property (strong,nonatomic) NSMutableArray *filteredResultsArray;
@property (strong,nonatomic) IBOutlet UISearchBar *resultsSearchBar;

@property (strong, nonatomic) CLLocation *userLocation;

- (IBAction)refreshButtonTouched:(id)sender;

And its implementation:

- (void)loadRecordsFromCoreData {
    [self.managedObjectContext performBlockAndWait:^{
        [self.managedObjectContext reset];
        NSError *error = nil;
        NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:self.entityName];
        [request setSortDescriptors:[NSArray arrayWithObject:
                                     [NSSortDescriptor sortDescriptorWithKey:@"date" ascending:YES]]];
        self.dates = [self.managedObjectContext executeFetchRequest:request error:&error];
        NSLog(@"self.dates==%@",self.dates);
    }];
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    if (tableView == self.searchDisplayController.searchResultsTableView) {
        return [filteredResultsArray count];
    } else {
        return [self.dates count];
    }

    //return [self.dates count];
}

// Add new method above refreshTapped
- (NSNumber*)calculateDistanceForLat:(double)lati Long:(double)longi {
    double distancia;
    CLLocation *pinLocation = [[CLLocation alloc] initWithLatitude:lati longitude:longi];
    CLLocationDistance calculatedDistance = [pinLocation distanceFromLocation:self.userLocation];

    //test locations
    NSLog(@"pinLocations is %@, userLocation is %@", pinLocation, self.userLocation);

    distancia = calculatedDistance/1000;
    return [NSNumber numberWithDouble:distancia];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    TableViewCell *cell = nil;

    if (tableView == self.searchDisplayController.searchResultsTableView) {
        static NSString *CellIdentifier = @"HolidayCell";
        if (cell == nil) {
            cell = [[TableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
        }
        cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];

        Holiday *holiday = [filteredResultsArray objectAtIndex:indexPath.row];
        NSLog(@"the holiday is %@", holiday.name);
        cell.nameLabel.text = holiday.name;

    } else {

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

        Holiday *holiday = [self.dates objectAtIndex:indexPath.row];
        cell.nameLabel.text = holiday.name;

        cell.dateLabel.text = [[self calculateDistanceForLat:[holiday.latitude doubleValue] Long:[holiday.longitude doubleValue]] stringValue];

        if (holiday.image != nil) {
            UIImage *image = [UIImage imageWithData:holiday.image];
            cell.imageView.image = image;
        } else {
            cell.imageView.image = nil;
        }
    }
    return cell;
}

Finally MyLocation:

import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>

@interface MyLocation : NSObject <MKAnnotation> {
    NSString *_name;
    NSString *_address;
    CLLocationCoordinate2D _coordinate;
}

@property (copy) NSString *name;
@property (copy) NSString *address;
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
@property (assign) float distance;

- (id)initWithName:(NSString*)name address:(NSString*)address coordinate:(CLLocationCoordinate2D)coordinate distance:(float)distance;

@end


#import "MyLocation.h"

@implementation MyLocation
@synthesize name = _name;
@synthesize address = _address;
@synthesize coordinate = _coordinate;
@synthesize distance = _distance;

- (id)initWithName:(NSString*)name address:(NSString*)address coordinate:(CLLocationCoordinate2D)coordinate distance:(float)distance{
    if ((self = [super init])) {
        _name = [name copy];
        _address = [address copy];
        _coordinate = coordinate;
        _distance = distance;
    }
    return self;
}

- (NSString *)title {
    if ([_name isKindOfClass:[NSNull class]]) 
        return @"Unknown charge";
    else
        return _name;
}

- (NSString *)subtitle {
    return [NSString stringWithFormat:@"A %0.2f Kms", _distance];
    //return _address;
}

Specific Questions:

1) If I added the cFRAIP (noris & nosit) method in mapVC (the new datasource) do I need to remove it from tableVC?

2) If I remove cFRAIP and the other 2 (noris and nosit) methods from tableVC, it crashes because there is no datasource. So the tabbarcontroller assignment of datasource doesnt seem to be working.

3) Finally if I have to remove cFRAIP from tableVC, I will lose my ability to UISearchBar the tableview. Or am I wrong?

When I run the app, the mapVC is the selected vc. The UIButton in the toolbar that calls plotStoreLocations in mapVC is greyed out until the web fetch finishes. At this point the console logs the self.farsiman locations which are Holiday entities. And I can see all the entities log into the console.

When I click the plot button, the userLocation is logged correctly as specified in the plotStorePositions and the distance value for each annotation is correct. So the distance for each MKAnnotation is calculated correctly.

When I switch to the tableVC tab, the new self.dates array is logged in the console (because I currently have the tableVC do another CD-db fetch. And I get this for each pin location:

Already in CFRAIP pinLocations is <+15.50288611,-88.02716389> +/- 0.00m (speed -1.00 mps / course -1.00) @ 1/24/13, 8:20:39 PM Central Standard Time, userLocation is (null)

which is called from the calculateDistanceForLat which is called by the CFRAIP. And every distance in the cell detail is -0.001.

回答1:

You definitely need to make mapVC the datasource and delegate. If UITableViewController isn't letting you assign it to something other that itself you might want to consider using a regular UIViewController and drop a UITableView on to it. If you set it up as a property called tableview the code in tabbarcontroller that does tableVC.tableView.dataSource = mapVC; will still work.

Yes you can and should remove all tableview delegate and datasource code from your tableVC, if it is calling those then it isn't calling the ones in the mapVC which is what you want.

It's probably giving you the wrong distance because tableVC doesn't have the user's location to measure from. Another good reason for having the table datasource attached to the map.