iphone why is he using valueForKeyPath - iphone

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.

Related

CGPoints, NSValues, NSMutableArray and the ID Type

I am trying to store CGPoints from UITouches in an Array. From reading other threads I have realized that using NSValue seems to be the best way to do this.
Currently I have:
NSMutableArray *dotsArray;
NSValue *pointObj;
UITouch *touch = obj;
CGPoint touchPoint = [touch locationInView:self.view];
// Need to store the CGPoints, can't store directly into an array // as it is a C struct // so we use NSValue
// CGPoint converted to NSValue
CGPoint point = touchPoint;
pointObj = [NSValue valueWithCGPoint:point];
dotsArray = [NSArray arrayWithObjects:pointObj,nil];
// Print to console the objects in the collection
NSLog(#"array content: %#", dotsArray);
NSLog(#"[dotsArray count] = %i",[dotsArray count]);
// Restore from NSValue to C structures
CGPoint pointRestored = [pointObj CGPointValue];
I want to be able to use:
[dotsArray insertObject:pointObj atIndex:0];
in place of
dotsArray = [NSArray arrayWithObjects:pointObj,nil];
so that I can increment the placing as I recall the method to store multiple points.
From reading I think this has something to do with the ID type required for the 'insertObject', but I don't fully understand it and am unable to find a solution online.
EDIT: When I use
[dotsArray insertObject:pointObj atIndex:0];
then
NSLog(#"array content: %#", dotsArray);
outputs NULL
Use addObject: method.
[dotsArray addObject:pointObj];
I think it will be helpful to you.
The method which you want to use [dotsArray insertObject:pointObj atIndex:0]; refers to the id of the array that is the array position. This means atIndex:0 is the first position in your array as the array starts from Zeroth position. So now if you put this in your DotsArray , the index which you want to call or the ID type is the value at the point you want to show.

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]];

How To Declare an MKPolygon

Ey guys, so a seemingly simple problem but apparently too complicated for me. I am trying to create one instance of MKPolygon and it ain't going too well. Here is the code:
MKMapPoint point1 = {38.53607,-121.765793};
MKMapPoint point2 = {38.537606,-121.768379};
MKMapPoint point3 = {38.53487,-121.770578};
NSArray *mapPointArr = [[NSArray alloc] initWithObjects:point1,point2,point3,nil count:3]; //errors here
MKPolygon *polygon = [MKPolygon polygonWithPoints:mapPointArr count:3];
I am getting a bunch of errors on the line at which I initialize the array(incompatible type for argument 1...). Any idea what's wrong? Thanks in advance!
MKMapPoint is a plain c-structure and you can't add it to objective-c container directly.
In your case you do not need to do that as +polygonWithPoints: requires not a NSArray, but a c-array as 1st parameter. Proper way to create polygon will be:
MKMapPoint points[3] = {{38.53607,-121.765793}, {38.537606,-121.768379}, {38.53487,-121.770578}};
MKPolygon *polygon = [MKPolygon polygonWithPoints:points count:3];

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

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)