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?
Related
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:
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.
I'm trying to perform a segue when a map annotation pin is tapped. Im using a custom annotation class if that makes a difference.
I've tried
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
NSLog(#"annotation selected");
[self performSegueWithIdentifier:#"mySegue" sender:self];
}
but my NSLog isnt running so I assume the method isnt getting called.
The only other things I've done are add the annotation pin to my map view and set my view controller as the map view delegate.
Here is how I added the annotation to the mapview
SPMapAnnotation *pin = [[SPMapAnnotation alloc] init];
pin.coordinate = spotLocation.coordinate;
pin.title = [spot objectForKey:#"spotName"];
[self.mapView addAnnotation:pin];
How can I make this work? That didSelectAnnotation method seems like it would make this easy to do but I'm not sure how it works.
Apparently I forgot I didnt allocate my mapView until viewDidAppear. Just needed to add
self.mapView.delegate = self;
after I alloc and inited it.
Implement following delegate method
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKPinAnnotationView *annotationView = nil;
if ([annotation isKindOfClass:[SPMapAnnotation class]])
{
annotationView = (MKPinAnnotationView *)[self.mapView dequeueReusableAnnotationViewWithIdentifier:#"Pin"];
if (annotationView == nil)
{
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"Pin"];
annotationView.canShowCallout = YES;
annotationView.animatesDrop = YES;
}
}
return annotationView;
}
as didSelectAnnotationView will not be called for standard annotation pin, you need to return MKAnnotationView for didSelectAnnotationView to be called.
I have having problems using a UILongPressGestureRecognizer together a draggable MKPinAnnotationView.
The behaviour I am trying to produce is similar to the Maps App.
The pin can be dragged.
When there is a long press/ tap, a pin is dropped.
However, I have problems having the long press being recognized outside the frame of the MKPinAnnotationView. The long press gesture to drop the pin works fine if the Pin is not draggable. When the pin is draggable however, I can't get the long press gesture recognizer to be recognized so that I can drop pin.
Any ideas?
By the way, I have tried to set the delegate for the long press recognizer so that
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
In this case, the long press gestures are recognized and the pins are dropped, but the dragging of the pin no longer works.
Snippets of the MapView (a subclass of MKMapView)
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// init the gesture recognizer
UILongPressGestureRecognizer* lpgr = [[UILongPressGestureRecognizer alloc]
initWithTarget:self action:#selector(handleLongPress:)];
lpgr.minimumPressDuration = 0.5f; //user needs to press for 2 seconds
lpgr.delegate = self;
[self addGestureRecognizer:lpgr];
[lpgr release];
//add some initial annotation
Marker *_annotation = [[Marker alloc] initWithCoordinate:_location];
[_annotation titleWithString:#"some title"];
[self addAnnotation:_annotation];
}
return self;
}
- (void)handleLongPress:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer.state != UIGestureRecognizerStateBegan)
{
return;
}
CGPoint touchPoint = [gestureRecognizer locationInView:self];
CLLocationCoordinate2D touchMapCoordinate = [self convertPoint:touchPoint toCoordinateFromView:self];
// add marker to self-map
// Marker is subclass of MKAnnotation
Marker *_annotation = [[Marker alloc] initWithCoordinate:_location];
[_annotation titleWithString:#"some title"];
[self addAnnotation:_annotation];
}
- (MKAnnotationView *)mapView:(MKMapView *)mView viewForAnnotation:(id<MKAnnotation>) annotation {
if([annotation isMemberOfClass:[Marker class]] ) {
// use MKPinAnnotationView for the view
MKPinAnnotationView *_pin = (MKPinAnnotationView *) [mView dequeueReusableAnnotationViewWithIdentifier:#"spot_pin"];
if (_pin == nil)
{
_pin = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"spot_pin"] autorelease];
}
else
{
_pin.annotation = annotation;
}
[_pin setDraggable:YES];
[_pin setSelected:YES animated:YES];
[_pin setCanShowCallout:YES];
return _pin;
} else {
return nil;
}
}
Ok guys, I solved it.
Apparently when after I subclassed MKMapView, I also added a method handleLongPress. This method apparently interfered with the handleLongPress method of the MKMapView.
Just by changing my handleLongPress selector to a different name like handleLongPress2 will make it work like the Maps app.
UILongPressGestureRecognizer* lpgr = [[UILongPressGestureRecognizer alloc]
initWithTarget:self action:#selector(handleLongPress2:)];
I am trying to annotate my map with MKPointAnnotation, like this:
- (void)viewDidLoad
{
NSLog(#"RootViewController viewDidLoad");
[super viewDidLoad];
CLLocationCoordinate2D coord =
CLLocationCoordinate2DMake(54.903683,23.895435);
MKPointAnnotation *toyAnnotation = [[MKPointAnnotation alloc] init];
toyAnnotation.coordinate = coord;
toyAnnotation.title = #"Title";
toyAnnotation.subtitle = #"Subtitle";
[mapView addAnnotation:toyAnnotation];
[toyAnnotation release];
}
- (MKAnnotationView *)mapView:(MKMapView *)m
viewForAnnotation:(id <MKAnnotation>)annotation
{
NSLog(#"RootViewController mapView: viewForAnnotation:");
NSLog(#"%#",annotation);
MKAnnotationView *pin = [[MKAnnotationView alloc]
initWithAnnotation:annotation
reuseIdentifier:nil];
pin.enabled = YES;
pin.canShowCallout = YES;
return [pin autorelease];
}
Pin fails to appear on the map. RootViewController is a delegate for mapView and thus mapView:viewForAnnotation: method gets called:
2011-11-24 15:04:03.808 App[2532:707] RootViewController mapView: viewForAnnotation:
2011-11-24 15:04:03.810 App[2532:707] <MKPointAnnotation: 0x1885c0>
What am I doing wrong and how to fix this issue?
In viewForAnnotation, create an MKPinAnnotationView instead of an MKAnnotationView (for which you have to set the image).
Although for a default pin annotation view, you don't need to implement the delegate method at all.