(Using iOS 5 and Xcode 4.2.)
I've followed the instructions here: http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/LocationAwarenessPG/AnnotatingMaps/AnnotatingMaps.html#//apple_ref/doc/uid/TP40009497-CH6-SW15 and used the MKCircle and MKCircleView classes to add a circle overlay on my MKMapView.
However what I actually want is an inverted circle overlay, like the left map in the sketch below (currently I have a circle overlay like the one on the right):
For the inverted circle, the overlay should cover the entire map - apart from the visible circle.
Is there an easy way to accomplish this using the MKCircle/MKCircleView classes? Or will I have to go deeper and define a custom overlay object/view?
Thank you for your help :)
I had the same task and here is how I solve it:
NOTE: this code will only work starting from iOS7
Add an overlay to the map, somewhere in your view controller:
MyMapOverlay *overlay = [[MyMapOverlay alloc] initWithCoordinate:coordinate];
[self.mapView addOverlay:overlay level:MKOverlayLevelAboveLabels];
In the MKMapViewDelegate methods write next:
- (MKOverlayRenderer *)mapView:(MKMapView *)map rendererForOverlay:(id<MKOverlay>)overlay {
/// we need to draw overlay on the map in a way when everything except the area in radius of 500 should be grayed
/// to do that there is special renderer implemented - NearbyMapOverlay
if ([overlay isKindOfClass:[NearbyMapOverlay class]]) {
MyMapOverlayRenderer *renderer = [[MyMapOverlayRenderer alloc] initWithOverlay:overlay];
renderer.fillColor = [UIColor whateverColor];/// specify color which you want to use for gray out everything out of radius
renderer.diameterInMeters = 1000;/// choose whatever diameter you need
return renderer;
}
return nil;
}
The MyMapOverlay itself should be something like followed:
@interface MyMapOverlay : NSObject<MKOverlay>
- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate;
@end
@implementation MyMapOverlay
@synthesize coordinate = _coordinate;
- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate {
self = [super init];
if (self) {
_coordinate = coordinate;
}
return self;
}
- (MKMapRect)boundingMapRect {
return MKMapRectWorld;
}
@end
And the MyMapOverlayRenderer:
@interface MyMapOverlayRenderer : MKOverlayRenderer
@property (nonatomic, assign) double diameterInMeters;
@property (nonatomic, copy) UIColor *fillColor;
@end
@implementation MyMapOverlayRenderer
/// this method is called as a part of rendering the map, and it draws the overlay polygon by polygon
/// which means that it renders overlay by square pieces
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)context {
/// main path - whole area
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(mapRect.origin.x, mapRect.origin.y, mapRect.size.width, mapRect.size.height)];
/// converting to the 'world' coordinates
double radiusInMapPoints = self.diameterInMeters * MKMapPointsPerMeterAtLatitude(self.overlay.coordinate.latitude);
MKMapSize radiusSquared = {radiusInMapPoints, radiusInMapPoints};
MKMapPoint regionOrigin = MKMapPointForCoordinate(self.overlay.coordinate);
MKMapRect regionRect = (MKMapRect){regionOrigin, radiusSquared}; //origin is the top-left corner
regionRect = MKMapRectOffset(regionRect, -radiusInMapPoints/2, -radiusInMapPoints/2);
// clamp the rect to be within the world
regionRect = MKMapRectIntersection(regionRect, MKMapRectWorld);
/// next path is used for excluding the area within the specific radius from current user location, so it will not be filled by overlay fill color
UIBezierPath *excludePath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(regionRect.origin.x, regionRect.origin.y, regionRect.size.width, regionRect.size.height) cornerRadius:regionRect.size.width / 2];
[path appendPath:excludePath];
/// setting overlay fill color
CGContextSetFillColorWithColor(context, self.fillColor.CGColor);
/// adding main path. NOTE that exclusionPath was appended to main path, so we should only add 'path'
CGContextAddPath(context, path.CGPath);
/// tells the context to fill the path but with regards to even odd rule
CGContextEOFillPath(context);
}
As a result you will have exact same view like on the left image that was posted in the question.
The best way to do it, would be to subclass MKMapView
and override the drawRect
method call super, then paint over the map with the color you want.
Then each time the user moves, drawRect
should respond by drawing appropriately.
Here a Swift version. Thanks Valerii.
https://github.com/dariopellegrini/MKInvertedCircle
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With