Add circle with blur effect on a MKMapView
Asked Answered
S

2

13

I'm trying to add a blur effect on a overlay on a map. I actually need the blur effect over a circle on the over the map, the method I use to get that is not that important.

I have a class that extends from MKCircleRenderer and I wanted to add a blur effect over the map that it covers.

I was trying using the -fillPath:inContext: method, but my ignorance over Core Graphics and Core Image lead me to nowhere and I'm really really lost about this issue.

My attempt was using the CIFilter and for that I needed a CIImage which I tried to create from the context. But I found no way to create a CGBitmapContext, CGImage nor any other class from the context. Any method I tried resulted on NULL with no further details about why. I can't remember all I tried so I'm sorry about not pointing anything about that.

My class currently implements one methods that does not do really much:

- (instancetype)initWithOverlay:(id<MKOverlay>)overlay {
    if (self = [super initWithOverlay:overlay]) {
        self.strokeColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:1];
        self.fillColor = [UIColor colorWithRed:0.4 green:0.2 blue:0.2 alpha:0.1];
        self.lineWidth = 1;
    }
    return self;
}

An alternative could be using a custom MKAnnotation and add the blur effect over the view with the UIVisualEffectView. The hard part with this approach is increasing/decreasing the size when zooming.

This should work on iOS 8+

Edit

In this case, the map behind the inside of the circle should be blurred

Simard answered 21/10, 2015 at 16:3 Comment(3)
can you provide a screenshot of what exactly you want to achieve? Blur the map except for a specific circle? Or only blur a circle? Why is it zoom-dependant?Exaggerated
It would be better if show a screenshot of what you needGelinas
Blur the circle. I'd like to have an area covered by a blur circle. The area should have a radius of a quarter mile, so if someone zooms in/out it must change its size. I'll be adding an image soon.Nabob
S
2

So I ended up using a UIVisualEffectView on top of the overlay. The trick was in using a CADisplayLink for keeping the view on place.

Here is some code example that does the job (it ignores a few things that should be taken in consideration when actually doing this on an app, like removing the link, keeping track that what is done on viewDidAppear has to be paired with probably symmetrical work on viewWillDissapear or something, I could use viewDidLoad I think, but did it this way when testing).

#import "ViewController.h"
#import <MapKit/MapKit.h>
#import <QuartzCore/QuartzCore.h>

@interface ViewController () {
    IBOutlet MKMapView *map;
    UIView *ov;
    MKCircle *c;
    UIVisualEffectView *ev;
}

@end

@implementation ViewController

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    CLLocationCoordinate2D center = CLLocationCoordinate2DMake(40.7828647,-73.9675438);
    c = [MKCircle circleWithCenterCoordinate:center radius:1000];
    [map addOverlay:c];
    MKCoordinateSpan span = MKCoordinateSpanMake(0.07, 0.07);
    MKCoordinateRegion region = MKCoordinateRegionMake(center, span);
    region = [map regionThatFits:region];
    [map setRegion:region animated:YES];

    ov = [[UIView alloc] init];
    ov.translatesAutoresizingMaskIntoConstraints = NO;
    ov.backgroundColor = [UIColor clearColor];
    ov.clipsToBounds = YES;
    ov.layer.borderWidth = 1;
    ov.layer.borderColor = [UIColor blackColor].CGColor;
    [map addSubview:ov];

    UIBlurEffect *blur = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
    ev = [[UIVisualEffectView alloc] initWithEffect:blur];
    [ov addSubview:ev];

    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(update:)];
    [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}

- (void)update:(CADisplayLink *)link {
    ov.frame = [map convertRegion:MKCoordinateRegionForMapRect(c.boundingMapRect) toRectToView:map];
    ov.layer.cornerRadius = ov.frame.size.height / 2;
    ev.frame = CGRectMake(0, 0, ov.frame.size.width, ov.frame.size.height);
}

@end

Edit: Screenshot

Simard answered 4/11, 2015 at 22:28 Comment(0)
H
2

There are two ways to do that.

One is simple, you can just use Image.

You might find the following images useful:

enter image description here enter image description here enter image description here enter image description here enter image description here

