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 :)
Related
I'm currently building an "Uber-like" application, not in the same business but same design, and I would like to know what should I do if I want to represent their "Set pickup location" View/Button.
I already saw this post : Customize MKAnnotation Callout View? which was about Custom Annotation Callout View
In older version it looked like a Custom Callout View, but now it's a bit different, and I don't know where to start if I want to have the same result :
Thanks for your help !
To set different image on left accessory and right accessory, use this code.
// set different pins colors
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
MKAnnotationView *annotationView = nil;
if( [annotation isKindOfClass:[YOURANNOTATION class] ] )
{
static NSString * AnnotationID = #"YOURANNOTATION";
annotationView = [self.mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationID];
if( annotationView == nil )
{
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:AnnotationID] ;
}
UIImage * flagImage = nil;
flagImage = [UIImage imageNamed:#"marker-map#1x.png"];
[annotationView setImage:flagImage];
annotationView.canShowCallout = YES;
// add an image to the callout window
UIImageView *leftIconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"marker-map#1x.png"]];
annotationView.leftCalloutAccessoryView = leftIconView;
//adding right button accessory
UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annotationView.rightCalloutAccessoryView = infoButton;
//image size and adding image on left accessory
CGRect resizeRect;
resizeRect.size = flagImage.size;
resizeRect.origin = (CGPoint){0.0f, 0.0f};
UIGraphicsBeginImageContext(resizeRect.size);
[flagImage drawInRect:resizeRect];
UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
annotationView.image = resizedImage;
}
return annotationView;
}
Then to call selected annotation
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
YOURANNOTATION *annotation=(YOURANNOTATION*)view.annotation;
//do something with your annotation
}
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.
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
I am now at a point of teaching myself the use of MapKit in Objective C. I got to the point where I can autozoom to a location that contains several annotations. I represent the annotations with the built in pins. If you click on the pin I have a an alphanumeric 2 character string to represent that spot.
I thought to myself, for better usability, why not replace the pins with the actual data. Kind of like a weathermap where they show the temperature as a pin. Is this doable?
I researched this and all I could find is this:
- (MKAnnotationView *)mapView:(MKMapView *)map viewForAnnotation:(id <MKAnnotation>)annotation
{
static NSString *AnnotationViewID = #"annotationViewID";
MKAnnotationView *annotationView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationViewID];
if (annotationView == nil) {
annotationView = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationViewID] autorelease];
}
annotationView.image = [UIImage imageNamed:#"location.png"];
annotationView.annotation = annotation;
return annotationView;
}
The problem is that I cannot and should not have to have a custom image for each combination of the two characters. Is there a way for me to draw these numbers at the location of the pin.
I found this reference:
https://developer.apple.com/library/ios/documentation/userexperience/conceptual/LocationAwarenessPG/AnnotatingMaps/AnnotatingMaps.html
Am I on the right track. Are there some full examples I can leverage to better understand the flow.
I want to be able to select that custom pin however and segue or show more details.
Thank you for your time.
Anna, thank you for the link, I tried it and it worked. I made some changes to improve the esthetic of the label and I thought I would post my findings here for reference:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id)annotation {
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString *reuseId = #"MapViewController";
MKAnnotationView *av = [mapView dequeueReusableAnnotationViewWithIdentifier:reuseId];
if (av == nil)
{
av = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseId];
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 20)];
//UILabel *lbl = [[UILabel alloc] init];;
lbl.backgroundColor = [UIColor clearColor]; // This makes the background clear and just shows the text
lbl.textColor = [UIColor whiteColor]; // Use any colour you wish
lbl.alpha = 1.0; //0.5;
lbl.tag = 42;
lbl.font = [UIFont fontWithName:#"Helvetica-Bold" size:16.0]; // This is optional, I found this fond and size most readable
[av addSubview:lbl];
//Following lets the callout still work if you tap on the label...
av.canShowCallout = YES;
av.frame = lbl.frame;
} else {
av.annotation = annotation;
}
UILabel *lbl = (UILabel *)[av viewWithTag:42];
// I added this to the text to improve its visibility by essentially adding a stroke around the text. Well its a poor man's stroke by adding a shadow
lbl.layer.shadowColor = [[UIColor blackColor] CGColor];
lbl.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
lbl.layer.shadowOpacity = 1.0f;
lbl.layer.shadowRadius = 1.0f;
lbl.text = annotation.title;
return av;
}
Below, I have created an Annotation for my map which works perfectly and shows up with the title and subtitle as it should.
But I wish to add a small image to the left of the annotation but can't figure out what I need to do to the below code to make it work.
// Annotation
CLLocationCoordinate2D poseLocation;
poseLocation.latitude = POSE_LATITUDE;
poseLocation.longitude = POSE_LONGITUDE;
Annotation *myAnnotation = [[Annotation alloc] init];
myAnnotation.coordinate = poseLocation;
myAnnotation.title = #"Pose Beauty Salon";
myAnnotation.subtitle = #"100, Moneyhaw Road";
[self.myMapView addAnnotation:myAnnotation];
You will need to set the delegate of your myMapView first and implement the viewForAnnotation delegate method.
There you will return MKAnnotationView instance which has a property leftCalloutAccessoryView:
The view to display on the left side of the standard callout bubble.
The default value of this property is nil. The left callout view is
typically used to display information about the annotation or to link
to custom information provided by your application. The height of your
view should be 32 pixels or less.
In the leftCalloutAccessoryView, you can assign your image there. For example:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
MKPinAnnotationView *annotationView = (MKPinAnnotationView *)[self.myMapView dequeueReusableAnnotationViewWithIdentifier:#"annotation"];
if (annotationView == nil) {
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"annotation"];
UIImageView *imageView = //initialize your image view here
annotationView.leftCalloutAccessoryView = imageView;
}
annotationView.annotation = annotation;
return annotationView;
}
PS: Apparently you have asked similar question before here: Add image to the left of my annotations. I am not sure you need to post another question. Please try to implement this first.
-(MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
MKPinAnnotationView *MyPin=[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"current"];
myImage = [UIImage imageNamed:imagename];
CGRect cropRect = CGRectMake(0.0, 0.0,35.0, 35.0);
myImageView = [[UIImageView alloc] initWithFrame:cropRect];
myImageView.clipsToBounds = YES;
myImageView.image = myImage;
MyPin.leftCalloutAccessoryView =myImageView;
MyPin.highlighted=YES;
MyPin.canShowCallout=YES;
return MyPin;
}
It works for me , try this one