Set custom image on pin in map in IOS - ios

i have a map view in my view controller, when i make a pin on the desired place i give my own image despite of the default image , but when i run and check the map it always shows default pin in my map instead of my image that i have passed. My code is this,
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
MKAnnotationView *view=[self.mapView dequeueReusableAnnotationViewWithIdentifier:#"annoView"];
if (!view) {
view=[[MKAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:#"annoView"];
if ([annotation isKindOfClass:[MKUserLocation class]]) {
return nil;
}else{
view.image=[UIImage imageNamed:#"car_marker.png"];
view.canShowCallout=YES;
}
}
return view;
}
MapPin *pin=[[MapPin alloc]init];
pin.title=loc[#"name"];
pin.subtitle=nil;
pin.coordinate=annoCordinate;
[self.mapView addAnnotation:pin];
[self.mapView setCenterCoordinate:pin.coordinate animated:YES];

This is very easy to accomplish, you just need to subclass MKAnnotationView and add a custom image view to it. A good example can be found here.
In case the link dies, I have also included the code snippet below, please do show the OP some love though!
#interface TStickerAnnotationView : MKAnnotationView
#property(nonatomic) float stickerColor;
#end
#interface TStickerAnnotationView () {
UIImageView *_imageView;
TCircleView *_circleView;
}
#end
#implementation TStickerAnnotationView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// make sure the x and y of the CGRect are half it's
// width and height, so the callout shows when user clicks
// in the middle of the image
CGRect viewRect = CGRectMake(-30, -30, 60, 60);
TCircleView* circleView = [[TCircleView alloc] initWithFrame:viewRect];
_circleView = circleView;
[self addSubview:circleView];
UIImageView* imageView = [[UIImageView alloc] initWithFrame:viewRect];
// keeps the image dimensions correct
// so if you have a rectangle image, it will show up as a rectangle,
// instead of being resized into a square
imageView.contentMode = UIViewContentModeScaleAspectFit;
_imageView = imageView;
[self addSubview:imageView];
}
return self;
}
- (void)setImage:(UIImage *)image
{
// when an image is set for the annotation view,
// it actually adds the image to the image view
_imageView.image = image;
}
- (void)stickerColor:(float)color {
_circleView.green = color;
[_circleView setNeedsDisplay];
}

Related

MKMarkerAnnotationView Doesn't Show

Setting up a map view with annotation of nearby cafes.
But when I try to custom the annotations using MKMarkerAnnotationView, nothing shows on the map view.
and when I log the marker, the frame is (0 0; 0 0);
I tried pin too, still didn't work.
I set up delegate on storyboard already.
I also debugged the view controller there are marker views but it is not displaying them because the width and height are zero ?
- (void)viewDidLoad {
[super viewDidLoad];
[self.cafeMap setShowsUserLocation:YES];
[self.locationManager requestWhenInUseAuthorization];
self.cafes = [NSMutableArray array];
[self fetchData];
[self.cafeMap registerClass:[MKAnnotationView class] forAnnotationViewWithReuseIdentifier:#"marker"];
}
-(void)fetchData{
[self.networkManager fetchCafeData:^(NSArray * _Nonnull businesses) {
for (NSDictionary* cafeInfo in businesses) {
Cafe *cafe = [[Cafe alloc]initWithCafeInfo:cafeInfo];
[self.cafes addObject:cafe];
}
for (Cafe *cafe in self.cafes) {
[self.cafeMap addAnnotation:cafe];
}
[self.cafeMap showAnnotations:self.cafes animated:YES];
}];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKUserLocation class]]) {
return nil;
}
MKMarkerAnnotationView *marker = [[MKMarkerAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:#"marker"];
marker = (MKMarkerAnnotationView*) [mapView dequeueReusableAnnotationViewWithIdentifier:#"marker" forAnnotation:annotation];
UIButton *button = [UIButton buttonWithType:UIButtonTypeInfoLight];
marker.rightCalloutAccessoryView = button;
[marker setEnabled:YES];
[marker setCanShowCallout:YES];
NSLog(#"MARKER:%#",marker);
return marker;
}
This is the output:
MARKER:<MKAnnotationView: 0x7f9fa3f333b0; frame = (0 0; 0 0); layer = <CALayer: 0x60000253d220>>
Note that your message says that it is a MKAnnotationView. That is because you have registered MKAnnotationView for your identifier rather than MKMarkerAnnotationView. You want:
[self.cafeMap registerClass:[MKMarkerAnnotationView class] forAnnotationViewWithReuseIdentifier:#"marker"];
As an aside, you should be able to simplify viewForAnnotation to:
MKAnnotationView *marker = [mapView dequeueReusableAnnotationViewWithIdentifier:#"marker" forAnnotation:annotation];
Personally, I’d move the configuration of the annotation view into its own subclass:
static NSString * const cafeClusteringIdentifier = #"cafe";
#interface CafeAnnotationView: MKMarkerAnnotationView
#end
#implementation CafeAnnotationView
- (instancetype)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier]) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeInfoLight];
self.rightCalloutAccessoryView = button;
self.canShowCallout = true;
self.clusteringIdentifier = cafeClusteringIdentifier;
}
return self;
}
- (void)setAnnotation:(id<MKAnnotation>)annotation {
[super setAnnotation:annotation];
self.clusteringIdentifier = cafeClusteringIdentifier;
}
#end
By doing this, I avoid bloating my view controller with code for configuring annotation views.
Note, I’m setting the clusteringIdentifier so that you enjoy that behavior of the MKMarkerAnnotationView.
And then I’d register that class for MKMapViewDefaultAnnotationViewReuseIdentifier:
[self.cafeMap registerClass:[CafeAnnotationView class] forAnnotationViewWithReuseIdentifier:MKMapViewDefaultAnnotationViewReuseIdentifier];
The benefit of using MKMapViewDefaultAnnotationViewReuseIdentifier is that you don’t have to implement viewForAnnotation at all. Delete your implementation of that method entirely. In iOS 11 and later, you only need to implement viewForAnnotation if you need to do something special like having multiple custom reuse identifiers for multiple types of annotations.
Anyway, that yields:

