devios1
devios1

Reputation: 37985

MKMapView setVisibleMapRect with completion?

I am having a heck of a time getting around some very annoying behavior in MapKit. Specifically, I want to show a popover annotation view after the map has animated to a certain visible region.

I can use mapView:regionDidChangeAnimated: to get a callback after the map transition (which I will call the "zoom"), but this only fires if the visible region actually changes.

You see the zoom happens in my app when an annotation (or its counterpart in a table view) is selected: I want to zoom the map to essentially focus on the selected annotation.

The problem is that if the map is already in the correct region, I will get no regionDidChange, and therefore no signal to show my popover view.

I am using variants of setVisibleMapRect: to actually perform the zoom. So I thought I was being smart by comparing the new and old MKMapRects to see if they are equal, and if so manually calling my callback.

The problem is it doesn't work! Even if I determine that the two MKMapRects are not equal, by means of MKMapRectEqualToRect, MapKit just sometimes decides it won't fire the regionDidChange event! Perhaps it has to be over a certain delta or something, I don't know.

So my question is: what the heck is the expected best practice for getting a guaranteed completion from setVisibleMapRect:?

Update: I'm now thinking this might have to do with the edgePadding argument which I am also using:

- (BOOL)vivoSetVisibleMapRect:(MKMapRect)mapRect edgePadding:(UIEdgeInsets)insets animated:(BOOL)animate {
    BOOL equal = MKMapRectEqualToRect(self.visibleMapRect, mapRect);
    NSLog(@"Map rects are: %@", equal ? @"equal" : @"NOT equal");
    NSLog(@"%@\n\n%@", MKStringFromMapRect(self.visibleMapRect), MKStringFromMapRect(newRect));
    [self setVisibleMapRect:mapRect edgePadding:insets animated:animate];
    return !equal;
}

As you can see, the edge padding is not taken into account in the comparison, yet it is most likely having an effect on the finally computed mapRect.

Does anyone know how I can perform this test to properly take into account edgePadding?

Update 2: It's now looking like MKMapRectEqualToRect is wrong and therefore completely useless. Check out this log statement:

2016-03-16 17:06:30.841 Mobile[70089:6240786] Map rects are: NOT equal

{{42403042.3, 91858289.9}, {14878.4, 12832.6}}

{{42403042.3, 91858289.9}, {14878.4, 12832.6}}

They look pretty darn equal to me!! 😒

Upvotes: 1

Views: 1383

Answers (2)

Paul King
Paul King

Reputation: 2001

MapRects are defined using Doubles, and comparing Doubles can give unexpected behavior. My recommendation is to define your own comparison that compares to your desired tolerance. For instance, compare to the nearest integer value.

In Swift:

public extension Double {
  func roundToInt() -> Int {
    let value = Int(self)
    return self - Double(value) < 0.5 ? value : value + 1
  }
}

public func == (lhs: MKMapRect, rhs: MKMapRect) -> Bool {
  if lhs.origin.x.roundToInt() != rhs.origin.x.roundToInt() {return false}
  if lhs.origin.y.roundToInt() != rhs.origin.y.roundToInt() {return false}
  if lhs.size.width.roundToInt() != rhs.size.width.roundToInt() {return false}
  if lhs.size.height.roundToInt() != rhs.size.height.roundToInt() {return false}
  return true
}

You can then do a comparison by typing

if mapRect1 == mapRect 2 {
  ...
}

Upvotes: 1

devios1
devios1

Reputation: 37985

It turns out MKMapRectEqualToRect is completely broken (at least on the simulator). Don't use it!!

I changed it to instead do a comparison on the strings returned by MKStringFromMapRect, as well as adjusting the map rect with mapRectThatFits:edgePadding: before the comparison and it now appears to be working correctly:

- (BOOL)vivoSetVisibleMapRect:(MKMapRect)mapRect edgePadding:(UIEdgeInsets)insets animated:(BOOL)animate {
    MKMapRect newRect = [self mapRectThatFits:mapRect edgePadding:insets];
    BOOL equal = [MKStringFromMapRect(self.visibleMapRect) isEqualToString:MKStringFromMapRect(newRect)];
    [self setVisibleMapRect:newRect animated:animate];
    return !equal;
}

Upvotes: 0

Related Questions