and the code to use them in viewForAnnotation:

- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation
{   
    // ... get the annotation delegate and allocate the MKAnnotationView (annView)
        if ([annotationDelegate.type localizedCaseInsensitiveCompare:@"NeedsBluePin"] == NSOrderedSame)
        {
            UIImage * image = [UIImage imageNamed:@"blue_pin.png"];
            UIImageView *imageView = [[[UIImageView alloc] initWithImage:image] autorelease];
            [annView addSubview:imageView];
        }
}

Other method do that programmatically [make circle yourself]

This code can help you.

import UIKit
import AddressBook
import AddressBookUI
import MapKit
import CoreLocation
import MessageUI
import Social

class ViewController: UIViewController, ABPeoplePickerNavigationControllerDelegate, MFMailComposeViewControllerDelegate, MKMapViewDelegate {

    @IBOutlet weak var name: UILabel!
    @IBOutlet weak var email: UILabel!
    @IBOutlet weak var photo: UIImageView!
    @IBOutlet weak var map: MKMapView!

    let locMan:CLLocationManager=CLLocationManager()

    // Blurring Code
    @IBOutlet weak var labelBackground: UIView!
    var backgroundBlur: UIVisualEffectView!


    @IBAction func newBFF(sender: AnyObject) {
        let picker: ABPeoplePickerNavigationController =
            ABPeoplePickerNavigationController()
        picker.peoplePickerDelegate = self
        presentViewController(picker, animated: true, completion: nil)
    }

    @IBAction func sendEmail(sender: AnyObject) {
        var emailAddresses:[String]=[self.email.text!]
        var mailComposer:MFMailComposeViewController =
            MFMailComposeViewController()
        mailComposer.mailComposeDelegate=self;
        mailComposer.setToRecipients(emailAddresses)

        presentViewController(mailComposer, animated: true, completion: nil)
    }

    func mailComposeController(controller: MFMailComposeViewController!,
        didFinishWithResult result: MFMailComposeResult, error: NSError!) {
        dismissViewControllerAnimated(true, completion: nil)
    }

    func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, didSelectPerson person: ABRecord!) {

        let friendName:String = ABRecordCopyValue(person, kABPersonFirstNameProperty).takeRetainedValue() as String as String
        name.text=friendName

        let friendAddressSet:ABMultiValueRef = ABRecordCopyValue(person, kABPersonAddressProperty).takeRetainedValue()

        if ABMultiValueGetCount(friendAddressSet)>0 {
            let friendFirstAddress: Dictionary = ABMultiValueCopyValueAtIndex(friendAddressSet, 0).takeRetainedValue() as NSDictionary
            showAddress(friendFirstAddress)
        }

        let friendEmailAddresses:ABMultiValueRef = ABRecordCopyValue(person, kABPersonEmailProperty).takeRetainedValue()

        if ABMultiValueGetCount(friendEmailAddresses)>0 {
            let friendEmail: String = ABMultiValueCopyValueAtIndex(friendEmailAddresses, 0).takeRetainedValue() as String
            email.text=friendEmail
        }

        if ABPersonHasImageData(person) {
            photo.image = UIImage(data: ABPersonCopyImageData(person).takeRetainedValue())
        }
    }

    func showAddress(fullAddress:NSDictionary) {
        let geocoder: CLGeocoder = CLGeocoder()
        geocoder.geocodeAddressDictionary(fullAddress, completionHandler:
            {(placemarks: [AnyObject]!, error: NSError!) -> Void in
                let friendPlacemark:CLPlacemark = placemarks[0] as CLPlacemark
                let mapRegion:MKCoordinateRegion =
                    MKCoordinateRegion(center: friendPlacemark.location.coordinate,
                        span: MKCoordinateSpanMake(0.2, 0.2))
                self.map.setRegion(mapRegion, animated: true)
                let mapPlacemark: MKPlacemark = MKPlacemark(placemark: friendPlacemark)
                self.map.addAnnotation(mapPlacemark)
        })
    }

    func mapView(aMapView: MKMapView!,
        viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
            let pinDrop:MKPinAnnotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "myspot")
            pinDrop.animatesDrop=true
            pinDrop.canShowCallout=true
            pinDrop.pinColor=MKPinAnnotationColor.Purple
            return pinDrop
    }

    @IBAction func sendTweet(sender: AnyObject) {
        let geocoder: CLGeocoder = CLGeocoder()
        geocoder.reverseGeocodeLocation(map.userLocation.location, completionHandler:
            {(placemarks: [AnyObject]!, error: NSError!) -> Void in
                let myPlacemark:CLPlacemark = placemarks[0] as CLPlacemark
                let tweetText:String =
                    "Hello all - I'm currently in \(myPlacemark.locality)!"

                let tweetComposer: SLComposeViewController =
                    SLComposeViewController(forServiceType: SLServiceTypeTwitter)

                if SLComposeViewController.isAvailableForServiceType(SLServiceTypeTwitter) {
                    tweetComposer.setInitialText(tweetText)
                    self.presentViewController(tweetComposer, animated: true, completion: nil)
                }
        })
    }


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        //let locMan:CLLocationManager=CLLocationManager()
        locMan.requestWhenInUseAuthorization()

        let blur: UIBlurEffect = UIBlurEffect(style: UIBlurEffectStyle.Light)
        backgroundBlur = UIVisualEffectView (effect: blur)
        backgroundBlur.frame = labelBackground.frame
        view.insertSubview(backgroundBlur, belowSubview: labelBackground)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    override func preferredStatusBarStyle() -> UIStatusBarStyle {
        return UIStatusBarStyle.LightContent
    }


}
Hershelhershell answered 28/10, 2015 at 6:33 Comment(2)
Maybe I expressed something wrong, but on the edit is said correctly. I need to blur the map behind the circle not a circle floating over the map :/ Thanks anyway.Nabob
Also the view for annotation will ignore zoom AFAIK. If I zoom in or out the view will show the same size on the screen, but not relative to the actual stuff shown on the map.Nabob
S
2

