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:
Related
I think those two are related and I'm probably doing something wrong here:
First I'm overriding viewForAnnotation - in order to put my custom Image
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
// we avoid changing the user's location blue point
if([annotation isKindOfClass: [MKUserLocation class]]) {
return nil;
}
else
{
static NSString *SFAnnotationIdentifier = #"SFAnnotationIdentifier";
MKPinAnnotationView* pinAnnotationView = (MKPinAnnotationView*)[self.mapToPin dequeueReusableAnnotationViewWithIdentifier:SFAnnotationIdentifier];
if (!pinAnnotationView)
{
MKAnnotationView *annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:SFAnnotationIdentifier];
UIImage *littleImage = [UIImage imageNamed:#"littleImage.png"];
// You may need to resize the image here.
annotationView.image = littleImage;
pinAnnotationView.draggable = true;
pinAnnotationView.canShowCallout = true;
pinAnnotationView.animatesDrop = true;
return annotationView;
}
else
{
pinAnnotationView.annotation = annotation;
}
return pinAnnotationView;
}
}
It seems that pinAnnotationView's draggable, canShowCallout and animatesDrop are not working.
Then, I'm calling my method in my viewDidLoad to set the Annotation
-(void) setAnnotation:(NSString*)lat : (NSString*)lng
{
_mapAnnotation = [[MKPointAnnotation alloc] init];
CLLocationCoordinate2D coordinateForPin;
coordinateForPin.latitude = [lat floatValue];
coordinateForPin.longitude = [lng floatValue];
[_mapAnnotation setCoordinate:coordinateForPin];
[_mapAnnotation setTitle:#"sometitle"];
[_mapToPin addAnnotation:_mapAnnotation];
}
It seems that [_mapAnnotation setTitle:#"sometitle"]; is not working anymore when I'm overriding viewForAnnotation.
Is this the right way to go? I have been browsing but didn't find any solution that could help. Any feedback would be appreciated.
The problem is the accidental reference to the wrong variable name. The pinAnnotationView is nil inside that if statement, so setting properties with that nil reference is a NOOP.
if (!pinAnnotationView) {
MKAnnotationView *annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:SFAnnotationIdentifier];
...
annotationView.draggable = true; // not pinAnnotationView
annotationView.canShowCallout = true;
annotationView.animatesDrop = true;
return annotationView;
}
FWIW, if targeting iOS 10 or later, you can register your own class and move your annotation view configuration code into that subclass. Furthermore, in iOS 11 and later you do not need mapView:viewForAnnotation: at all.
For example, in iOS 11 and later:
// MyAnnotationView.h
#import MapKit;
NS_ASSUME_NONNULL_BEGIN
#interface MyAnnotationView : MKPinAnnotationView
#end
NS_ASSUME_NONNULL_END
and
// MyAnnotationView.m
#import "MyAnnotationView.h"
#implementation MyAnnotationView
- (instancetype)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if (self) {
...
self.draggable = true;
self.canShowCallout = true;
self.animatesDrop = true;
}
return self;
}
#end
And then, in your viewDidLoad, you register this class.
- (void)viewDidLoad {
[super viewDidLoad];
[self.mapView registerClass:[MyAnnotationView class] forAnnotationViewWithReuseIdentifier:MKMapViewDefaultAnnotationViewReuseIdentifier];
...
}
You can then remove the mapView:viewForAnnotation: implementation entirely.
I am loading the mapview as below, with MKAnnotationView added as subview in the mapview. I have added as subview, instead of addAnnotation, as I wanted the annotation view in the center all the time and the mapview to scroll under it.
- (void)viewDidLoad {
[super viewDidLoad];
self.mapView.delegate = self;
[self.mapView addSubview:self.centerAnnotationView];
}
- (MKPointAnnotation *)centerAnnotaion
{
if (!_centerAnnotaion) {
_centerAnnotaion = [[MKPointAnnotation alloc] init];
_centerAnnotaion.title = #"Title";
}
return _centerAnnotaion;
}
- (MKPinAnnotationView *)centerAnnotationView
{
if (!_centerAnnotationView) {
_centerAnnotationView = [[MKPinAnnotationView alloc] initWithAnnotation:self.centerAnnotaion
reuseIdentifier:#"centerAnnotationView"];
_centerAnnotationView.pinColor = MKPinAnnotationColorGreen;
_centerAnnotationView.canShowCallout = YES;
_centerAnnotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
return _centerAnnotationView;
}
I have the following questions.
Is it possible to get the default callout even when I haven't added the pin as annotation to the map view.
Is there a way that I could get - (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view event working in my case.
Should I use the tap gesture to capture the tap on the annotation pin?
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.
Helloall in MapKit I can't figure out how to get the animateDrop animation to run, I'm using 1 viewController with both CoreLocation and MapKit running on it.
I read that I need to make sure I have set a delegate from the mapview to self in viewDidLoad which I have done and also to make sure I am using MKPinAnnotationView to get access to the animateDrop property, which I think I have done also?
I'm building for iOS8 also.
Any help would be great
//I'm not showing all code just showing some of the related parts of code below
.h
//I do set standard imports UIKit, CoreLocation, MapKit etc
#interface ViewController : UIViewController <CLLocationManagerDelegate, MKMapViewDelegate>
//I do set various properties related. CLLocationManager, CLLocation, MKMapView
.m
- (void)viewDidLoad {
[super viewDidLoad];
self.mapView.delegate = self;
MKPointAnnotation *myAnnotation = [[MKPointAnnotation alloc] init];
myAnnotation.coordinate = CLLocationCoordinate2DMake(37.813186, 144.962979);
myAnnotation.title = #"My title";
myAnnotation.subtitle = #"My sub title";
[self.mapView addAnnotation:myAnnotation];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
// If it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
// Handle any custom annotations.
if ([annotation isKindOfClass:[MKPointAnnotation class]])
{
// Try to dequeue an existing pin view first.
MKPinAnnotationView *pinView = (MKPinAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:#"CustomPinAnnotationView"];
//pinView.animatesDrop = YES;
if (!pinView)
{
// If an existing pin view was not available, create one.
pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"CustomPinAnnotationView"];
pinView.canShowCallout = YES;
pinView.animatesDrop = YES;
} else {
pinView.annotation = annotation;
}
return pinView;
}
return nil;
}
Any help would be great
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.