How to Create Rounded Image with Border and Shadow as Mkannotationview in Swift

Border out (not IN) of custom annotation swift when tapped

If you implement the following two methods to detect when an annotation is selected/deselected, you can draw/remove a white circle as a UIView beneath your annotation. First in your view controller, set the mapView's delegate.

self.mapView.delegate = self

Beneath are the two methods to detect when an annotation is clicked.

func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
/*Make a white circle*/
let whiteBackground = UIView(frame: CGRect(x: -27, y: -55, width: 80, height: 80))
whiteBackground.backgroundColor = UIColor.white
whiteBackground.layer.cornerRadius = 40

/*Set circle's tag to 1*/
whiteBackground.tag = 1
/*Add the circle beneath the annotation*/
view.insertSubview(whiteBackground, at: 0)
}

func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
/*Removing the white circle*/
view.viewWithTag(1)?.removeFromSuperview()
}

Explanation. When an annotation is clicked, the didSelect method is called, and a white circle is created from a UIView. This white circle view has a tag of 1 for convenience when it needs to be removed. Then this view will be inserted as a subview having the annotation's view as the superview at index zero to make it be shown behind the annotation. The didDeselect method removes the circle by referencing the tag.

Personally, you're better off just inserting a white circle as an UIImageView instead of making a circle from a UIView, as it's less hack-ish.

Demo

Map Annotation with white circle

UIView extension in Swift

You're making an extension for UIView. Not UIImageView

extension UIImageView {

func addShadow() {
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 0)
layer.shadowOpacity = 0.5
layer.shadowRadius = 5
clipsToBounds = false
}
}

Also please use the UIImageVIew directly, not the UIImage

EDIT

Assuming by your comments, i think you're trying to modify an MKAnnotationView. If so here's a question that will help you.

How to create rounded image with border and shadow as MKAnnotationView in Swift?

Draw glow around inside edge of multiple CGPaths

What you are asking for is the union of bezier paths. Apple doesn't ship any APIs for computing the union of paths. It is in fact a rather complicated algorithm. Here are a couple of links:

  • http://www.cocoadev.com/index.pl?NSBezierPathcombinatorics
  • http://losingfight.com/blog/2011/07/09/how-to-implement-boolean-operations-on-bezier-paths-part-3/

If you explain what you want to do with the union path, we might be able to suggest some alternatives that don't require actually computing the union.

You can draw a pretty decent inner glow without actually computing the union of the paths. Instead, make a bitmap. Fill each path on the bitmap. You'll use this as the mask. Next, create an inverted image of the mask, that has everything outside of the union area filled. You'll draw this to make CoreGraphics draw a shadow around the inner edge of the union. Finally, set the mask as your CGContext mask, set the shadow parameters, and draw the inverted image.

Ok, that sounds complicated. But here's what it looks like (Retina version on the right):

glow glow retina

It's not perfect (too light at the corners), but it's pretty good.

So here's the code. I'm passing around UIBezierPaths instead of CGPaths, but it's trivial to convert between them. I use a some UIKit functions and objects. Remember that you can always make UIKit draw to an arbitrary CGContext using UIGraphicsPushContext and UIGraphicsPopContext.

First, we need an mask image. It should be an alpha-channel-only image that is 1 inside any of the paths, and 0 outside all of the paths. This method returns such an image:

- (UIImage *)maskWithPaths:(NSArray *)paths bounds:(CGRect)bounds
{
// Get the scale for good results on Retina screens.
CGFloat scale = [UIScreen mainScreen].scale;
CGSize scaledSize = CGSizeMake(bounds.size.width * scale, bounds.size.height * scale);

// Create the bitmap with just an alpha channel.
// When created, it has value 0 at every pixel.
CGContextRef gc = CGBitmapContextCreate(NULL, scaledSize.width, scaledSize.height, 8, scaledSize.width, NULL, kCGImageAlphaOnly);

// Adjust the current transform matrix for the screen scale.
CGContextScaleCTM(gc, scale, scale);
// Adjust the CTM in case the bounds origin isn't zero.
CGContextTranslateCTM(gc, -bounds.origin.x, -bounds.origin.y);

// whiteColor has all components 1, including alpha.
CGContextSetFillColorWithColor(gc, [UIColor whiteColor].CGColor);

// Fill each path into the mask.
for (UIBezierPath *path in paths) {
CGContextBeginPath(gc);
CGContextAddPath(gc, path.CGPath);
CGContextFillPath(gc);
}

// Turn the bitmap context into a UIImage.
CGImageRef cgImage = CGBitmapContextCreateImage(gc);
CGContextRelease(gc);
UIImage *image = [UIImage imageWithCGImage:cgImage scale:scale orientation:UIImageOrientationDownMirrored];
CGImageRelease(cgImage);
return image;
}

