6

Within my app we have a screen with an MKMapView.

This map shows pins for a bunch of Locations (location is a model defined within the app).

When running in the simulator this works fine. However, when running this on a device the pins seem to disappear and re-appear almost each time the map is panned. Even slight touches and movements can cause the map to show and hide pins.

The map isn't particularly busy, even when showing one pin it will show and hide it.

Has anybody any idea why this might be happening? I've pasted my code below... It's a Rubymotion app so the code is in Ruby.

- UPDATE -

I've added the Objective C equivalent to this Ruby code below. Apologies if there are a couple of typos or idiomatic errors, it's been a while since I've written any OC.

- UPDATE 2 -

Watching the logger, I can see that mapView:viewForAnnotation is being called each time a map pin disappears/reappears.

Also each time mapView:regionDidChangeAnimated I can see that the Object IDs for the annotations are the same - so I don't think they're being removed (which they shouldn't be)

# ====================
# = MKMapKitDelegate =
# ====================

# Don't react if the user has moved less than three meters
USER_MOVE_THRESHOLD = 3

# The user location has changed
def mapView(mapView, didUpdateUserLocation: newLocation)
  NSLog("mapView:didUpdateUserLocation")
  return unless userLocation
  coord = newLocation.coordinate
  newLocationAsCL = CLLocation.alloc.initWithCoordinate(coord, altitude: 1, horizontalAccuracy:1, verticalAccuracy: -1, timestamp: nil)    
  meters = newLocationAsCL.distanceFromLocation(@lastUserCLLocation)

  # If user has moved less than 3m, return
  if meters > 0 and meters < USER_MOVE_THRESHOLD
    log "Distance was less than #{USER_MOVE_THRESHOLD} meters (#{meters}) - returning ***"
    return
  end

  # If the coord is the same as the previous user location
  if userLocation.coordinate.latitude == coord.latitude && userLocation.coordinate.longitude == coord.longitude
    log "User hasn't moved - returning ***"
  else
    log 'User has moved'
  end

  log "Did update user location: #{coord.latitude},#{coord.longitude}"

  if coord.latitude.to_f == 0.0 and coord.longitude.to_f == 0.0
    log 'Invalid coordinate received - returning ***'
  else
    fetchLocationsFromAPI
  end
end

def mapView(mapView, regionDidChangeAnimated: animated)
  NSLog("mapView:regionDidChangeAnimated:#{animated}")
  # do nothing here yet...
end

# create map pins...
def mapView(mapView, viewForAnnotation: annotation)
  log "mapView:viewForAnnotation: #{annotation.inspect}"

  if annotation.is_a?(Location)
    # If there's already an annotation we can use, use it! Otherwise create a new one
    annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(annotation.class.to_s) || begin
      annotationView = MKPinAnnotationView.alloc.initWithAnnotation(annotation, reuseIdentifier: annotation.class.to_s)
      annotationView.enabled        = true
      annotationView.canShowCallout = true
      annotationView.animatesDrop   = false
      annotationView.pinColor = MKPinAnnotationColorRed

      rightButton = UIButton.buttonWithType(UIButtonTypeDetailDisclosure)
      rightButton.addTarget(self, action: 'showLocationScreen:', forControlEvents: UIControlEventTouchUpInside)
      annotationView.rightCalloutAccessoryView = rightButton      
      annotationView      
    end
    annotationView.annotation = annotation
    annotationView.rightCalloutAccessoryView.tag = @mapLocations.index(annotation)
    return annotationView
  end
end

def mapView(mapViewm, didAddAnnotationViews: views)
  NSLog("mapView:didAddAnnotationViews - #{views}")
  # do nothing here yet...
end

Objective C

#define kUserMoveThreshold 1

-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)newLocation
{
  NSLog(@"mapView:didUpdateUserLocation")
  if (!userLocation) {
    return;
  }
  CLLocationCoordinate2D coord = newLocation.coordinate;
  CLLocation newLocationAsCL = [[CLLocation alloc] initWithCoordinate: coord altitude: 1 horizontalAccuracy: 1 verticalAccuracy: -1 timestamp: NULL];
  CLLocationDistance meters = [newLocationAsCL distanceFromLocation: lastUserCLLocation];

  // If user has moved less than 3m, return
  if (meters > 0 && meters < kUserMoveThreshold){
    NSLog(@"Distance was less than %d meters (%d) - returning ***", kUserMoveThreshold, meters);
    return;
  }

  // If the coord is the same as the previous user location
  if (userLocation.coordinate.latitude == coord.latitude && userLocation.coordinate.longitude == coord.longitude){
    NSLog(@"User hasn't moved - returning ***");
    return;
  } else {
    NSLog(@"User has moved");  
  }
  NSLog(@"Did update user location: %f,%f", coord.latitude, coord.longitude);
  if (coord.latitude == 0.0 && coord.longitude == 0.0){
    NSLog(@"Invalid coordinate received - returning ***");
  } else {
    [self fetchLocationsFromAPI];
  }
}

