I have a MKMapView with some custom annotations that don't look that great when the map is zoom far out.
Is it possible to only show/add them when the map is at a certain zoom level?
Using Marko's answer I came to this solution.
Everytime region changes, I change the ViewController's property isAtBigZoom.
func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
isAtBigZoom = mapView.region.span.latitudeDelta < 0.01
}
Then at didSet of the property, I execute this code.
var isAtBigZoom = false {
didSet {
// this guard ensures, that the showing and hiding happens only once
guard oldValue != isAtBigZoom else {
return
}
// in my case I wanted to show/hide only a certain type of annotations
for case let annot as MapTextAnnotation in mapView.annotations {
mapView.viewForAnnotation(annot)?.alpha = isAtBigZoom ? 1 : 0
}
}
}
If you also want to start with the annotations hidden, just add the alpha changing code to viewForAnnotation method.
Works great and I haven't noticed big issues with performance. Though that may change with the increasing number of annotations...
You can get the map zoom level via
[map region];
property of the MKMapView. also you get the notifications for region changing events
by implementing the MKMapViewDelegate method and setting the delegate
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
here you can check what your current zoom level is. I don't recommend removing or adding all the annotations while zooming / panning since that could really effect the app performance. I haven't really tried setting alpha to 0.0 or hidden property on MKAnnotationView, but that could be your best bet.
Related
I add custom MKOverlayRenderer to MKMapView,Then I remove it with
removeOverlays() but overlay stays there for a while, mkmapview updates the drawing, most of the time i move mkmapview region somewhere then back. is there a method to set that will force mkmapview render immediately?
I ran into this same issue. I found online that
self.mapView.removeOverlays(self.mapView.overlays)
should remove them but it doesn't.
Turns out you have to create your MKPolylineRenderer or overlays as ivar.
var directionLineRenderer = MKPolylineRenderer()
func remove() {
if self.mapView.overlays.count != 0 {
self.mapView.remove(self.directionLineRenderer.overlay)
}
}
I am trying to zoom in significantly on a pin when a MKAnnotationView is clicked with MapkKit using the following code:
MKCoordinateRegion mapRegion;
mapRegion.center = view.annotation.coordinate;;
mapRegion.span.latitudeDelta = 0.2;
mapRegion.span.longitudeDelta = 0.2;
[MKMapView animateWithDuration:0.15 animations:^{
[mapView setRegion:mapRegion animated: YES];
}];
However, whenever I zoom in I want the pin to remain selected. Is there a way to prevent the MKAnnotatiotionView from being deselected and the function didDeselectAnnotationView from being called.
I think the reason it might be happening is because the mapView on the zoom is updating the annotations. Is there a way to prevent this from happening if this is the cause?
Yes, if [mapView setRegion: ...] causes the annotations on the mapView to change for whatever reason, then your selected annotation will be deselected (because it's about to be removed!).
One way to fix this is to do a 'diff' replace of your annotations. For example, at the moment you might have some code that looks like (expressed in Swift):
func displayNewMapPins(pinModels: [MyCustomPinModel]) {
self.mapView.removeAnnotations(self.mapView.annotations) //remove all of the currently displayed annotations
let newAnnotations = annotationModels.map { $0.toAnnotation } //convert 'MyCustomPinModel' to an 'MKAnnotation'
self.mapView.addAnnotations(newAnnotations) //put the new annotations on the map
}
You want to change it to be more like this:
func displayNewMapPins(pinModels: [MyCustomPinModel]) {
let oldAnnotations = self.mapView.annotations
let newAnnotations = annotationModels.map { $0.toAnnotation }
let annotationsToRemove = SomeOtherThing.thingsContainedIn(oldAnnotations, butNotIn: newAnnotations)
let annotationsToAdd = SomeOtherThing.thingsContainedIn(newAnnotations, butNotIn: oldAnnotations)
self.mapView.removeAnnotations(annotationsToRemove)
self.mapView.addAnnotations(annotationsToAdd)
}
The exact implementation of SomeOtherThing.thingsContainedIn(:butNotIn:) depends on your requirements, but this is the general code structure you want to aim for.
Doing it this way will have an added benefit of improving the performance of your app - adding and removing annotations from a MKMapView can be really expensive!
Is it possible to change a GMSPolygon .fillColor property from the didTapOverlay GMSMapViewDelegate delegation method? This is the delegation method:
func mapView(mapView: GMSMapView, didTapOverlay overlay: GMSOverlay)
my problem is that GMSPolygon inherits from GMSOverlay and the overlay doesn't have a fillColor property. The result I would like to achieve is to change the color of the polygon when the user taps it
Thanks
I met the same situation before (I used Objective-C for that time).
My solution was that generating unique IDs(random strings or number), then set them to the title property of each GMSPolygons.
The below code is written by Objective-C, but you might understand
- (void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSOverlay *)overlay {
NSString *overlayClass = NSStringFromClass([overlay class]);
if ([overlayClass isEqualToString:#"GMSPolygon"] ||
[overlayClass isEqualToString:#"GMSPolyline"] ||
[overlayClass isEqualToString:#"GMSCircle"] ||
[overlayClass isEqualToString:#"GMSGroundOverlay"]) {
[self triggerOverlayEvent:#"overlay_click" id:overlay.title]; // <-- here
}
}
Of course, you need to manage all polygons (and others) with the generated IDs by yourself.
I have found a simple (single line) way to do that. Since GMSPolygon is a subclass of GMSOverlay we can cast him to be a GMSPolygon object, the delegate method will continue to work normally and it will treat it as a GMSPolygon object, changing it's .fillColor property.
Here's the code:
func mapView(mapView: GMSMapView, didTapOverlay overlay: GMSOverlay) {
/* Here we cast the GMSOverlay to be a GMSPolygon */
let overlay = overlay as! GMSPolygon
overlay.fillColor = UIColor.whiteColor()
/* Now the color of the polygon has changed on the map */
}
I am trying to center an MKMapView after an annotation was selected. I also have enabled canShowCallout but it seems that iOS is first displaying the callout (which is shifted when it would not fit in the screen) and then the map is being moved, resulting in the callout being not completely visible on the screen.
How can I center the map BEFORE the callout's position is being rendered and displayed?
I wanted to accomplish the same thing and ended up doing the following.
A word of caution before I begin: I know the solution is pretty ugly!...but hey, it works.
Note: I am targeting iOS 9 but it should work on prior versions of iOS:
Okay, here we go:
first off, create a new property in your view controller, e.g.: #property(nonatomic, assign, getter=isPinCenteringOngoing) BOOL pinCenteringOngoing;
in mapView:viewForAnnotation: set canShowCallout to NO for your annotationViews
in mapView:didSelectAnnotationView: do the following:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
if([view isKindOfClass:$YOURANNOTATIONVIEWCLASS$.class])
{
if(!self.isPinCenteringOngoing)
{
self.pinCenteringOngoing = YES;
[self centerMapOnSelectedAnnotationView:($YOURANNOTATIONVIEWCLASS$ *)view];
}
else
{
self.pinCenteringOngoing = NO;
}
}
}
in mapView:didDeselectAnnotationView: do the following:
- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view
{
if([view isKindOfClass:$YOURANNOTATIONVIEWCLASS$.class])
{
if(!self.isPinCenteringOngoing)
{
view.canShowCallout = NO;
}
}
}
and finally create a new method that does the actual work:
- (void)centerMapOnSelectedAnnotationView:($YOURANNOTATIONVIEWCLASS$ *)view
{
// Center map
CGPoint annotationCenter = CGPointMake(CGRectGetMidX(view.frame), CGRectGetMidY(view.frame));
CLLocationCoordinate2D newCenter = [self.mapView convertPoint:annotationCenter toCoordinateFromView:view.superview];
[self.mapView setCenterCoordinate:newCenter animated:YES];
// Allow callout to be shown
view.canShowCallout = YES;
// Deselect and then select the annotation so the callout is actually displayed
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void)
{
[self.mapView deselectAnnotation:view.annotation animated:NO];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void)
{
[self.mapView selectAnnotation:view.annotation animated:NO];
});
});
}
To complete my answer, here is a textual explanation of what I'm doing in the code above and why I'm doing it:
What I want is the annotation to be centered on screen, and the callout to be centered above it.
What I get by default is:
When selecting an annotation, the map opens the callout, and if necessary adjusts the map so the callout fits on screen. By no mean does that standard implementation guarantee, that the callout is "centered" above the annotation.
By centering the map with setCenterCoordinate:, the annotation view is centered on the map.
Now the two previous points combined can result in the callout to be "cut off" as the annotation is centered on the map, but the callout is not centered above the annotation.
To fix this, I do the following:
first I disable the callout to be displayed by default, setting canShowCallout to NO for every annotationView
when the user selects an annotation, I first center the map
I then allow the callout to be shown, setting canShowCallout to YES for the selected annotation
I then deselect and then again select the annotation, so the callout is actually displayed
in order for the callout to be correctly centered above the annotation, I need to do the deselecting/selecting somewhat delayed so that the map centering can complete
I hope my answer may prove useful.
Here an other solution :
Create a new boolean property var selectFirstAnnotation = false in your controller
Set it to true before to center the annotation
Add this is in regionDidChangeAnimated.
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
if selectFirstAnnotation == true {
if let annotation = mapView.annotations.first(where: { !($0 is MKUserLocation) }) {
mapView.selectAnnotation(annotation, animated: true)
selectFirstAnnotation = false
}}}
Works fine for my behaviour
I tried both previous solutions and Greg's is the correct answer with a couple of tweaks... I put the map centering in and animation block to slow down the animation.
UIView.animate(withDuration: 0.8) {
self.mapView.setCenter(CLLocationCoordinate2D(latitude: newCenter.latitude, longitude: newCenter.longitude), animated: true)
}
Then I was getting an unacceptable blip from the separation of the deselect and select calls into different dispatches with different times and discovered they can both go in the same dispatch. Adding animated: true to the select call adds a nice touch as well.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.9) {
mapView.deselectAnnotation(view.annotation, animated: false)
mapView.selectAnnotation(view.annotation!, animated: true)
}
I have a problem that I can't solve for some time.
I have a GMSMapView with imageView in front of it in the center. In result - I can drag map and always have centered pin. But problems come when I zoom the map.
On zooming - position of map target changes and my imageView points to another location.
I can detect if zoom changed, but I cant actually force GMSMapView to do ONLY zoom without any location changing.
-(void) mapView:(GMSMapView *)mapView didChangeCameraPosition:(GMSCameraPosition *)position
{
if (mZoomLevel != mapView.camera.zoom)
{
mZoomLevel = mapView.camera.zoom;
}
}
So basically, I want to have always centered pin, even if I perform zoom.
I tried GMSMarker - but it has problems with performance when following map center. It doesn't do it instantly, so I decided to use imageView.
Main question: how to lock current location of the map while performing zoom?
Google fixed this issue with google maps sdk 1.10.0.
The solution is to simply add this line when configuring GMSMapview:
_mapView.settings.allowScrollGesturesDuringRotateOrZoom = NO;
Well, after 10 days of going back to this problem, I finally solved it!
The answer was pretty easy!
Few steps here:
1. Add imageView as marker on top of the GMSMapView
2. Add UIPanGestureRecognizer to the GMSMapView (don't forget to set delegate, it is important)
3. Then use this code:
- (void) didPan:(UIPanGestureRecognizer*) gestureRecognizer
{
if (gestureRecognizer.state == UIGestureRecognizerStateEnded)
{
_mapView.settings.scrollGestures = true;
}
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if (gestureRecognizer.numberOfTouches > 1)
{
_mapView.settings.scrollGestures = false;
}
else
{
_mapView.settings.scrollGestures = true;
}
return true;
}
Swift 3:
mapView.settings.scrollGestures = false