That was actually the hard part. Now we need an image that is our glow color everywhere outside of the mask (path union) area. We can use UIKit functions to make this easier than a pure CoreGraphics approach:

- (UIImage *)invertedImageWithMask:(UIImage *)mask color:(UIColor *)color
{
CGRect rect = { CGPointZero, mask.size };
UIGraphicsBeginImageContextWithOptions(rect.size, NO, mask.scale); {
// Fill the entire image with color.
[color setFill];
UIRectFill(rect);
// Now erase the masked part.
CGContextClipToMask(UIGraphicsGetCurrentContext(), rect, mask.CGImage);
CGContextClearRect(UIGraphicsGetCurrentContext(), rect);
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}

With those two images, we can draw an inner glow into the current UIKit graphics context for an array of paths:

- (void)drawInnerGlowWithPaths:(NSArray *)paths bounds:(CGRect)bounds color:(UIColor *)color offset:(CGSize)offset blur:(CGFloat)blur
{
UIImage *mask = [self maskWithPaths:paths bounds:bounds];
UIImage *invertedImage = [self invertedImageWithMask:mask color:color];
CGContextRef gc = UIGraphicsGetCurrentContext();

// Save the graphics state so I can restore the clip and
// shadow attributes after drawing.
CGContextSaveGState(gc); {
CGContextClipToMask(gc, bounds, mask.CGImage);
CGContextSetShadowWithColor(gc, offset, blur, color.CGColor);
[invertedImage drawInRect:bounds];
} CGContextRestoreGState(gc);
}

To test it, I created an image using a couple of circles and put it in a UIImageView:

- (void)viewDidLoad
{
[super viewDidLoad];

UIBezierPath *path1 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(20, 20, 60, 60)];
UIBezierPath *path2 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 50, 60, 60)];
NSArray *paths = [NSArray arrayWithObjects:path1, path2, nil];

UIGraphicsBeginImageContextWithOptions(self.imageView.bounds.size, NO, 0.0); {
[self drawInnerGlowWithPaths:paths bounds:self.imageView.bounds color:[UIColor colorWithHue:0 saturation:1 brightness:.8 alpha:.8] offset:CGSizeZero blur:10.0];
}
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}

Adding image to MKAnnotationView displaces pin

The code is creating an MKPinAnnotationView with a custom image.

The MKPinAnnotationView class should only be used to display the default pin images.

To show a custom image, it's better to use a plain MKAnnotationView.


Because the code is using an MKPinAnnotationView, the image is automatically getting an offset applied to it (the centerOffset property).

This built-in offset works for the default pin images but not for your custom image.

Rather than trying to override this default behavior, use a plain MKAnnotationView instead:

func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {

if annotation is StopAnnotation {

let identifier = "stopAnnotation"
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier)
if pinView == nil {
//println("Pinview was nil")

//Create a plain MKAnnotationView if using a custom image...
pinView = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier)

pinView!.canShowCallout = true
pinView.image = UIImage(named: "stopIcon")
}
else {
//Unrelated to the image problem but...
//Update the annotation reference if re-using a view...
pinView.annotation = annotation
}

return pinView
}
return nil
}

How do I draw a shadow under a UIView?

In your current code, you save the GState of the current context, configure it to draw a shadow .. and the restore it to what it was before you configured it to draw a shadow. Then, finally, you invoke the superclass's implementation of drawRect: .

Any drawing that should be affected by the shadow setting needs to happen after

CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);

but before

CGContextRestoreGState(currentContext);

So if you want the superclass's drawRect: to be 'wrapped' in a shadow, then how about if you rearrange your code like this?

- (void)drawRect:(CGRect)rect {
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(currentContext);
CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
[super drawRect: rect];
CGContextRestoreGState(currentContext);
}


Related Topics



Leave a reply



Submit