So I ended up using a UIVisualEffectView on top of the overlay. The trick was in using a CADisplayLink for keeping the view on place.

Here is some code example that does the job (it ignores a few things that should be taken in consideration when actually doing this on an app, like removing the link, keeping track that what is done on viewDidAppear has to be paired with probably symmetrical work on viewWillDissapear or something, I could use viewDidLoad I think, but did it this way when testing).

#import "ViewController.h"
#import <MapKit/MapKit.h>
#import <QuartzCore/QuartzCore.h>

@interface ViewController () {
    IBOutlet MKMapView *map;
    UIView *ov;
    MKCircle *c;
    UIVisualEffectView *ev;
}

@end

@implementation ViewController

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    CLLocationCoordinate2D center = CLLocationCoordinate2DMake(40.7828647,-73.9675438);
    c = [MKCircle circleWithCenterCoordinate:center radius:1000];
    [map addOverlay:c];
    MKCoordinateSpan span = MKCoordinateSpanMake(0.07, 0.07);
    MKCoordinateRegion region = MKCoordinateRegionMake(center, span);
    region = [map regionThatFits:region];
    [map setRegion:region animated:YES];

    ov = [[UIView alloc] init];
    ov.translatesAutoresizingMaskIntoConstraints = NO;
    ov.backgroundColor = [UIColor clearColor];
    ov.clipsToBounds = YES;
    ov.layer.borderWidth = 1;
    ov.layer.borderColor = [UIColor blackColor].CGColor;
    [map addSubview:ov];

    UIBlurEffect *blur = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
    ev = [[UIVisualEffectView alloc] initWithEffect:blur];
    [ov addSubview:ev];

    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(update:)];
    [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}

- (void)update:(CADisplayLink *)link {
    ov.frame = [map convertRegion:MKCoordinateRegionForMapRect(c.boundingMapRect) toRectToView:map];
    ov.layer.cornerRadius = ov.frame.size.height / 2;
    ev.frame = CGRectMake(0, 0, ov.frame.size.width, ov.frame.size.height);
}

@end

Edit: Screenshot

Simard answered 4/11, 2015 at 22:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.