I have an NSMutableArray that stores IDs, latitudes, longitudes, etc... I have a requirement to compare the user's current location to the latitudes and longitudes of the items stored in the array below.
I know how to get the user's current coordinates, but I don't know how to access the coordinates in the array or how to compare the distances.
The array is an NSMutableArray called scrolledPast (see below). Lets say the current user's coordinates are 21.31,-157.86. How would I even start? Any guidance would be greatly appreciated. Thank you al for your wonderful help!
array: (
{
key1 = 80;
key2 = "11:34 PM";
key3 = "place1";
key4 = "21.3111656";
key5 = "-157.8606953";
},
{
key1 = 251;
key2 = "11:34 PM";
key3 = "place2";
key4 = "21.310672";
key5 = "-157.8611839";
},
{
key1 = 79;
key2 = "11:34 PM";
key3 = "place3";
key4 = "21.3106798";
key5 = "-157.8612934";
}
)
Here is the code that generates the above array:
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:placeId forKey:#"key1"];
[dict setObject:currentTime forKey:#"key2"];
[dict setObject:textForMyLabel forKey:#"key3"];
[dict setObject:placeLatitude forKey:#"key4"];
[dict setObject:placeLongitude forKey:#"key5"];
[scrolledPast addObject:dict];
NSLog(#"array: %#", scrolledPast);
You have an array of dictionaries. You can access elements in the following way:
for (NSDictionary *dict in scrolledPast) {
NSString *lat = dict[#"key4"];
NSString *lng = dict[#"key5"];
// compare distance here
}
For comparing distances it's best to use CLLocation's distanceFromLocation: (I suppose that internally it uses the Haversine formula).
I will take another approach to solve your problem. I will create a new object called Coordinate for example to save the data like:
#import "Coordinates.h"
#interface Coordinates ()
#property (nonatomic, strong) NSNumber *identifier;
#property (nonatomic, strong) NSString *name;
#property (nonatomic, strong) NSDate *date;
#property (nonatomic, assign) CGPoint coordinate;
#end
#implementation Coordinates
- (id) initWithDictionary: (NSDictionary *) dict
{
//Create and hydrate object
}
- (float) comparePointWithPoint: (CGPoint) point
{
CGFloat xDist = (self.coordinate.x - point.x);
CGFloat yDist = (self.coordinate.y - point.y);
CGFloat distance = sqrt((xDist * xDist) + (yDist * yDist));
return distance;
}
You can use CLLocation class instead of CGPoint if you want to save the coordinates.
First create a model object for each store and implement useful methods within the class:
Store.h:
#interface Store : NSObject
#property (strong, nonatomic) NSString *storeId; // Don't use "id" as that is type in Objective-C
#property (assign, nonatomic) NSUInteger timeHour;
#property (assign, nonatomic) NSUInteger timeMinute;
#property (strong, nonatomic) NSString *label;
#property (assign, nonatomic) float lat;
#property (assign, nonatomic) float lng; // not "long" as that is a type
- (id)initWithDictionary:(NSDictionary *)dict;
- (BOOL)isStoreNearLatitude:(float)lat longitude:(float)lng;
#end;
Store.m:
#import "Store.h"
#implementation Store
#synthesize storeId, timeHour, timeMinute, label, lat, lng;
- (id)initWithDictionary:(NSDictionary *)dict {
self = [super init];
if (self) {
self.storeId = [dict objectForKey:#"key1"];
// etc.
}
return self;
}
- (BOOL)isStoreNearLatitude:(float)lat longitude:(float)lng {
// You will never get an exact match, so you will need to match the lat/long within
// a certain tolerance. You might want to pass it in so it can change at runtime...
#define TOLERANCE 10.0f
return
fabs(self.lat - lat) < TOLERANCE &&
fabs(self.lng - lng) < TOLERANCE;
}
#end
You will find that this new Store object becomes more and more useful as your project progresses.
Related
I want to show multiple pin in MKMapView for "Sports" category places in my program. Could someone guide me how can i show multi pin in MKMapView for "Sports" places around 20 miles distance from the current location?
thanks.
Create annotation objects and add them to the map. I called them LocalAnnotation.
LocalAnnotation.h:
#interface LocationAnnotation : NSObject <MKAnnotation>
{
CLLocationCoordinate2D coordinate;
NSString *title;
NSString *subtitle;
}
#property (nonatomic) CLLocationCoordinate2D coordinate;
#property (nonatomic, copy) NSString *title;
#property (nonatomic, copy) NSString *subtitle;
-initWithCoordinate:(CLLocationCoordinate2D)inCoord;
#end
LocalAnnotation.m:
#import "locationAnnotation.h"
#implementation LocationAnnotation
#synthesize coordinate;
#synthesize title;
#synthesize subtitle;
-init
{
return self;
}
-initWithCoordinate:(CLLocationCoordinate2D)inCoord
{
coordinate = inCoord;
return self;
}
#end
Within the method using the map:
//erase all annotations
[self.geoMap removeAnnotations:[self.geoMap annotations]]; //geoMap is an MKMapView object.
//Set the pin
CLLocationCoordinate2D newCoordinate;
newCoordinate.latitude = (CLLocationDegrees) [exifGeoLatitudeNumeric doubleValue]; //exifGeoLatitudeNumeric is a NSNumber representing a coordinate in the format +/-23.5435423
newCoordinate.longitude = (CLLocationDegrees) [exifGeoLongitudeNumeric doubleValue]; //exifGeoLongitudeNumeric is a NSNumber representing a coordinate in the format +/y23.5435423
LocationAnnotation *newAnnotation = [[LocationAnnotation alloc] initWithCoordinate:newCoordinate];
[self.geoMap addAnnotation:newAnnotation];
Is there a place where I can read up on this or maybe better to give me an example, how to change this code into an NSArray?
-(void)loadOurAnnotations
{
CLLocationCoordinate2D workingCoordinate;
workingCoordinate.latitude = -37.711455; //This has to be an integer
workingCoordinate.longitude = 176.285013; //This has to be an integer
MyAnnotation *myLocation1 = [[MyAnnotation alloc] initWithCoordinate:workingCoordinate]; //The pointer has to be set to an array
[myLocation1 setTitle:#"The Palms Cafe"]; //The pointer and the setTitle here
[myLocation1 setSubtitle:#"157 Domain Road - (07) 542 2430"]; //The pointer and the setSubTitle here
[myLocation1 setAnnotationType:MyAnnotationTypeMine]; //again the pointer here
[mapView addAnnotation:myLocation1]; //and the pointer here
}
All of the pointers obviously come from the same place in the array and that whole piece of code (within the curly braces) is one record, so if I want to add another place i'll need to copy all of that again.
So what I am wanting to achieve is to set that up in a Plist, so that I can add the records in there, but only have the -(void)loadOurAnnotations be set once in the code and repeat itself. Of course if I should drop the -(void)loadOurAnnotations then that is not an issue, its just the way I have it at the moment.
As you may be able to tell by the info I gathering, these will be represented as an annotation on a MKMapView.
Any help is appreciated:-)
-Jeff
If I understood correctly,
First: you read your data from plist file into NSArray via some modal class. ( The example below "Location" just a modal class, and I fill in via core data )
// Location.h
#interface Location : NSManagedObject
#property (nonatomic, strong) NSNumber * longitude;
#property (nonatomic, strong) NSNumber * latitude;
#property (nonatomic, strong) NSString * name;
#end
// Location.h
#implementation Location
#dynamic longitude;
#dynamic latitude;
#dynamic name;
#end
Second: in your "loadOurAnnotations" method, iterate all values in "locationArray" which contains Location objects and for each Location object, I create PinLocation instance ( subclass of MkAnnotation ), and add them into mapView.
for(Location *location in locationArray) {
NSString *name = [location name];
CLLocationCoordinate2D coordinate;
coordinate.latitude = [[location latitude] doubleValue];
coordinate.longitude = [[location longitude] doubleValue];
PinLocation *pinLocation = [[PinLocation alloc] initWithName:name coordinate:coordinate];
[self.mapView addAnnotation:pinLocation];
}
For some reason, I can't access any of my variables after the first IF Statement in the following code. For instance, if index path is [0,0], then the variable phoneText spits out a phone number. But if its [1,0] or [2,0], I get a "null" return. Why is my variable being erased?
The following function in mapviewcontroller.m sets the values. I do actually have an error here that says "instance method setDetails not found".
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
//this determines what kind of item was selected
if ([control isKindOfClass:[UIButton class]]) {
NSLog(#"Trying to load VenueIdentifier...");
FinderAnnotation *clicked = view.annotation;
FinderViewController *fvi = [self.storyboard instantiateViewControllerWithIdentifier:#"FinderDetail"];
NSString* latitude = [NSString stringWithFormat:#"%f",clicked.coordinate.latitude];
NSString* longitude = [NSString stringWithFormat:#"%f",clicked.coordinate.longitude];
NSLog(#"lat: %#",latitude);
NSLog(#"lon: %#",longitude);
[fvi setDetails:clicked.title phone:clicked.phone address:clicked.address beersavailable:clicked.beersavailable latitude:latitude longitude:longitude];
[self.navigationController pushViewController:fvi animated:YES];
}
}
Then my finderdetail.h creates these variables:
#interface FinderDetail : UITableViewController{
UITableViewCell *phone;
UITableViewCell *address;
UITableViewCell *directions;
UILabel *venueLabel;
NSString *phoneText;
NSString *addressText;
NSString *venueText;
NSString *beersavailable;
NSString *latitudeText;
NSString *longitudeText;
}
#property (nonatomic, retain) IBOutlet UITableViewCell *phone;
#property (nonatomic, retain) IBOutlet UITableViewCell *address;
#property (nonatomic, retain) IBOutlet UITableViewCell *directions;
#property (nonatomic, retain) IBOutlet UILabel *venueLabel;
#property (nonatomic, retain) NSString *phoneText;
#property (nonatomic, retain) NSString *addressText;
#property (nonatomic, retain) NSString *venueText;
#property (nonatomic, retain) NSString *beersavailble;
#property (nonatomic, retain) NSString *latitudeText;
#property (nonatomic, retain) NSString *longitudeText;
#end
Lastly, finderdetail.m grabs these values, assigns them to the variables, and spits them into the table:
#implementation FinderDetail
#synthesize venueLabel, phone, address, directions;
#synthesize phoneText, addressText, venueText, beersavailble, latitudeText, longitudeText;
NSString *notlisted;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
-(void)setDetails:(NSString *)v phone:(NSString *)p address:(NSString *)a beersavailable:(NSString *)ba latitude:(NSString *)lat longitude:(NSString *)lon
{
NSLog(#"venue: %#",v);
NSLog(#"phone: %#",p);
NSLog(#"address: %#",a);
NSLog(#"beersavailable: %#",ba);
NSLog(#"%#",lat);
NSLog(#"%#",lon);
latitudeText = lat;
longitudeText = lon;
phoneText = p;
addressText = a;
venueText = v;
beersavailble = ba;
NSLog(#"%#", latitudeText);
NSLog(#"%#", longitudeText);
notlisted = #"Not Listed";
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"Latitude: %#", latitudeText);
NSLog(#"Longitude: %#", longitudeText);
phone.detailTextLabel.text = phoneText;
address.detailTextLabel.text = addressText;
self.venueLabel.text = venueText;
if(phoneText == nil){
phone.detailTextLabel.text = notlisted;
}
if(addressText == nil){
address.detailTextLabel.text = notlisted;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//#warning Incomplete method implementation.
// Return the number of rows in the section.
if(section ==0)
return 1;
else
if(section ==1)
return 1;
else
if(section ==2)
return 1;
else
return 0;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"%#",indexPath);
if((indexPath.section==0) && (indexPath.row ==0))
{
NSLog(#"%#",phoneText);
}
if((indexPath.section==1) && (indexPath.row ==0))
{
NSLog(#"%#",addressText);
}
if((indexPath.section==2) && (indexPath.row ==0))
{
NSLog(#"%#",latitudeText);
NSLog(#"%#",longitudeText);
}
}
The initial phoneText will display in an NSLog, but the addressText and latitudeText and longitudeText return null. I can put phoneText in one of those lower if statements and it too returns null. Thanks!!!
You aren't actually using your #property when you are doing the following:
latitudeText = lat;
longitudeText = lon;
phoneText = p;
addressText = a;
venueText = v;
beersavailble = ba;
Also, you are leaking memory every time those assignments are performed after the initial time (when they were still nil).
What you really want is:
self.latitudeText = lat;
self.longitudeText = lon;
self.phoneText = p;
self.addressText = a;
self.venueText = v;
self.beersavailble = ba;
Also, with a NSString (also NSData, NSSet, etc.) #property, it is better to define them as a copy, since it would be perfectly valid to pass in a NSMutableString instead (since it is a subclass of NSString), which then the contents could be altered externally of this object:
#property (nonatomic, copy) NSString *phoneText;
#property (nonatomic, copy) NSString *addressText;
#property (nonatomic, copy) NSString *venueText;
#property (nonatomic, copy) NSString *beersavailble;
#property (nonatomic, copy) NSString *latitudeText;
#property (nonatomic, copy) NSString *longitudeText;
Finally, the fact that you get (NULL) outputted by NSLog suggests the ivars are getting set to nil (and most likely released), and you are using ARC (Automatic Reference Counting), instead of manual retain/release/autorelease.
In setDetails you need to use the properties in order to retain the objects and release previous objects. Assigning directly to the ivars subverts the properties setters/getters and the memory management they provide is lost. Basically if properties are defined use them every time.
Since the objects are not being retained their memory can be reused and unpredictable results can occur such as the values becoming nil.
One way to find such problems is to turn on NSZombies in the simulator runs. I do this occasionally even when I am not having problems just as a check.
To fix the problem rewrite setDetails as:
-(void)setDetails:(NSString *)v phone:(NSString *)p address:(NSString *)a beersavailable:(NSString *)ba latitude:(NSString *)lat longitude:(NSString *)lon
{
self.latitudeText = lat;
self.longitudeText = lon;
self.phoneText = p;
self.addressText = a;
self.venueText = v;
self.beersavailble = ba;
self.notlisted = #"Not Listed";
}
One way to insure that properties are not inadvertently not used is to define the ivars with a slightly different name than the properties. The synthesize statement supports this. Here is how:
in the #interface:
NSString *_latitudeText;
...
#property (nonatomic, retain) NSString *latitudeText;
in the #implementation
#synthesize latitudeText = _latitudeText;
I am not sure why I am getting the following error:
the height doesn't complain anything, it's just the width... why?
Here's my code:
#interface Embed : RKObject {
NSString * _url;
NSString * _original;
NSNumber * _width;
NSNumber * _height;
}
#property (nonatomic, retain) NSString * url;
#property (nonatomic, retain) NSString * original;
#property (nonatomic, retain) NSNumber * width;
#property (nonatomic, retain) NSNumber * height;
#end
#implementation Embed
#synthesize url = _url;
#synthesize original = _original;
#synthesize width = _width;
#synthesize height = _height;
+ (NSDictionary*) elementToPropertyMappings {
return [NSDictionary dictionaryWithKeysAndObjects:
#"url", #"url",
#"original", #"original",
#"width", #"width",
#"height", #"height",
nil];
}
- (void)dealloc
{
[_url release];
[_original release];
[_width release];
[_height release];
[super dealloc];
}
#end
not sure why it's a CGFloat, never convert it to CGFloat anywhere else
The error message would imply that it thinks the 'width' message, as posted to the objectAtIndex:0 returns a CGFloat, not an NSNumber.
It's possible it has made an incorrect guess about the thing you're calling width on, so assuming you have your object defined correctly you probably just need to give the compiler the correct hint, e.g. (broken down to make it clear where the cast goes):
[
[
(MyCorrectType *)[
[
[postsObject objectAtIndex:indexPath.row]
embeds]
objectAtIndex:0] // cast goes with the result of this
width]
floatValue]
The barely discernable error leads me to think that your -width method is returning a CGFloat rather than an NSNumber*.
I do not understand why i am getting this error. Here is the related code:
Photo.h
#import <CoreData/CoreData.h>
#class Person;
#interface Photo : NSManagedObject
{
}
#property (nonatomic, retain) NSData * imageData;
#property (nonatomic, retain) NSNumber * Latitude;
#property (nonatomic, retain) NSString * ImageName;
#property (nonatomic, retain) NSString * ImagePath;
#property (nonatomic, retain) NSNumber * Longitude;
#property (nonatomic, retain) Person * PhotoToPerson;
#end
Photo.m
#import "Photo.h"
#import "Person.h"
#implementation Photo
#dynamic imageData;
#dynamic Latitude;
#dynamic ImageName;
#dynamic ImagePath;
#dynamic Longitude;
#dynamic PhotoToPerson;
#end
This is a mapViewController.m class i have created. If i run this, the CLLocationDegrees CLLat and CLLong lines:
CLLocationDegrees CLLat = (CLLocationDegrees)photo.Latitude;
CLLocationDegrees CLLong = (CLLocationDegrees)photo.Longitude;
give me the error : pointer value used where a floating point value was expected.
for(int i = 0; i < iPerson; i++)
{
//get the person that corresponds to the row indexPath that is currently being rendered and set the text
Person * person = (Person *)[myArrayPerson objectAtIndex:i];
//get the photos associated with the person
NSArray * PhotoArray = [person.PersonToPhoto allObjects];
int iPhoto = [PhotoArray count];
for(int j = 0; j < iPhoto; j++)
{
//get the first photo (all people will have atleast 1 photo, else they will not exist). Set the image
Photo * photo = (Photo *)[PhotoArray objectAtIndex:j];
if(photo.Latitude != nil && photo.Longitude != nil)
{
MyAnnotation *ann = [[MyAnnotation alloc] init];
ann.title = photo.ImageName;
ann.subtitle = photo.ImageName;
CLLocationCoordinate2D cord;
CLLocationDegrees CLLat = (CLLocationDegrees)photo.Latitude;
CLLocationDegrees CLLong = (CLLocationDegrees)photo.Longitude;
cord.latitude = CLLat;
cord.longitude = CLLong;
ann.coordinate = cord;
[mkMapView addAnnotation:ann];
}
}
}
NSNumber is not a float type, but a pointer, so you need to do this to convert it:
CLLocationDegrees CLLat = (CLLocationDegrees)[photo.Latitude doubleValue];
CLLocationDegrees CLLong = (CLLocationDegrees)[photo.Longitude doubleValue];