MKAnnotationView Subviews

Currently, I am having an issue with my project in implementing a custom MKAnnotationView that has multiple custom UIImageViews. So these custom UIImageViews have a clear button on top of them to not have to add gesture recognizers.
As you can see, it would be beneficial to actually tap the MKAnnotationView subviews and have some action happen.
I implemented a protocol for the MKAnnotationView where each image subview within the MKAnnotationView makes a callback to the controller that is the owner of the MKMapView... Heres the code...
PHProfileImageView *image = [[PHProfileImageView alloc] initWithFrame:CGRectMake(newX - radius / 5.0f, newY - radius / 5.0f, width, height)];
[image setFile:[object objectForKey:kPHEventPictureKey]];
[image.layer setCornerRadius:image.frame.size.height/2];
[image.layer setBorderColor:[[UIColor whiteColor] CGColor]];
[image.layer setBorderWidth:2.0f];
[image.layer setMasksToBounds:YES];
[image.profileButton setTag:i];
[image.profileButton addTarget:self action:#selector(didTapEvent:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:image];
- (void)didTapEvent:(UIButton *)button
{
NSLog(#"%#", [self.pins objectAtIndex:button.tag]);
if (self.delegate && [self.delegate respondsToSelector:#selector(didTapEvent:)]) {
[self.delegate JSClusterAnnotationView:self didTapEvent:[self.pins objectAtIndex:button.tag]];
}
}
So as you can see, I already attempt to log the result of the tapped image but nothing :(. Is the way I'm implementing this not the way to go? Am I supposed to have CAShapeLayers or something? Not really sure at this point. Anyone got any ideas?
Edit
Im thinking that I might have to implement a custom callout view. Since a callout view actually adds buttons to its view and can respond to touch events... Not totally sure though because callouts are only shown once the annotation view is tapped. And in this case, the ACTUAL annotation view is the middle label
So I resized the mkannotationview's frame to a much larger frame and apparently all the subviews are actually not within the MKAnnotationView's bounds, so the subviews aren't actually being tapped. Now that Im thinking about this solution, it probably wasn't the best solution.
If anyone has any suggestions rather than adding subviews to a MKAnnotationView to create the view I currently have, that would be great!
For the Custom AnnotationView with Clickable Buttons, you have to create custom AnnotationView SubClass in the Project. For that create a new file.
And add these two methods to the implementation file.
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
UIView* hitView = [super hitTest:point withEvent:event];
if (hitView != nil)
{
[self.superview bringSubviewToFront:self];
}
return hitView;
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event
{
CGRect rect = self.bounds;
BOOL isInside = CGRectContainsPoint(rect, point);
if(!isInside)
{
for (UIView *view in self.subviews)
{
isInside = CGRectContainsPoint(view.frame, point);
if(isInside)
break;
}
}
return isInside;
}
Then go to the ViewController.m file again and modify the viewDidLoad method as this.
- (void)viewDidLoad {
[super viewDidLoad];
self.mapKit.delegate = self;
//Set Default location to zoom
CLLocationCoordinate2D noLocation = CLLocationCoordinate2DMake(51.900708, -2.083160); //Create the CLLocation from user cordinates
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(noLocation, 50000, 50000); //Set zooming level
MKCoordinateRegion adjustedRegion = [self.mapKit regionThatFits:viewRegion]; //add location to map
[self.mapKit setRegion:adjustedRegion animated:YES]; // create animation zooming
// Place Annotation Point
MKPointAnnotation *annotation1 = [[MKPointAnnotation alloc] init]; //Setting Sample location Annotation
[annotation1 setCoordinate:CLLocationCoordinate2DMake(51.900708, -2.083160)]; //Add cordinates
[self.mapKit addAnnotation:annotation1];
}
Now add that custom View to the ViewController.xib.
Now create this delegate method as below.
#pragma mark : MKMapKit Delegate
-(MKAnnotationView *)mapView:(MKMapView *)mV viewForAnnotation:(id <MKAnnotation>)annotation
{
AnnotationView *pinView = nil; //create MKAnnotationView Property
static NSString *defaultPinID = #"com.invasivecode.pin"; //Get the ID to change the pin
pinView = (AnnotationView *)[self.mapKit dequeueReusableAnnotationViewWithIdentifier:defaultPinID]; //Setting custom MKAnnotationView to the ID
if ( pinView == nil )
pinView = [[AnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:defaultPinID]; // init pinView with ID
[pinView addSubview:self.customView];
addSubview:self.customView.center = CGPointMake(self.customView.bounds.size.width*0.1f, -self.customView.bounds.size.height*0.5f);
pinView.image = [UIImage imageNamed:#"Pin"]; //Set the image to pinView
return pinView;
}
I also got this answer few months ago from someone posted on Stackoverflow. I modified it to my project as I want. Hope this will do your work.

How to add custom transparent MKAnnotation to MKMapView?

So I am trying to replicate the following scenario (translucent annotation views) :
And I have tried unsuccessfully the following implementations:
1- Creating a custom image with 30% opacity and adding to the map ---> Result: The image stays opaque.
Code:
-(id)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if (self) {
LLAnnotation *myA = (LLAnnotation*) annotation;
self.accessibilityLabel = myA.title;
self.annotation = myA;
self.enabled = YES;
self.canShowCallout = YES;
self.centerOffset = CGPointMake(5,-10);
self.backgroundColor = [UIColor yellowColor];
self.image = [UIImage imageNamed:#"circle"];
}
return self;
}`
And then adding it in - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id )annotation_
2- Adding a sublayer to the AnnotationView and clearing it ---> Result: Doesn't show any annotation.
Code:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation_
{
if (annotation_ == mapView.userLocation) return nil;
MKAnnotationView *m = [[MKAnnotationView alloc] initWithAnnotation:annotation_ reuseIdentifier:#"default"];
// m.backgroundColor = [UIColor clearColor];
CALayer *layer = [[CALayer alloc]init];
layer.frame = m.frame;
layer.backgroundColor = [UIColor lightGreenColor].CGColor;
[m.layer addSublayer:layer];
m.layer.cornerRadius = m.frame.size.width/2;
m.layer.borderWidth = 2;
m.layer.masksToBounds = YES;
return m;
}
I was thinking that adding MKOverlays on top of annotations maybe a workaround but it shouldn't be the way to go I believe.
Does anyone have other suggestions on how to implement this?
Create UIImageView object and make it looks like the image you required.
Add as subview of annotationView in viewForAnnotation delegate method will do the trick.
Also you need to set center position offset for annotation image to render annotation exactly correct position of location.
Have look on below code:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation_
{
if (annotation_ == mapView.userLocation) return nil;
MKAnnotationView *m = [[MKAnnotationView alloc] initWithAnnotation:annotation_ reuseIdentifier:#"default"];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(m.center.x, m.center.y, 20, 20)];
[imageView setBackgroundColor:[UIColor colorWithRed:0.7294 green:0.7843 blue:0.1921 alpha:1.0]];
imageView.layer.cornerRadius = imageView.frame.size.width / 2;
imageView.alpha = 0.6f;
[m addSubview:imageView];
// Also set center offset for annotation
[m setCenterOffset:CGPointMake(-10, -20)];
return m;
}
What I would do is create an image in photoshop which has a transparent background, and then add your desired yellow circle on top. Then make the opacity of that circle to what ever opacity you want. Save the image as a PNG.
Once you have saved the image, add it to your Xcode project. Once you've added it, add the following line under your viewForAnnotation.
annotationView.image = [UIImage imageNamed:#"ThisIsThePNGImagesName.png"];
Hope that helps :)

MKAnnotationView with custom view and image view

In my Map application, instead of showing a pin, I want to show a colored background circle with image in it. The color of the background (which is shade of green in below image) circle is dynamic. It will look as in below image:
I created TCircleView which draws the color in "drawRect"
To show similar annotation, I created object of TCircleView and UIImageView and add them to MKAnnotationView object. Its looking good and visible as expected.
But its not allowing to detect tap/touch to show the call out.
I'm using the below code:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKPointAnnotation class]]) {
return nil;
}
static NSString *annotationIdentifier = #"StickerPin";
MKAnnotationView *annotationView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
if (!annotationView) {
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:annotationIdentifier];
annotationView.canShowCallout = YES;
}
TCircleView* circleView = [[TCircleView alloc] init];
circleView.green = [postObj[#"severity"] floatValue]; //dynamic value coming from server
UIImageView* imgView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Piano"]];
CGRect r = imgView.frame;
r.size.height = r.size.width = 60;
imgView.frame = r;
circleView.frame = r;
[annotationView addSubview:circleView];
[annotationView addSubview:imgView];
return annotationView;
}
Its not allowing to show the callout or not even calling the delegate "didSelectAnnotationView:"
How to show the custom view as annotation on the map?
The default frame width and height for MKAnnotationView is 0,0.
This is most likely preventing it from responding to touches.
Normally, if you set its image property, the frame is automatically set for you.
Since you're not setting the image and adding subviews instead, try manually setting frame to be at least as big as its largest subview.
For example:
imgView.frame = r;
circleView.frame = r;
annotationView.frame = r; // <-- add this line
I created a sub class of annotation view and achieved it. The code is below:
#interface TStickerAnnotationView : MKAnnotationView
#property(nonatomic) float stickerColor;
#end
#interface TStickerAnnotationView () {
UIImageView *_imageView;
TCircleView *_circleView;
}
#end
#implementation TStickerAnnotationView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// make sure the x and y of the CGRect are half it's
// width and height, so the callout shows when user clicks
// in the middle of the image
CGRect viewRect = CGRectMake(-30, -30, 60, 60);
TCircleView* circleView = [[TCircleView alloc] initWithFrame:viewRect];
_circleView = circleView;
[self addSubview:circleView];
UIImageView* imageView = [[UIImageView alloc] initWithFrame:viewRect];
// keeps the image dimensions correct
// so if you have a rectangle image, it will show up as a rectangle,
// instead of being resized into a square
imageView.contentMode = UIViewContentModeScaleAspectFit;
_imageView = imageView;
[self addSubview:imageView];
}
return self;
}
- (void)setImage:(UIImage *)image
{
// when an image is set for the annotation view,
// it actually adds the image to the image view
_imageView.image = image;
}
- (void)stickerColor:(float)color {
_circleView.green = color;
[_circleView setNeedsDisplay];
}
Try looking at this solution- I found it relevant
http://blog.jaanussiim.com/2014/01/28/floating-annotations.html

MKAnnotation goes out of mapview when dragging

I have a view with a little mapView inside. I have a custom annotationView to mark the user position. Everything works ok as usual but I've realize that the annotation is not clipped to the map view so, when I drag the map, it goes out of bounds. Check the pictures:
The mapView has the Clip Subviews enabled. I think this is the first time I found this behavior. Maybe I've never placed a map inside a bigger view with space around. I'm targeting iOS7 by the way.
EDIT: Here's the code. Nothing out of ordinary, I think.
Here, I add the custom annotation:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
static NSString *const kAnnotationIdentifier = #"DYNAnnotationView";
DYNAnnotationView *annotationView = (DYNAnnotationView *)
[mapView dequeueReusableAnnotationViewWithIdentifier:kAnnotationIdentifier];
if (! annotationView)
{
annotationView = [[DYNAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:kAnnotationIdentifier];
}
[annotationView setAnnotation:annotation];
return annotationView;
}
And here is the custom annotationView:
DYNAnnotation.h
#import <MapKit/MapKit.h>
#interface DYNAnnotationView : MKAnnotationView
- (id)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier;
#end
DYNAnnotation.m
#import "DYNAnnotationView.h"
#implementation DYNAnnotationView
-(id)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if (self)
{
UIImage *image = [UIImage imageNamed:#"locationMark"];
CGRect frame = [self frame];
frame.size = [image size];
[self setFrame:frame];
[self setCenterOffset:CGPointMake(0.5f, 1.0f)];
[self setImage:image];
}
return self;
}
OK. The solution was easy after all. All I needed to do was embed the mapView inside another view and set its clip subview property to YES. Still don't understand why the map view doesn't take care of this.
Hope this help somebody.

Resources