Handle empty structs in Objective-C (Coordinate in custom class) - iphone

I have a custom class that has as an instance variable of a coordinate:
CLLocationCoordinate2D eventLocation;
#property(nonatomic) CLLocationCoordinate2D eventLocation;
I'm parsing an xml file that has an optional field that may or may not be there.
If it is i set it like so:
CLLocationCoordinate2D location;
NSArray *coordinateArray = [paramValue componentsSeparatedByString:#","];
if ([coordinateArray count] >= 2) {
location.latitude = [[coordinateArray objectAtIndex:0] doubleValue];
location.longitude = [[coordinateArray objectAtIndex:1] doubleValue];
} else {
NSLog(#"Coordinate problem");
}
info.eventLocation = location;
What I do this is basically add an annotation on a map
annotation.coordinate = alert.info.eventLocation;
I know I need to do some checking here to make sure that that exists but I'm not allowed to do a if (info.eventLocation == nil) or even if (info.eventLocation.latitude == nil)
This seems like a very basic question but I've done some searching and no one's been able to really provide a good answer/idea. Is my architecture completely off?

Because CLLocationCoordinate2D is a struct, there's not such thing as a nil value. Objective-C will initialize structs to 0 if they are object instance variables, so if you don't set a value for eventLocation, annotation.coordinate.longitude and eventLocation.lattitude will both be 0. Since this is a valid location, it's not a useful marker.
I would define a non-phyical value:
static const CLLocationDegrees emptyLocation = -1000.0;
static const CLLocationCoordinate2D emptyLocationCoordinate = {emptyLocation, emptyLocation}
and then assign this value to your alert.info.eventLocation = EmptyLocationCoordinate to represent an empty value. You can then check if (alert.info.eventLocation == emptyLocationCoordinate).

I used the code above except it wouldn't let me declare a const with another const, so i simply changed it to:
static const CLLocationDegrees EmptyLocation = -1000.0;
static const CLLocationCoordinate2D EmptyLocationCoordinate = {-1000.0, -1000.0};
I also added in my init for the class:
eventLocation = EmptyLocationCoordinate;
Thanks for the help Barry.

Just for completeness sake, you can use the kCLLocationCoordinate2DInvalid constant to hold a reference to an invalid CLLocationCoordinate2D. (see: #Klaas's answer)

Related

Get variable or object from IBAction method

I have been reading, googling and watching Lynda videos to find the answer for this the last couple days. I haven't found a good answer yet.
This seems like it should be pretty simple. With normal methods I can pass variables. But with IBAction being (void) I cant figure out how to get a variable to another method.
Here are some simple examples of what I would like to do:
- (IBAction)treeButton:(id)sender {
int test = 10;
}
-(void)myMethod{
NSLog(#"the value of test is %i",test);
}
This what I really want to have work. I am try to have a button set the initial location that I want to store and use in another method.
- (IBAction)locationButton:(id)sender {
CLLocation *loc1 = [[CLLocation alloc]
initWithLatitude:_locationManager.location.coordinate.latitude
longitude:_locationManager.location.coordinate.longitude];
}
-(void)myMethod{
NSLog(#"the value of test is %i",test);
NSLog(#"location 1 is %#",loc1);
}
Any suggestions to lead me in the right direction would be great. I have read and watched videos on variable scope, instance varaibles etc. Just not understanding what I need to do here
Change myMethod to accept the parameters you need:
- (void)myMethod:(CLLocation *)location {
NSLog(#"location 1 is %#", location);
}
Invoke it something like so:
- (IBAction)locationButton:(id)sender {
CLLocation *loc1 = [[CLLocation alloc]
initWithLatitude:_locationManager.location.coordinate.latitude
longitude:_locationManager.location.coordinate.longitude];
[self myMethod:loc1];
}
If you need it to be accessible by multiple methods or at different points in the code, I recommend creating an instance variable for loc1 in your #interface declaration.
#interface MyClass : NSObject {
CLLocation *loc1;
}
In your method, instead of re-declaring it, you'd just set it:
loc1 = [[CLLocation alloc]
initWithLatitude:_locationManager.location.coordinate.latitude
longitude:_locationManager.location.coordinate.longitude];
In myMethod, just access it:
- (void)myMethod{
NSLog(#"location 1 is %#", loc1);
}

iOS MapKit show nearest annotations within certain distance

Currently i am working on a Location based application for iPhone/iPad . I have several annotations in my MapKit , what i want to do is to track the location of the user and shows the annotations that are within the 3km . Can somebody give me a start ?
Sorry for the delayed response... the question just fell off my radar.
I'm going to suppose that you have a method that returns a set of NSValue-wrapped CLLocationCoordinate2D structs (the basic approach is the same regardless of what your internal data representations are). You can then filter the list using a method something akin to the following (warning: typed in browser):
NSSet *locations = ...;
CLLocation centerLocation = ...; // Reference location for comparison, maybe from CLLocationManager
CLLocationDistance radius = 3000.; // Radius in meters
NSSet *nearbyLocations = [locations objectsPassingTest:^(id obj, BOOL *stop) {
CLLocationCoordinate2D testCoordinate;
[obj getValue:&testCoordinate];
CLLocation *testLocation = [[CLLocation alloc] initWithLatitude:testCoordinate.latitude
longitude:testCoordinate.longitude];
BOOL returnValue = ([centerLocation distanceFromLocation:testLocation] <= radius);
[testLocation release];
return returnValue;
}
];
With the filtered set of coordinates in hand, you can create MKAnnotation instances and add them to the map in the usual manner, as described in Apple's documentation.
If you have many thousands of test locations then I suppose this approach could start to incur performance issues. You would then want to switch your point storage approach to use, e.g., quadtrees, to reduce the number of points that need to be precision-filtered. But don't optimize prematurely!
Hope that helps!

NSMutableArray of ClLocationCoordinate2D

I'm trying to create then retrieve an array of CLLocationCoordinate2D objects, but for some reason the array is always empty.
I have:
NSMutableArray *currentlyDisplayedTowers;
CLLocationCoordinate2D new_coordinate = { currentTowerLocation.latitude, currentTowerLocation.longitude };
[currentlyDisplayedTowers addObject:[NSData dataWithBytes:&new_coordinate length:sizeof(new_coordinate)] ];
I've also tried this for adding the data:
[currentlyDisplayedTowers addObject:[NSValue value:&new_coordinate withObjCType:#encode(struct CLLocationCoordinate2D)] ];
And either way, the [currentlyDisplayedTowers count] always returns zero. Any ideas what might be going wrong?
Thanks!
To stay in object land, you could create instances of CLLocation and add those to the mutable array.
CLLocation *towerLocation = [[CLLocation alloc] initWithLatitude:lat longitude:lon];
[currentDisplayedTowers addObject:towerLocation];
To get the CLLocationCoordinate struct back from CLLocation, call coordinate on the object.
CLLocationCoordinate2D coord = [[currentDisplayedTowers lastObject] coordinate];
As SB said, make sure your array is allocated and initialized.
You’ll also probably want to use NSValue wrapping as in your second code snippet. Then decoding is as simple as:
NSValue *wrappedCoordinates = [currentlyDisplayedTowers lastObject]; // or whatever object you wish to grab
CLLocationCoordinate2D coordinates;
[wrappedCoordinates getValue:&coordinates];
You need to allocate your array.
NSMutableArray* currentlyDisplayedTowers = [[NSMutableArray alloc] init];
Then you can use it. Be sure to call release when you are done with it or use another factory method.
I had currentlyDisplayedTowers = nil which was causing all the problems. Also, the previous advice to init and alloc were necessary. Thanks everyone for the help!
For anyone else with this issue, there's another solution if you are planning on using MapKit.
(The reason I say IF, of course, is because importing a module such as MapKit purely for a convenient wrapper method is probably not the best move.. but nonetheless here you go.)
#import MapKit;
Then just use MapKit's coordinate value wrapper whenever you need to:
[coordinateArray addObject:[NSValue valueWithMKCoordinate:coordinateToAdd]];
In your example..
[currentlyDisplayedTowers addObject:[NSValue valueWithMKCoordinate:new_coordinate]];

Determining distance using mapkit

How is a 1000ft or 1/2 mile distance determined with mapkit? Either a radius from some pin or the distance between two pins.
For example, I center the map on pin A. Pins B, C, and D are also on the map at various distances from pin A. B and C are within 1/2 mile from A but D is 1 mile away. I'd like to know that B and C are within 1/2 mile from A. How can I calculate that?
Since you've stated that the two different points are "pins", I'm going to assume you're using MKPinAnnotationView (or some other annotation view). If not, you're going to have to get the location some other way.
If you have pointers to the annotation objects for these locations, then you can easily call -coordinate on them, create CLLocations from these coordinates (using -initWithLatitude:longitude:), and then use the method -getDistanceFrom to retrieve the distance in meters. From there, it's an easy conversion to miles. All told, the code would look something like this:
CLLocationCoordinate2D pointACoordinate = [pointAAnnotation coordinate];
CLLocation *pointALocation = [[CLLocation alloc] initWithLatitude:pointACoordinate.latitude longitude:pointACoordinate.longitude];
CLLocationCoordinate2D pointBCoordinate = [pointBAnnotation coordinate];
CLLocation *pointBLocation = [[CLLocation alloc] initWithLatitude:pointBCoordinate.latitude longitude:pointBCoordinate.longitude];
double distanceMeters = [pointALocation getDistanceFrom:pointBLocation];
double distanceMiles = (distanceMeters / 1609.344);
You'll end up with the distance in miles, and can compare it from there. Hope this helps.
getDistanceFrom is now deprecated so here's an alternative answer for anyone looking to do this.
CLLocationCoordinate2D pointACoordinate = [pointAAnnotation coordinate];
CLLocation *pointALocation = [[CLLocation alloc] initWithLatitude:pointACoordinate.latitude longitude:pointACoordinate.longitude];
CLLocationCoordinate2D pointBCoordinate = [pointBAnnotation coordinate];
CLLocation *pointBLocation = [[CLLocation alloc] initWithLatitude:pointBCoordinate.latitude longitude:pointBCoordinate.longitude];
float distanceMeters = [pointALocation distanceFromLocation:pointBLocation];
float distanceMiles = (distanceMeters / 1609.344);
[pointALocation release];
[pointBLocation release];
As above, you could use float instead of double and you could cast the results to an int if you don't require the precision of the float like so:
int distanceCastToInt = (int) [pointALocation distanceFromLocation:pointBLocation];
The int is handy if you wanted to give a rough idea of distance in the annotation like so:
pointAAnnotation.title = #"Point A";
pointAAnnotation.subtitle = [NSString stringWithFormat: #"Distance: %im",distanceCastToInt];
The subtitle of the annotation would read "Distance: 50m" for example.
This is Swift 3 equivalent of the given answer:
let pointACoordinate = pointAAnnotation.coordinate
let pointALocation = CLLocation(latitude: pointACoordinate.latitude, longitude: pointACoordinate.longitude)
let pointBCoordinate = pointBAnnotation.coordinate
let pointBLocation = CLLocation(latitude: pointBCoordinate.latitude, longitude: pointBCoordinate.longitude)
let distanceMeters = pointALocation.distance(from: pointBLocation)
let distanceMiles = (distanceMeters / 1609.344)

iphone why is he using valueForKeyPath

I found some mapkit code on the internet that looks like this:
- (void)recenterMap {
NSArray *coordinates = [self.mapView valueForKeyPath:#"annotations.coordinate"];
CLLocationCoordinate2D maxCoord = {-90.0f, -180.0f};
CLLocationCoordinate2D minCoord = {90.0f, 180.0f};
for(NSValue *value in coordinates) {
CLLocationCoordinate2D coord = {0.0f, 0.0f};
[value getValue:&coord];
if(coord.longitude > maxCoord.longitude) {
maxCoord.longitude = coord.longitude;
}
--omitted the rest
I want to know why valueForKeypath is used to get the coordinate data, instead of just
[self.mapView.annotations]
Has it something to do with speed?
He's using the valueForKeyPath: construct to return an array of all the coordinates for all of the annotations.
Assuming that self.mapView is an MKMapView then it has an annotations property which returns an array of objects conforming to the MKAnnotation protocol. That means that every object in that array should implement the coordinate property. By issuing a call to [self.mapView valueForKeyPath:#"annotations.coordinate"] he is immediately getting an array of all of the coordinates without having to iterate over each individual annotation item in the array.
Without using the KVC construct here he'd have to do something similar to this:
NSMutableArray *coordinates = [NSMutableArray array];
for (id<MKAnnotation> annotation in self.mapView.annotations)
[coordinates addObject:annotation.coordinate];
In all, it just makes for simpler, easier to read code.