-(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
  NSLog(@"mapView:regionDidChangeAnimated: %s", animated ? @"TRUE" : @"FALSE");
}

-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id < MKAnnotation >)annotation
{
  NSLog(@"mapView:viewForAnnotation %s", annotation.description);

  if ([annotation isKindOfClass: [Location class]]){
      // If there's already an annotation we can use, use it! Otherwise create a new one
    MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier: [annotation className]];
    if (!annotationView){
      annotationView = [[MKPinAnnotationView alloc] initWithAnnotation: annotation reuseIdentifier: [annotation className]];
      [annotationView setEnabled: YES];
      [annotationView setCanShowCallout: YES];
      [annotationView setAnimatesDrop: NO];
      [annotationView setPinColor: MKPinAnnotationColorRed];

      UIButton *rightButton = [UIButton buttonWithType: UIButtonTypeDetailDisclosure];
      [rightButton addTarget: self action: @selector(showLocationScreen:) forControlEvents: UIControlEventTouchUpInside];
      [annotationView setRightCalloutAccessoryView: rightButton];
    }

    [annotationView annotation: annotation];
    [[annotationView rightCalloutAccessoryView] setTag: [mapLocations indexOfObject: annotation]];
    return annotationView
  }
}

-(void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
{
  NSLog(@"mapView:didAddAnnotationViews %@", views)
}
bodacious
  • 6,608
  • 9
  • 45
  • 74
  • `didUpdateUserLocation` may or may not be being called when the user pans the map around. it would probably be more useful to show the code where you set up the map and add the annotations. viewForAnnotations is another relevant one too. And any code that responds to zoom or uses 'addAnnotation` or `removeAnnotation` – Craig Nov 07 '12 at 19:57
  • I am having the same issue also with rubymotion. Pins disappear and reappear randomly even on the simulatior. I am trying to lazy load annotations. Any breakthroughs? – Macario Dec 05 '12 at 08:05
  • Nothing yet - are you using Rubymotion or Objective C/Xcode? – bodacious Dec 10 '12 at 11:53

2 Answers2

2

This was a bug with Rubymotion which seems to have been addressed in version 1.30

= RubyMotion 1.30 =

...

  • Fixed a bug where MapKit annotation pins would disappear, because certain Ruby objects (in this case, MKAnnotations) would use a Bignum value as the return value of the `hash' method, which would not work properly when used within MapKit.

...

bodacious
  • 6,608
  • 9
  • 45
  • 74
0

When you create pin annotations, are you adding them to the mapView's annotation array?

CLLocationCoordinate2D locationForSelectedFloorOffice;
locationForSelectedFloorOffice.longitude = [[myGeoCode objectAtIndex:0] floatValue] * 1.0;
locationForSelectedFloorOffice.latitude = [[myGeoCode objectAtIndex:1] floatValue] * 1.0;
MyLocation *annotation = [[MyLocation alloc] initWithName:dStore.selectedFloorRoomName address:@" " coordinate:locationForSelectedFloorOffice];
[self.mapView addAnnotation:annotation];
Alex Zavatone
  • 4,106
  • 36
  • 54
  • Yes - I should have mentioned that. Location has title and subtitle defined so that it conforms to MKAnnotation and elsewhere in the controller `[self.mapView addAnnotation: locations]` is called. This happens after an API call and that method is not called again when the pins disappear/reappear. – bodacious Nov 07 '12 at 17:39