Pages

Thursday 15 December 2016

Add inverted circle overlay to map view

Drawing circle in MKMapView as we see in reminders in iPhone. Here have pasted some code which works as below.


Reference
http://stackoverflow.com/a/31050127


I used class names are below

1. MyMapOverlay.h
2. MyMapOverlayRenderer.h
3. ViewController.h


MyMapOverlay.h

#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>

@interface MyMapOverlay : NSObject<MKOverlay>
- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate;
@end


MyMapOverlay.m

#import "MyMapOverlay.h"

@implementation MyMapOverlay
@synthesize coordinate = _coordinate;

- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate {
    self = [super init];
    if (self) {
        _coordinate = coordinate;
    }
    return self;
}

- (MKMapRect)boundingMapRect {
    return MKMapRectWorld;
}

@end


MyMapOverlayRenderer.h

#import <MapKit/MapKit.h>

@interface MyMapOverlayRenderer : MKOverlayRenderer
@property (nonatomic, assign) double diameterInMeters;
@property (nonatomic, assign) MKMapRect circleRect;
@property (nonatomic, copy) UIColor *fillColor;
@end


MyMapOverlayRenderer.m

#import "MyMapOverlayRenderer.h"


@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(_circleRect.origin.x, _circleRect.origin.y, _circleRect.size.width, _circleRect.size.height) cornerRadius:_circleRect.size.width];
    [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);
}

@end

ViewController.h

#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>

@interface ViewController : UIViewController<MKMapViewDelegate>

@property(nonatomic,weak)IBOutlet MKMapView *map;

@end

ViewController.m

#import "ViewController.h"
#import "MKMapView+ZoomLevel.h"
#import "MyMapOverlay.h"
#import "MyMapOverlayRenderer.h"


@interface ViewController ()
{
    MKCircle *circle,*circle2;
}
@end

@implementation ViewController
@synthesize map;

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    map.delegate=self;
    
    
    
    [self centerMap];
}

-(void)centerMap

{
    
    CLLocationCoordinate2D center;
    
    center.latitude = 12.8421;
    center.longitude = 77.6631;
    
    double retVal=1609.344f;
   
    MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(center,10000 ,10000);
    
    [map setRegion:[map regionThatFits:region] animated:YES];
    
    
    CLLocationDistance fenceDistance = retVal;
    
    CLLocationCoordinate2D circleMiddlePoint = CLLocationCoordinate2DMake(center.latitude, center.longitude);
    
    circle = [MKCircle circleWithCenterCoordinate:circleMiddlePoint radius:fenceDistance];
    
    [map addOverlay: circle];
    
    MyMapOverlay *overlay = [[MyMapOverlay alloc] initWithCoordinate:center];
    [self.map addOverlay:overlay level:MKOverlayLevelAboveLabels];
    

}


-(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
    NSLog(@"zoom level =%f",[map getZoomLevel]);
    
    CLLocationCoordinate2D center;
    
    center.latitude = 12.8421;
    center.longitude = 77.6631;
    
    if([map getZoomLevel]>30)
    {
         [mapView setCenterCoordinate:center zoomLevel:15 animated:NO];
    }
    
}

- (MKCircleRenderer *)mapView:(MKMapView *)map viewForOverlay:(id <MKOverlay>)overlay
{
   if ([overlay isKindOfClass:[MyMapOverlay class]]) {
        MyMapOverlayRenderer *renderer = [[MyMapOverlayRenderer alloc] initWithOverlay:overlay];
        renderer.fillColor = [[UIColor blackColor] colorWithAlphaComponent:0.1];/// specify color which you want to use for gray out everything out of radius
        renderer.circleRect=circle.boundingMapRect;
       
        return (MKCircleRenderer *)renderer;
    }
    

    
    
    MKCircleRenderer *circleView = [[MKCircleRenderer alloc] initWithOverlay:overlay];
    circleView.strokeColor = [UIColor yellowColor];
    circleView.lineWidth=2.0;
    
    
    return circleView;
}


-(IBAction)btnPlusClick:(id)sender
{
    MKCoordinateRegion region;
    MKCoordinateSpan span;
    region.center.latitude = map.region.center.latitude;
    region.center.longitude = map.region.center.longitude;
    span.latitudeDelta=map.region.span.latitudeDelta /2.0002;
    span.longitudeDelta=map.region.span.longitudeDelta /2.0002;
    region.span=span;
    [map setRegion:region animated:TRUE];
}

-(IBAction)btnMinusClick:(id)sender
{
    MKCoordinateRegion region;
    MKCoordinateSpan span;
    region.center.latitude = map.region.center.latitude;
    region.center.longitude = map.region.center.longitude;
    span.latitudeDelta=map.region.span.latitudeDelta *2.0002;
    span.longitudeDelta=map.region.span.longitudeDelta *2.0002;
    region.span=span;
    [map setRegion:region animated:TRUE];
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

@end