Reputation: 5009
I want to do overlays on an MKMapView. So I created my custom object:
@interface Spot : NSObject
@property int spot_id;
@property CLLocationCoordinate2D coordinate;
@property float radius;
@property NSObject<MKOverlay> * overlay;
- (id) initWithSpotId:(int)spot_id position:(CLLocationCoordinate2D)coordinate andRadius:(float)radius;
@end
There is a backend, that constantly updates the Spots and delivers them to the phone. So I have to:
Is there a better way of keeping track of the overlays? Keeping an NSObject<MKoverlay> *
reference attached to each spot and constantly reassigning it feels a bit strange to me.
Upvotes: 1
Views: 4015
Reputation: 437372
It seems like there are three questions here:
How to detect changes in spots in your server?
The ugly way to do this would be iterate through your spots and see if the coordinate
and/or radius
changed. A little better would be to have the server update create, modify, and delete timestamps or some other identifier so the client would have the ability to retrieve all creations, modifications, and/or deletions since the last update. Best would be to marry that with some form of push notification, so the client would also be proactively notified of these changes.
This question is difficult to answer in the abstract. It depends a lot upon the capabilities of your server and the nature of the database (e.g. how many "spots", how frequently do they change, etc.). This impacts both the client-server architecture, but also the client implementation.
How to update the map when a spot changes?
This is a far easier question. Insertions and deletions are easy. You just do addOverlay:
(or, in iOS 7, addOverlay:level:
and removeOverlay:
. For updates, while it's inelegant, I think the easiest way is to just remove the old overlay and add it back in and viewForOverlay
will take care of the user interface for you.
What is the right structure of the Spot
class?
Just as a thought, but it seems duplicative to have your coordinate
and radius
properties, and then have a id<MKOverlay> overlay
object too (because that's presumably a MKCircle
with the same two properties). If your overlays are going to be MKCircle
objects, it might be easier to just have a Spot
class, itself, to conform to MKOverlay
:
@interface Spot : NSObject <MKOverlay>
@property (nonatomic) int spot_id;
@property (nonatomic) CLLocationCoordinate2D coordinate;
@property (nonatomic) CLLocationDistance radius;
@property (nonatomic, readonly) MKMapRect boundingMapRect;
- (id) initWithSpotId:(int)spot_id position:(CLLocationCoordinate2D)coordinate andRadius:(CLLocationDistance)radius;
@end
Then, all you need to do is to implement boundingMapRect
and intersectsMapRect
:
- (MKMapRect) boundingMapRect
{
MKMapPoint point = MKMapPointForCoordinate(self.coordinate);
CLLocationDistance distance = self.radius * MKMetersPerMapPointAtLatitude(self.coordinate.latitude);
MKMapRect rect = MKMapRectMake(point.x, point.y, distance * 2.0, distance * 2.0);
rect = MKMapRectOffset(rect, -distance, -distance);
return rect;
}
- (BOOL)intersectsMapRect:(MKMapRect)mapRect
{
return MKMapRectIntersectsRect(mapRect, [self boundingMapRect]);
}
You might want to double-check that boundingMapRect
logic, but I think it's right.
Then you can just add and remove Spot
objects as overlays themselves. And all you need to do is to implement a viewForOverlay
in your MKMapViewDelegate
, for example, in iOS versions prior to 7, that would be:
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay
{
if ([overlay isKindOfClass:[Spot class]])
{
Spot *spot = (id)overlay;
MKCircle *circle = [MKCircle circleWithCenterCoordinate:spot.coordinate
radius:spot.radius];
MKCircleView *overlayView = [[MKCircleView alloc] initWithCircle:circle];
overlayView.fillColor = [[UIColor cyanColor] colorWithAlphaComponent:0.2];
overlayView.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:0.7];
overlayView.lineWidth = 3.0;
return overlayView;
}
return nil;
}
In iOS 7, that would be:
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
if ([overlay isKindOfClass:[Spot class]])
{
Spot *spot = (id)overlay;
MKCircle *circle = [MKCircle circleWithCenterCoordinate:spot.coordinate
radius:spot.radius];
MKCircleRenderer *renderer = [[MKCircleRenderer alloc] initWithCircle:circle];
renderer.fillColor = [[UIColor cyanColor] colorWithAlphaComponent:0.2];
renderer.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:0.7];
renderer.lineWidth = 3;
return renderer;
}
return nil;
}
Another approach would be to define your Spot
as:
@interface Spot : NSObject <MKOverlay>
@property (nonatomic) int spot_id;
@property (nonatomic, strong) MKCircle *overlay;
- (id) initWithSpotId:(int)spot_id position:(CLLocationCoordinate2D)coordinate andRadius:(CLLocationDistance)radius;
And you could then just define boundingMapRect
and coordinate
return the appropriate values from the MKCircle
(saving you from having to write your own):
- (MKMapRect)boundingMapRect
{
return [self.circle boundingMapRect];
}
- (CLLocationCoordinate2D)coordinate
{
return [self.circle coordinate];
}
Clearly the init
method would change:
- (id) initWithSpotId:(int)spot_id position:(CLLocationCoordinate2D)coordinate andRadius:(CLLocationDistance)radius;
{
self = [super init];
if (self) {
_spot_id = spot_id;
_circle = [MKCircle circleWithCenterCoordinate:coordinate radius:radius];
}
return self;
}
As would the viewForOverlay
(iOS versions prior to 7.0) in the MKMapViewDelegate
:
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay
{
if ([overlay isKindOfClass:[SpotCircle class]])
{
SpotCircle *spot = (id)overlay;
MKCircleView *overlayView = [[MKCircleView alloc] initWithCircle:spot.circle];
overlayView.fillColor = [[UIColor cyanColor] colorWithAlphaComponent:0.2];
overlayView.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:0.7];
overlayView.lineWidth = 3.0;
return overlayView;
}
return nil;
}
In iOS 7, that would be:
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay
{
if ([overlay isKindOfClass:[SpotCircle class]])
{
SpotCircle *spot = (id)overlay;
MKCircleRenderer *renderer = [[MKCircleRenderer alloc] initWithCircle:spot.circle];
renderer.fillColor = [[UIColor cyanColor] colorWithAlphaComponent:0.2];
renderer.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:0.7];
renderer.lineWidth = 3;
return renderer;
}
return nil;
}
I hope that helps.
Upvotes: 2