I have a custom annotation view class which is like
#interface MyAnnotationView : MKAnnotationView
#property (nonatomic, strong) UIImageView *imageView;
#end
and then I use it in the MKMapView like :
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
MKAnnotationView *annotationView = nil;
MyAnnotationView *view = (MyAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:MyAnnotationViewIdentifier];
if (view == nil) {
view = [[MyAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:MyAnnotationViewIdentifier];
}
view.imageView.image = myImage;
annotationView = view;
return annotationView;
}
This code works as expected till iOS 11, but in iOS 12 I dont see the image anymore. What am I missing? Any tips appreciated.
Related
In my iOS app I have a MapView and I have a custom Annotation (extends MKAnnotation).
I set an icon image for each annotation depending on it's type. When I first load up the map view, all the images are correct, but as you move the view around the images start to change to different ones.
It's almost like the OS is recycling annotations and bringing them back as they reappear on the screen, but with the wrong image.
Does anyone know how to fix this? Thanks for your help.
Here is my delegate method:
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
if ([annotation isKindOfClass:[CustomAnnotationView class]]) {
CustomAnnotationView *customAnnotation = (CustomAnnotationView *)annotation;
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"CustomAnnotation"];
if (annotationView == nil) {
annotationView = customAnnotation.annotationView;
}
else{
annotationView.annotation = annotation;
}
return annotationView;
}
else{
return nil;
}
}
Here is what my object looks like:
#interface CustomAnnotationView : NSObject<MKAnnotation>
#property (nonatomic,readonly) CLLocationCoordinate2D coordinate;
#property (copy, nonatomic) NSString *title;
#property (copy, nonatomic) NSString *subtitle;
#property (weak, nonatomic) Thing *thing;
-(id)init:(Thing *)thing;
-(MKAnnotationView *) annotationView;
-(void)setDistance:(CLLocationCoordinate2D)point;
#end
I know this is a question asked a lot. I have been reading through many of the questions on here regarding this for a while. I was hoping someone would have a simpler way using the code I have to replace the pins on my map with an image. I am still very new to objective c.The code I have is very short and easy. Most of what I have found online to post pins to a map is very long and drawn out. I have the following classes and controllers for my map view:
LocationsViewController.h
#import "TechBookAppDelegate.h"
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#interface LocationsViewController : UIViewController <MKMapViewDelegate>
{
IBOutlet MKMapView *worldView;
IBOutlet UIActivityIndicatorView *activityIndicator;
}
#end
LocationsViewController.m
#import "LocationsViewController.h"
#import "MapAnnotation.h"
#interface LocationsViewController ()
#end
#implementation LocationsViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialization code
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
CLLocationCoordinate2D wisconsin = {.latitude = xx.xxxxxxx, .longitude = -xx.xxxxxx};
CLLocationCoordinate2D pennsyvainia = {.latitude = xx.xxxxxxx, .longitude = -xx.xxxxxx};
CLLocationCoordinate2D nevada = {.latitude = xx.xxxxxxx, .longitude = -xx.xxxxxx};
CLLocationCoordinate2D texas = {.latitude = xx.xxxxxxx, .longitude = -xx.xxxxxx};
MapAnnotation *pinWI = [[MapAnnotation alloc] initWithCoordinate: wisconsin];
MapAnnotation *pinPA = [[MapAnnotation alloc] initWithCoordinate: pennsyvainia];
MapAnnotation *pinNV = [[MapAnnotation alloc] initWithCoordinate: nevada];
MapAnnotation *pinTX = [[MapAnnotation alloc] initWithCoordinate: texas];
[worldView addAnnotation:pinTX];
[worldView addAnnotation:pinNV];
[worldView addAnnotation:pinWI];
[worldView addAnnotation:pinPA];
}
MapAnnotation.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#interface MapAnnotation : NSObject <MKAnnotation> {
CLLocationCoordinate2D _coordinate;
MKAnnotationView *mapView;
}
- (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate;
#end
MapAnnotation.m
#import "MapAnnotation.h"
#import "LocationsViewController.h"
#implementation MapAnnotation
#synthesize coordinate = _coordinate;
- (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate
{
self = [super init];
if (self != nil)
{
_coordinate = coordinate;
}
return self;
}
#end
I have tried to implement methods similar to this:
-(MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{.......
with various types of code but to no avail. Not sure what I am doing wrong. Isn't there a shorter way to do what I am thinking than this than what I have seen so far online.
I was hoping there was something similar to :
MapAnnotation *pinWI = [[[MapAnnotation alloc] initWithCoordinate: wisconsin] initWithPinImage:#"imagename.png"];
or could I implement something similar to this:
pinWI:MKPinAnnotationColorGreen;
only with a image location defined?
or something along those lines. Thanks in advance for everyones help and patience with a newbie.
I really need to change the pin to a custom image. One coding schema I tried was 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;
}
WORKING CODE:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
static NSString *AnnotationViewID = #"annotationViewID";
MKAnnotationView *annotationView = (MKAnnotationView *)[worldView dequeueReusableAnnotationViewWithIdentifier:AnnotationViewID];
if (annotationView == nil)
{
annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationViewID];
}
annotationView.image = [UIImage imageNamed:#"locations_marker.png"];
annotationView.annotation = annotation;
return annotationView;
}
add the type property(example-firstPin, secondPin,thirdPin etc) to your custom MapViewAnnotation class.
MapViewAnnotation *newAnnotation = [[MapViewAnnotation alloc] init...
newAnnotation.type = #"firstPin"; // <-- set property in annotation
[self.mapView addAnnotation:newAnnotation];
and in your - (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>) annotation method put a simple check
MapViewAnnotation *mvAnn = (MapViewAnnotation *)annotation;
if ([mvAnn.type isEqualToString:#"firstPin"])
{
annView.pinColor = MKPinAnnotationColorGreen;
}
else if ([mvAnn.type isEqualToString:#"secondPin"])
{
annView.pinColor = MKPinAnnotationColorRed;
}
else if ([mvAnn.type isEqualToString:#"thirdPin"])
{
annView.pinColor = MKPinAnnotationColorOrange;
}
I need to put custom annotation views to mapview, and they need to be clickable. i need rather than classic callout, i will tap on the annotation and it will perform some functions.
I tried many things but still not achieved.
I found a really good implementation for you:
http://blog.asolutions.com/2010/09/building-custom-map-annotation-callouts-part-1/
there you can add buttons to calloutView with actions
or..
in mapView delegate you can customize the annotation, and the callout view:
viewForAnnotation:
Returns the annotation view associated with the specified annotation object, if any.
(MKAnnotationView *)viewForAnnotation:(id < MKAnnotation >)annotation
there is an example:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"loc"];
// Button
UIButton *button = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
button.frame = CGRectMake(0, 0, 23, 23);
annotationView.rightCalloutAccessoryView = button;
// Image and two labels
UIView *leftCAV = [[UIView alloc] initWithFrame:CGRectMake(0,0,23,23)];
[leftCAV addSubview : yourImageView];
[leftCAV addSubview : yourFirstLabel];
[leftCAV addSubview : yourSecondLabel];
annotationView.leftCalloutAccessoryView = leftCAV;
annotationView.canShowCallout = YES;
return annotationView;
}
EDITED ANSWER FOR CUSTOM PIN:
make a MKAnnotationView subclass
.h
#import <MapKit/MapKit.h>
#interface AnnotationView : MKAnnotationView
- (id)initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier;
#end
.m
#import "AnnotationView.h"
#implementation AnnotationView
{
NSString *identifier;
}
- (id)initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if (self != nil)
{
CGRect frame = self.frame;
frame.size = CGSizeMake(78.0, 35.0);
self.frame = frame;
self.backgroundColor = [UIColor clearColor];
self.centerOffset = CGPointMake(0, 0);
}
return self;
}
-(void) drawRect:(CGRect)rect
{
[[UIImage imageNamed:#"yourImage"]drawInRect:CGRectMake(0, 0, 78, 35)];
}
#end
then in the mapview delegate:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKUserLocation class]]) {
return nil;
}
if ([annotation isKindOfClass:[Annotation class]]) {
NSString* annotationIdentifier = #"yourAnnotation";
AnnotationView* customAnnotationView = (AnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
if (!customAnnotationView) {
customAnnotationView = [[AnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:annotationIdentifier];
customAnnotationView.canShowCallout = YES;
}
else{
customAnnotationView.annotation= annotation;
}
return customAnnotationView;
}
return nil;
}
Annotation class is my custom annotation class:
#interface Annotation : NSObject <MKAnnotation>
I hope this helps
There is a method on the mapViewDelegate: viewForMapView. In this method, you return a view. This view can be completely customized by you, e.g. you can add gestureregognizers to it to handle additional gestures beside tapping.
I have class
#interface MyLocation : NSObject <MKAnnotation>
#property (copy) NSString *name;
#property (copy) NSString *address;
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
#property (nonatomic, strong) UIImage *banner;
#property (nonatomic, strong) NSString *descr;
When I add location on map i want that method - (MKAnnotationView*) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation show banner .... how can i do this?
- (MKAnnotationView*) mapView:
(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
static NSString *identifier = #"MyLocation";
if ([annotation isKindOfClass:[MyLocation class]]) {
MKPinAnnotationView *annotationView = (MKPinAnnotationView*)
[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil) {
annotationView = [[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:identifier];
} else {
annotationView.annotation = annotation;
}
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
annotationView.image = [UIImage imageNamed:#"Image2.jpg"];
return annotationView;
}
return nil;
}
this method change all annotation pins but i need differed images for each annotation.
Thanks for help!
You have to use MKAnnotationView for custom pins, since MKPinAnnotationView always displays the "default" pins.
custom CLASS MKAnnotationView, and U can change the pins
see the sample code from apple:map callouts
I've a MKMapView.
I want to put a custom MKAnnotation on my map.
They are some restaurant places. How can I do it?
My question is how can I make a custom MKAnnotation?
Thanks, guys.
First, let's define custom as meaning not simply title and subtitle. We want to change the size of the MKAnnotation and include some custom graphics.
There are two parts to an annotation you might want to customize:
MKAnnotation
MKAnnotationView
For the most basic MKAnnotation you would simply adopt the protocol and return nil for title and subtitle, but you could also carry a lot more information in your annotation for an extended callout upon tapping an accessory indicator. You can add all of the annotations to the MKMapView using addAnnotation: in viewDidLoad for example.
MKAnnotation Header
#interface CPAnnotation : NSObject <MKAnnotation> {
#private
CLLocationCoordinate2D _coordinate;
NSString *_title;
NSString *_subtitle;
}
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
#property (nonatomic, readonly, copy) NSString *title;
#property (nonatomic, readonly, copy) NSString *subtitle;
- (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate;
#end
MKAnnotation Implementation
#implementation CPAnnotation
#synthesize coordinate = _coordinate;
#synthesize title = _title;
#synthesize subtitle = _subtitle;
- (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate {
self = [super init];
if (self != nil) {
self.coordinate = coordinate;
}
return self;
}
- (NSString *)title {
return _title;
}
- (NSString *)subtitle {
return _subtitle;
}
#end
The next step is to customize the callout from the pin dropped. To do this you need to customize MKAnnotationView. According to Apple you shouldn't make a huge callout by default. They recommend a standard size callout that has a button to open a bigger one. They use the lowercase i in a blue circle icon. Those icons can be set via the view's leftCalloutAccessoryView and rightCalloutAccessoryView property. If you already adopted the MKMapViewDelegate protocol and set yourself as the MKMapView's delegate you will get the callback for viewForAnnotation:.
MKAnnotationView MKMapViewDelegate callback
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
static NSString *const kAnnotationReuseIdentifier = #"CPAnnotationView";
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:kAnnotationReuseIdentifier];
if (annotationView == nil) {
annotationView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:kAnnotationReuseIdentifier] autorelease];
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeInfoLight];
}
return annotationView;
}
You can further customize this in a custom view overriding the drawRect method, providing an image to the image property, or you could even implement an MKAnnotationView in a XIB. It is worth some experimentation.
Apple's WeatherAnnotationView Example illustrates overriding drawRect.
I had a case where I wanted something like a standard Pin annotation, but the designer wanted a custom graphic.
I wrote a subclass of MKAnnotationView to display the graphic. The only difference is that it overrides the standard class's image.
BlipAnnotationView.h
#import <MapKit/MapKit.h>
#interface BlipAnnotationView : MKAnnotationView
- (id)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier;
#end
BlipAnnotationView.m
#import "BlipAnnotationView.h"
#implementation BlipAnnotationView
- (id)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if (self) {
UIImage *blipImage = [UIImage imageNamed:#"blip.png"];
CGRect frame = [self frame];
frame.size = [blipImage size];
[self setFrame:frame];
[self setCenterOffset:CGPointMake(0.0, -7.0)];
[self setImage:blipImage];
}
return self;
}
#end
Then in the class that displays the map, I made the class implement the MKMapViewDelegate protocol. The mapView:viewForAnnotation: method creates a new instance of BlipAnnotationView if necessary.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
NSLog(#"mapView:%# viewForAnnotation:%#", mapView, annotation);
static NSString *const kAnnotationIdentifier = #"BlipMapAnnotation";
BlipAnnotationView *annotationView = (BlipAnnotationView *)
[mapView dequeueReusableAnnotationViewWithIdentifier:kAnnotationIdentifier];
if (! annotationView) {
annotationView = [[BlipAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:kAnnotationIdentifier];
}
[annotationView setAnnotation:annotation];
return annotationView;
}
Finally I set the class as the delegate of the map view in awakeFromNib:
- (void)awakeFromNib
{
[...]
[_theMapView setDelegate:self];
}
I didn't have to change the code that positioned the annotation at all:
MKPointAnnotation *annotationPoint = [[MKPointAnnotation alloc] init];
[annotationPoint setCoordinate:[userLocation coordinate]];
[annotationPoint setTitle:label];
[_theMapView addAnnotation:annotationPoint];