Authored by Justin R. Miller

Merge branch 'develop' of github.com:mapbox/mapbox-ios-sdk into develop

... ... @@ -34,7 +34,7 @@
@interface RMAbstractWebMapSource : RMAbstractMercatorTileSource
@property (nonatomic, assign) NSUInteger retryCount;
@property (nonatomic, assign) NSTimeInterval waitSeconds;
@property (nonatomic, assign) NSTimeInterval requestTimeoutSeconds;
- (NSURL *)URLForTile:(RMTile)tile;
... ...
... ... @@ -28,9 +28,11 @@
#import "RMAbstractWebMapSource.h"
#import "RMTileCache.h"
#define HTTP_404_NOT_FOUND 404
@implementation RMAbstractWebMapSource
@synthesize retryCount, waitSeconds;
@synthesize retryCount, requestTimeoutSeconds;
- (id)init
{
... ... @@ -38,7 +40,7 @@
return nil;
self.retryCount = RMAbstractWebMapSourceDefaultRetryCount;
self.waitSeconds = RMAbstractWebMapSourceDefaultWaitSeconds;
self.requestTimeoutSeconds = RMAbstractWebMapSourceDefaultWaitSeconds;
return self;
}
... ... @@ -74,66 +76,82 @@
NSArray *URLs = [self URLsForTile:tile];
// fill up collection array with placeholders
//
NSMutableArray *tilesData = [NSMutableArray arrayWithCapacity:[URLs count]];
if ([URLs count] > 1)
{
// fill up collection array with placeholders
//
NSMutableArray *tilesData = [NSMutableArray arrayWithCapacity:[URLs count]];
for (NSUInteger p = 0; p < [URLs count]; ++p)
[tilesData addObject:[NSNull null]];
for (NSUInteger p = 0; p < [URLs count]; ++p)
[tilesData addObject:[NSNull null]];
dispatch_group_t fetchGroup = dispatch_group_create();
dispatch_group_t fetchGroup = dispatch_group_create();
for (NSUInteger u = 0; u < [URLs count]; ++u)
{
NSURL *currentURL = [URLs objectAtIndex:u];
dispatch_group_async(fetchGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
for (NSUInteger u = 0; u < [URLs count]; ++u)
{
NSData *tileData = nil;
NSURL *currentURL = [URLs objectAtIndex:u];
for (NSUInteger try = 0; tileData == nil && try < self.retryCount; ++try)
dispatch_group_async(fetchGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:currentURL];
[request setTimeoutInterval:(self.waitSeconds / (CGFloat)self.retryCount)];
tileData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
}
NSData *tileData = nil;
for (NSUInteger try = 0; tileData == nil && try < self.retryCount; ++try)
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:currentURL];
[request setTimeoutInterval:(self.requestTimeoutSeconds / (CGFloat)self.retryCount)];
tileData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
}
if (tileData)
{
@synchronized(self)
{
// safely put into collection array in proper order
//
[tilesData replaceObjectAtIndex:u withObject:tileData];
};
}
});
}
if (tileData)
// wait for whole group of fetches (with retries) to finish, then clean up
//
dispatch_group_wait(fetchGroup, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * self.requestTimeoutSeconds));
dispatch_release(fetchGroup);
// composite the collected images together
//
for (NSData *tileData in tilesData)
{
if (tileData && [tileData isKindOfClass:[NSData class]] && [tileData length])
{
@synchronized(self)
if (image != nil)
{
UIGraphicsBeginImageContext(image.size);
[image drawAtPoint:CGPointMake(0,0)];
[[UIImage imageWithData:tileData] drawAtPoint:CGPointMake(0,0)];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
else
{
// safely put into collection array in proper order
//
[tilesData replaceObjectAtIndex:u withObject:tileData];
};
image = [UIImage imageWithData:tileData];
}
}
});
}
}
// wait for whole group of fetches (with retries) to finish, then clean up
//
dispatch_group_wait(fetchGroup, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * self.waitSeconds));
dispatch_release(fetchGroup);
// composite the collected images together
//
for (NSData *tileData in tilesData)
else
{
if (tileData && [tileData isKindOfClass:[NSData class]] && [tileData length])
for (NSUInteger try = 0; image == nil && try < self.retryCount; ++try)
{
if (image != nil)
{
UIGraphicsBeginImageContext(image.size);
[image drawAtPoint:CGPointMake(0,0)];
[[UIImage imageWithData:tileData] drawAtPoint:CGPointMake(0,0)];
NSHTTPURLResponse *response = nil;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[URLs objectAtIndex:0]];
[request setTimeoutInterval:(self.requestTimeoutSeconds / (CGFloat)self.retryCount)];
image = [UIImage imageWithData:[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil]];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
else
{
image = [UIImage imageWithData:tileData];
}
if (response.statusCode == HTTP_404_NOT_FOUND)
break;
}
}
... ...
... ... @@ -74,7 +74,7 @@
@property (nonatomic, assign) RMQuadTreeNode *quadTreeNode;
// This is for filtering framework-provided annotations.
@property (nonatomic, assign, readonly) BOOL isUserLocationAnnotation;
@property (nonatomic, readonly) BOOL isUserLocationAnnotation;
#pragma mark -
... ...
... ... @@ -76,6 +76,8 @@
self.enabled = YES;
self.clusteringEnabled = YES;
self.isUserLocationAnnotation = NO;
layer = nil;
return self;
... ...
... ... @@ -12,6 +12,9 @@
#import "RMTileSource.h"
@implementation RMAttributionViewController
{
RMMapView *_mapView;
}
- (id)initWithMapView:(RMMapView *)mapView
{
... ... @@ -24,6 +27,8 @@
if (self)
{
_mapView = mapView;
self.view.backgroundColor = [UIColor darkGrayColor];
[self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissModalViewControllerAnimated:)]];
... ... @@ -32,6 +37,8 @@
webView.delegate = self;
webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
webView.backgroundColor = [UIColor clearColor];
webView.opaque = NO;
... ... @@ -56,6 +63,11 @@
return self;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return [_mapView.viewControllerPresentingAttribution shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}
#pragma mark -
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
... ...
... ... @@ -88,12 +88,7 @@ typedef enum : NSUInteger {
@property (nonatomic, assign) BOOL adjustTilesForRetinaDisplay;
@property (nonatomic, readonly) float adjustedZoomForRetinaDisplay; // takes adjustTilesForRetinaDisplay and screen scale into account
@property (nonatomic) BOOL showsUserLocation;
@property (nonatomic, readonly, retain) RMUserLocation *userLocation;
@property (nonatomic, readonly, getter=isUserLocationVisible) BOOL userLocationVisible;
@property (nonatomic) RMUserTrackingMode userTrackingMode;
@property (weak) UIViewController *viewControllerPresentingAttribution;
@property (nonatomic, assign) UIViewController *viewControllerPresentingAttribution;
// take missing tiles from lower zoom levels, up to #missingTilesDepth zoom levels (defaults to 0, which disables this feature)
@property (nonatomic, assign) NSUInteger missingTilesDepth;
... ... @@ -250,6 +245,12 @@ typedef enum : NSUInteger {
#pragma mark -
#pragma mark User Location
@property (nonatomic, assign) BOOL showsUserLocation;
@property (nonatomic, readonly) RMUserLocation *userLocation;
@property (nonatomic, readonly, getter=isUserLocationVisible) BOOL userLocationVisible;
@property (nonatomic, assign) RMUserTrackingMode userTrackingMode;
@property (nonatomic, assign) BOOL displayHeadingCalibration;
- (void)setUserTrackingMode:(RMUserTrackingMode)mode animated:(BOOL)animated;
@end
... ...
... ... @@ -75,6 +75,8 @@
- (void)correctMinZoomScaleForBoundingMask;
- (void)updateHeadingForDeviceOrientation;
@end
#pragma mark -
... ... @@ -160,8 +162,13 @@
UIImageView *userLocationTrackingView;
UIImageView *userHeadingTrackingView;
UIViewController *viewControllerPresentingAttribution;
UIButton *attributionButton;
UIViewController *_viewControllerPresentingAttribution;
UIButton *_attributionButton;
BOOL _userAlteringPanOrZoom;
CGAffineTransform _mapTransform;
CATransform3D _annotationTransform;
}
@synthesize decelerationMode = _decelerationMode;
... ... @@ -176,7 +183,7 @@
@synthesize positionClusterMarkersAtTheGravityCenter = _positionClusterMarkersAtTheGravityCenter;
@synthesize clusterMarkerSize = _clusterMarkerSize, clusterAreaSize = _clusterAreaSize;
@synthesize adjustTilesForRetinaDisplay = _adjustTilesForRetinaDisplay;
@synthesize userLocation, showsUserLocation, userTrackingMode;
@synthesize userLocation, showsUserLocation, userTrackingMode, displayHeadingCalibration;
@synthesize missingTilesDepth = _missingTilesDepth;
@synthesize debugTiles = _debugTiles;
... ... @@ -240,11 +247,26 @@
[self setDecelerationMode:RMMapDecelerationFast];
[self setBoundingMask:RMMapMinHeightBound];
self.displayHeadingCalibration = YES;
_mapTransform = CGAffineTransformIdentity;
_annotationTransform = CATransform3DIdentity;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleMemoryWarningNotification:)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleWillChangeOrientationNotification:)
name:UIApplicationWillChangeStatusBarOrientationNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleDidChangeOrientationNotification:)
name:UIApplicationDidChangeStatusBarOrientationNotification
object:nil];
RMLog(@"Map initialised. tileSource:%@, minZoom:%f, maxZoom:%f, zoom:%f at {%f,%f}", newTilesource, self.minZoom, self.maxZoom, self.zoom, initialCenterCoordinate.longitude, initialCenterCoordinate.latitude);
}
... ... @@ -361,7 +383,7 @@
[userLocation release]; userLocation = nil;
[userLocationTrackingView release]; userLocationTrackingView = nil;
[userHeadingTrackingView release]; userHeadingTrackingView = nil;
[attributionButton release]; attributionButton = nil;
[_attributionButton release]; _attributionButton = nil;
[super dealloc];
}
... ... @@ -378,6 +400,19 @@
[self didReceiveMemoryWarning];
}
- (void)handleWillChangeOrientationNotification:(NSNotification *)notification
{
// send a dummy heading update to force re-rotation
//
if (userTrackingMode == RMUserTrackingModeFollowWithHeading)
[self locationManager:locationManager didUpdateHeading:locationManager.heading];
}
- (void)handleDidChangeOrientationNotification:(NSNotification *)notification
{
[self updateHeadingForDeviceOrientation];
}
- (NSString *)description
{
CGRect bounds = self.bounds;
... ... @@ -400,13 +435,13 @@
_delegate = aDelegate;
_delegateHasBeforeMapMove = [_delegate respondsToSelector:@selector(beforeMapMove:)];
_delegateHasAfterMapMove = [_delegate respondsToSelector:@selector(afterMapMove:)];
_delegateHasBeforeMapMove = [_delegate respondsToSelector:@selector(beforeMapMove:byUser:)];
_delegateHasAfterMapMove = [_delegate respondsToSelector:@selector(afterMapMove:byUser:)];
_delegateHasBeforeMapZoom = [_delegate respondsToSelector:@selector(beforeMapZoom:)];
_delegateHasAfterMapZoom = [_delegate respondsToSelector:@selector(afterMapZoom:)];
_delegateHasBeforeMapZoom = [_delegate respondsToSelector:@selector(beforeMapZoom:byUser:)];
_delegateHasAfterMapZoom = [_delegate respondsToSelector:@selector(afterMapZoom:byUser:)];
_delegateHasMapViewRegionDidChange = [_delegate respondsToSelector:@selector(mapViewRegionDidChange:)];
_delegateHasMapViewRegionDidChange = [_delegate respondsToSelector:@selector(mapViewRegionDidChange:byUser:)];
_delegateHasDoubleTapOnMap = [_delegate respondsToSelector:@selector(doubleTapOnMap:at:)];
_delegateHasSingleTapOnMap = [_delegate respondsToSelector:@selector(singleTapOnMap:at:)];
... ... @@ -593,7 +628,7 @@
- (void)setCenterProjectedPoint:(RMProjectedPoint)centerProjectedPoint animated:(BOOL)animated
{
if (_delegateHasBeforeMapMove)
[_delegate beforeMapMove:self];
[_delegate beforeMapMove:self byUser:_userAlteringPanOrZoom];
// RMLog(@"Current contentSize: {%.0f,%.0f}, zoom: %f", mapScrollView.contentSize.width, mapScrollView.contentSize.height, self.zoom);
... ... @@ -609,7 +644,7 @@
// RMLog(@"setMapCenterProjectedPoint: {%f,%f} -> {%.0f,%.0f}", centerProjectedPoint.x, centerProjectedPoint.y, mapScrollView.contentOffset.x, mapScrollView.contentOffset.y);
if (_delegateHasAfterMapMove && !animated)
[_delegate afterMapMove:self];
[_delegate afterMapMove:self byUser:_userAlteringPanOrZoom];
[self correctPositionOfAllAnnotations];
}
... ... @@ -619,7 +654,7 @@
- (void)moveBy:(CGSize)delta
{
if (_delegateHasBeforeMapMove)
[_delegate beforeMapMove:self];
[_delegate beforeMapMove:self byUser:_userAlteringPanOrZoom];
CGPoint contentOffset = _mapScrollView.contentOffset;
contentOffset.x += delta.width;
... ... @@ -627,7 +662,7 @@
_mapScrollView.contentOffset = contentOffset;
if (_delegateHasAfterMapMove)
[_delegate afterMapMove:self];
[_delegate afterMapMove:self byUser:_userAlteringPanOrZoom];
}
#pragma mark -
... ... @@ -824,7 +859,7 @@
- (void)zoomInToNextNativeZoomAt:(CGPoint)pivot animated:(BOOL)animated
{
if (self.userTrackingMode != RMUserTrackingModeNone && ! CGPointEqualToPoint(pivot, self.center))
if (self.userTrackingMode != RMUserTrackingModeNone && ! CGPointEqualToPoint(pivot, [self coordinateToPixel:userLocation.location.coordinate]))
self.userTrackingMode = RMUserTrackingModeNone;
// Calculate rounded zoom
... ... @@ -935,9 +970,6 @@
myOrigin.y = myOrigin.y - (zoomRect.size.height / 2);
zoomRect.origin = myOrigin;
RMProjectedPoint topRight = RMProjectedPointMake(myOrigin.x + zoomRect.size.width, myOrigin.y + zoomRect.size.height);
RMLog(@"zoomWithBoundingBox: {%f,%f} - {%f,%f}", [_projection projectedPointToCoordinate:myOrigin].longitude, [_projection projectedPointToCoordinate:myOrigin].latitude, [_projection projectedPointToCoordinate:topRight].longitude, [_projection projectedPointToCoordinate:topRight].latitude);
[self setProjectedBounds:zoomRect animated:animated];
}
}
... ... @@ -1079,14 +1111,19 @@
if (self.userTrackingMode != RMUserTrackingModeNone)
self.userTrackingMode = RMUserTrackingModeNone;
_userAlteringPanOrZoom = YES;
if (_delegateHasBeforeMapMove)
[_delegate beforeMapMove:self];
[_delegate beforeMapMove:self byUser:_userAlteringPanOrZoom];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (!decelerate && _delegateHasAfterMapMove)
[_delegate afterMapMove:self];
[_delegate afterMapMove:self byUser:_userAlteringPanOrZoom];
if (!decelerate)
_userAlteringPanOrZoom = NO;
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
... ... @@ -1098,21 +1135,25 @@
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
if (_delegateHasAfterMapMove)
[_delegate afterMapMove:self];
[_delegate afterMapMove:self byUser:_userAlteringPanOrZoom];
_userAlteringPanOrZoom = NO;
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
if (_delegateHasAfterMapMove)
[_delegate afterMapMove:self];
[_delegate afterMapMove:self byUser:_userAlteringPanOrZoom];
}
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
{
_mapScrollViewIsZooming = YES;
_userAlteringPanOrZoom = (scrollView.pinchGestureRecognizer.state == UIGestureRecognizerStateBegan);
if (_delegateHasBeforeMapZoom)
[_delegate beforeMapZoom:self];
[_delegate beforeMapZoom:self byUser:_userAlteringPanOrZoom];
}
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
... ... @@ -1124,7 +1165,9 @@
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
if (self.userTrackingMode != RMUserTrackingModeNone && scrollView.pinchGestureRecognizer.state == UIGestureRecognizerStateChanged)
_userAlteringPanOrZoom = (scrollView.pinchGestureRecognizer.state == UIGestureRecognizerStateChanged);
if (self.userTrackingMode != RMUserTrackingModeNone && _userAlteringPanOrZoom)
self.userTrackingMode = RMUserTrackingModeNone;
[self correctPositionOfAllAnnotations];
... ... @@ -1133,7 +1176,7 @@
self.userTrackingMode = RMUserTrackingModeFollow;
if (_delegateHasAfterMapZoom)
[_delegate afterMapZoom:self];
[_delegate afterMapZoom:self byUser:_userAlteringPanOrZoom];
}
// Detect dragging/zooming
... ... @@ -1272,7 +1315,7 @@
// Don't do anything stupid here or your scrolling experience will suck
if (_delegateHasMapViewRegionDidChange)
[_delegate mapViewRegionDidChange:self];
[_delegate mapViewRegionDidChange:self byUser:_userAlteringPanOrZoom];
}
#pragma mark - Gesture Recognizers and event handling
... ... @@ -1300,7 +1343,7 @@
- (void)handleSingleTap:(UIGestureRecognizer *)recognizer
{
CALayer *hit = [_overlayView.layer hitTest:[recognizer locationInView:_overlayView]];
CALayer *hit = [_overlayView.layer hitTest:[recognizer locationInView:self]];
if ( ! hit)
{
... ... @@ -1332,9 +1375,17 @@
- (void)doubleTapAtPoint:(CGPoint)aPoint
{
if (self.zoomingInPivotsAroundCenter)
{
[self zoomInToNextNativeZoomAt:[self convertPoint:self.center fromView:self.superview] animated:YES];
}
else if (userTrackingMode != RMUserTrackingModeNone && fabsf(aPoint.x - [self coordinateToPixel:userLocation.location.coordinate].x) < 75 && fabsf(aPoint.y - [self coordinateToPixel:userLocation.location.coordinate].y) < 75)
{
[self zoomInToNextNativeZoomAt:[self coordinateToPixel:userLocation.location.coordinate] animated:YES];
}
else
{
[self zoomInToNextNativeZoomAt:aPoint animated:YES];
}
if (_delegateHasDoubleTapOnMap)
[_delegate doubleTapOnMap:self at:aPoint];
... ... @@ -1342,7 +1393,7 @@
- (void)handleDoubleTap:(UIGestureRecognizer *)recognizer
{
CALayer *hit = [_overlayView.layer hitTest:[recognizer locationInView:_overlayView]];
CALayer *hit = [_overlayView.layer hitTest:[recognizer locationInView:self]];
if ( ! hit)
{
... ... @@ -1373,7 +1424,12 @@
- (void)handleTwoFingerSingleTap:(UIGestureRecognizer *)recognizer
{
[self zoomOutToNextNativeZoomAt:[self convertPoint:self.center fromView:self.superview] animated:YES];
CGPoint centerPoint = [self convertPoint:self.center fromView:self.superview];
if (userTrackingMode != RMUserTrackingModeNone)
centerPoint = [self coordinateToPixel:userLocation.location.coordinate];
[self zoomOutToNextNativeZoomAt:centerPoint animated:YES];
if (_delegateHasSingleTapTwoFingersOnMap)
[_delegate singleTapTwoFingersOnMap:self at:[recognizer locationInView:self]];
... ... @@ -1396,6 +1452,9 @@
// check whether our custom pan gesture recognizer should start recognizing the gesture
CALayer *hit = [_overlayView.layer hitTest:[recognizer locationInView:_overlayView]];
if ([hit isEqual:_overlayView.layer])
return NO;
if (!hit || ([hit respondsToSelector:@selector(enableDragging)] && ![(RMMarker *)hit enableDragging]))
return NO;
... ... @@ -2241,6 +2300,8 @@
if (annotation.layer == nil)
continue;
annotation.layer.transform = _annotationTransform;
// Use the zPosition property to order the layer hierarchy
if ( ! [_visibleAnnotations containsObject:annotation])
{
... ... @@ -2292,6 +2353,8 @@
if (annotation.layer == nil)
continue;
annotation.layer.transform = _annotationTransform;
if (![_visibleAnnotations containsObject:annotation])
{
if (!lastLayer)
... ... @@ -2438,18 +2501,17 @@
{
if (newShowsUserLocation == showsUserLocation)
return;
showsUserLocation = newShowsUserLocation;
if (newShowsUserLocation)
{
if (_delegateHasWillStartLocatingUser)
[_delegate mapViewWillStartLocatingUser:self];
self.userLocation = [RMUserLocation annotationWithMapView:self coordinate:CLLocationCoordinate2DMake(0, 0) andTitle:nil];
locationManager = [[CLLocationManager alloc] init];
locationManager.headingFilter = 5;
locationManager.delegate = self;
[locationManager startUpdatingLocation];
}
... ... @@ -2460,21 +2522,25 @@
locationManager.delegate = nil;
[locationManager release];
locationManager = nil;
if (_delegateHasDidStopLocatingUser)
[_delegate mapViewDidStopLocatingUser:self];
[self setUserTrackingMode:RMUserTrackingModeNone animated:YES];
NSMutableArray *annotationsToRemove = [NSMutableArray array];
for (RMAnnotation *annotation in _annotations)
{
if (annotation.isUserLocationAnnotation)
[annotationsToRemove addObject:annotation];
}
for (RMAnnotation *annotationToRemove in annotationsToRemove)
{
[self removeAnnotation:annotationToRemove];
}
self.userLocation = nil;
}
}
... ... @@ -2493,15 +2559,15 @@
if (userLocation)
{
CGPoint locationPoint = [self mapPositionForAnnotation:userLocation];
CGRect locationRect = CGRectMake(locationPoint.x - userLocation.location.horizontalAccuracy,
locationPoint.y - userLocation.location.horizontalAccuracy,
userLocation.location.horizontalAccuracy * 2,
userLocation.location.horizontalAccuracy * 2);
return CGRectIntersectsRect([self bounds], locationRect);
}
return NO;
}
... ... @@ -2514,9 +2580,9 @@
{
if (mode == userTrackingMode)
return;
userTrackingMode = mode;
switch (userTrackingMode)
{
case RMUserTrackingModeNone:
... ... @@ -2524,20 +2590,27 @@
{
[locationManager stopUpdatingHeading];
[CATransaction setAnimationDuration:0.5];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[UIView animateWithDuration:(animated ? 0.5 : 0.0)
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationCurveEaseInOut
animations:^(void)
{
_mapScrollView.transform = CGAffineTransformIdentity;
_overlayView.transform = CGAffineTransformIdentity;
_mapTransform = CGAffineTransformIdentity;
_annotationTransform = CATransform3DIdentity;
_mapScrollView.transform = _mapTransform;
_overlayView.transform = _mapTransform;
for (RMAnnotation *annotation in _annotations)
if ( ! annotation.isUserLocationAnnotation)
annotation.layer.transform = CATransform3DIdentity;
annotation.layer.transform = _annotationTransform;
}
completion:nil];
[CATransaction commit];
if (userLocationTrackingView || userHeadingTrackingView)
{
[userLocationTrackingView removeFromSuperview];
... ... @@ -2545,15 +2618,15 @@
[userHeadingTrackingView removeFromSuperview];
userHeadingTrackingView = nil;
}
userLocation.layer.hidden = NO;
break;
}
case RMUserTrackingModeFollow:
{
self.showsUserLocation = YES;
[locationManager stopUpdatingHeading];
if (self.userLocation)
... ... @@ -2566,57 +2639,78 @@
[userHeadingTrackingView removeFromSuperview];
userHeadingTrackingView = nil;
}
[CATransaction setAnimationDuration:0.5];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[UIView animateWithDuration:(animated ? 0.5 : 0.0)
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationCurveEaseInOut
animations:^(void)
{
_mapScrollView.transform = CGAffineTransformIdentity;
_overlayView.transform = CGAffineTransformIdentity;
_mapTransform = CGAffineTransformIdentity;
_annotationTransform = CATransform3DIdentity;
_mapScrollView.transform = _mapTransform;
_overlayView.transform = _mapTransform;
for (RMAnnotation *annotation in _annotations)
if ( ! annotation.isUserLocationAnnotation)
annotation.layer.transform = CATransform3DIdentity;
annotation.layer.transform = _annotationTransform;
}
completion:nil];
[CATransaction commit];
userLocation.layer.hidden = NO;
break;
}
case RMUserTrackingModeFollowWithHeading:
{
self.showsUserLocation = YES;
userLocation.layer.hidden = YES;
userHeadingTrackingView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"HeadingAngleSmall.png"]];
userHeadingTrackingView.center = CGPointMake(round([self bounds].size.width / 2),
round([self bounds].size.height / 2) - (userHeadingTrackingView.bounds.size.height / 2) - 4);
userHeadingTrackingView.frame = CGRectMake((self.bounds.size.width / 2) - (userHeadingTrackingView.bounds.size.width / 2),
(self.bounds.size.height / 2) - userHeadingTrackingView.bounds.size.height,
userHeadingTrackingView.bounds.size.width,
userHeadingTrackingView.bounds.size.height * 2);
userHeadingTrackingView.contentMode = UIViewContentModeTop;
userHeadingTrackingView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin |
UIViewAutoresizingFlexibleRightMargin |
UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleBottomMargin;
userHeadingTrackingView.alpha = 0.0;
[self addSubview:userHeadingTrackingView];
[UIView animateWithDuration:0.5 animations:^(void) { userHeadingTrackingView.alpha = 1.0; }];
userLocationTrackingView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"TrackingDot.png"]];
userLocationTrackingView.center = CGPointMake(round([self bounds].size.width / 2),
round([self bounds].size.height / 2));
userLocationTrackingView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin |
UIViewAutoresizingFlexibleRightMargin |
UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleBottomMargin;
[self addSubview:userLocationTrackingView];
if (self.zoom < 3)
[self zoomByFactor:exp2f(3 - [self zoom]) near:self.center animated:YES];
if (self.userLocation)
[self locationManager:locationManager didUpdateToLocation:self.userLocation.location fromLocation:self.userLocation.location];
[self updateHeadingForDeviceOrientation];
[locationManager startUpdatingHeading];
break;
}
}
... ... @@ -2629,7 +2723,7 @@
{
if ( ! showsUserLocation || _mapScrollView.isDragging)
return;
if ([newLocation distanceFromLocation:oldLocation])
{
userLocation.location = newLocation;
... ... @@ -2637,185 +2731,230 @@
if (_delegateHasDidUpdateUserLocation)
[_delegate mapView:self didUpdateUserLocation:userLocation];
}
if (self.userTrackingMode != RMUserTrackingModeNone)
{
// zoom centered on user location unless we're already centered there (or very close)
// center on user location unless we're already centered there (or very close)
//
CGPoint mapCenterPoint = [self convertPoint:self.center fromView:self.superview];
CGPoint userLocationPoint = [self mapPositionForAnnotation:userLocation];
if (fabsf(userLocationPoint.x - mapCenterPoint.x) > 2 || fabsf(userLocationPoint.y - mapCenterPoint.y > 2))
if (fabsf(userLocationPoint.x - mapCenterPoint.x) || fabsf(userLocationPoint.y - mapCenterPoint.y))
{
float delta = newLocation.horizontalAccuracy / 110000; // approx. meter per degree latitude
CLLocationCoordinate2D southWest = CLLocationCoordinate2DMake(newLocation.coordinate.latitude - delta,
newLocation.coordinate.longitude - delta);
CLLocationCoordinate2D northEast = CLLocationCoordinate2DMake(newLocation.coordinate.latitude + delta,
newLocation.coordinate.longitude + delta);
if (northEast.latitude != [self latitudeLongitudeBoundingBox].northEast.latitude ||
northEast.longitude != [self latitudeLongitudeBoundingBox].northEast.longitude ||
southWest.latitude != [self latitudeLongitudeBoundingBox].southWest.latitude ||
southWest.longitude != [self latitudeLongitudeBoundingBox].southWest.longitude)
[self zoomWithLatitudeLongitudeBoundsSouthWest:southWest northEast:northEast animated:YES];
if (round(_zoom) >= 10)
{
// at sufficient detail, just re-center the map; don't zoom
//
[self setCenterCoordinate:userLocation.location.coordinate animated:YES];
}
else
{
// otherwise re-center and zoom in to near accuracy confidence
//
float delta = (newLocation.horizontalAccuracy / 110000) * 1.2; // approx. meter per degree latitude, plus some margin
CLLocationCoordinate2D desiredSouthWest = CLLocationCoordinate2DMake(newLocation.coordinate.latitude - delta,
newLocation.coordinate.longitude - delta);
CLLocationCoordinate2D desiredNorthEast = CLLocationCoordinate2DMake(newLocation.coordinate.latitude + delta,
newLocation.coordinate.longitude + delta);
CGFloat pixelRadius = fminf(self.bounds.size.width, self.bounds.size.height) / 2;
CLLocationCoordinate2D actualSouthWest = [self pixelToCoordinate:CGPointMake(userLocationPoint.x - pixelRadius, userLocationPoint.y - pixelRadius)];
CLLocationCoordinate2D actualNorthEast = [self pixelToCoordinate:CGPointMake(userLocationPoint.x + pixelRadius, userLocationPoint.y + pixelRadius)];
if (desiredNorthEast.latitude != actualNorthEast.latitude ||
desiredNorthEast.longitude != actualNorthEast.longitude ||
desiredSouthWest.latitude != actualSouthWest.latitude ||
desiredSouthWest.longitude != actualSouthWest.longitude)
{
[self zoomWithLatitudeLongitudeBoundsSouthWest:desiredSouthWest northEast:desiredNorthEast animated:YES];
}
}
}
}
RMAnnotation *accuracyCircleAnnotation = nil;
for (RMAnnotation *annotation in _annotations)
{
if ([annotation.annotationType isEqualToString:kRMAccuracyCircleAnnotationTypeName])
accuracyCircleAnnotation = annotation;
}
if ( ! accuracyCircleAnnotation)
{
accuracyCircleAnnotation = [RMAnnotation annotationWithMapView:self coordinate:newLocation.coordinate andTitle:nil];
accuracyCircleAnnotation.annotationType = kRMAccuracyCircleAnnotationTypeName;
accuracyCircleAnnotation.clusteringEnabled = NO;
accuracyCircleAnnotation.layer = [[RMCircle alloc] initWithView:self radiusInMeters:newLocation.horizontalAccuracy];
accuracyCircleAnnotation.isUserLocationAnnotation = YES;
((RMCircle *)accuracyCircleAnnotation.layer).lineColor = [UIColor colorWithRed:0.378 green:0.552 blue:0.827 alpha:0.7];
((RMCircle *)accuracyCircleAnnotation.layer).fillColor = [UIColor colorWithRed:0.378 green:0.552 blue:0.827 alpha:0.15];
((RMCircle *)accuracyCircleAnnotation.layer).lineWidthInPixels = 2.0;
[self addAnnotation:accuracyCircleAnnotation];
}
if ([newLocation distanceFromLocation:oldLocation])
accuracyCircleAnnotation.coordinate = newLocation.coordinate;
if (newLocation.horizontalAccuracy != oldLocation.horizontalAccuracy)
((RMCircle *)accuracyCircleAnnotation.layer).radiusInMeters = newLocation.horizontalAccuracy;
RMAnnotation *trackingHaloAnnotation = nil;
for (RMAnnotation *annotation in _annotations)
{
if ([annotation.annotationType isEqualToString:kRMTrackingHaloAnnotationTypeName])
trackingHaloAnnotation = annotation;
}
if ( ! trackingHaloAnnotation)
{
trackingHaloAnnotation = [RMAnnotation annotationWithMapView:self coordinate:newLocation.coordinate andTitle:nil];
trackingHaloAnnotation.annotationType = kRMTrackingHaloAnnotationTypeName;
trackingHaloAnnotation.clusteringEnabled = NO;
// create image marker
//
trackingHaloAnnotation.layer = [[RMMarker alloc] initWithUIImage:[UIImage imageNamed:@"TrackingDotHalo.png"]];
trackingHaloAnnotation.isUserLocationAnnotation = YES;
[CATransaction begin];
[CATransaction setAnimationDuration:2.5];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
// scale out radially
//
CABasicAnimation *boundsAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
boundsAnimation.repeatCount = MAXFLOAT;
boundsAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1, 0.1, 1.0)];
boundsAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(2.0, 2.0, 1.0)];
boundsAnimation.removedOnCompletion = NO;
boundsAnimation.fillMode = kCAFillModeForwards;
[trackingHaloAnnotation.layer addAnimation:boundsAnimation forKey:@"animateScale"];
// go transparent as scaled out
//
CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
opacityAnimation.repeatCount = MAXFLOAT;
opacityAnimation.fromValue = [NSNumber numberWithFloat:1.0];
opacityAnimation.toValue = [NSNumber numberWithFloat:-1.0];
opacityAnimation.removedOnCompletion = NO;
opacityAnimation.fillMode = kCAFillModeForwards;
[trackingHaloAnnotation.layer addAnimation:opacityAnimation forKey:@"animateOpacity"];
[CATransaction commit];
[self addAnnotation:trackingHaloAnnotation];
}
if ([newLocation distanceFromLocation:oldLocation])
trackingHaloAnnotation.coordinate = newLocation.coordinate;
userLocation.layer.hidden = ((trackingHaloAnnotation.coordinate.latitude == 0 && trackingHaloAnnotation.coordinate.longitude == 0) || self.userTrackingMode == RMUserTrackingModeFollowWithHeading);
accuracyCircleAnnotation.layer.hidden = newLocation.horizontalAccuracy <= 10;
trackingHaloAnnotation.layer.hidden = ((trackingHaloAnnotation.coordinate.latitude == 0 && trackingHaloAnnotation.coordinate.longitude == 0) || newLocation.horizontalAccuracy > 10);
if ( ! [_annotations containsObject:userLocation])
[self addAnnotation:userLocation];
[self correctPositionOfAllAnnotations];
}
- (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager
{
return YES;
return self.displayHeadingCalibration;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
if ( ! showsUserLocation || _mapScrollView.isDragging)
if ( ! showsUserLocation || _mapScrollView.isDragging || newHeading.headingAccuracy < 0)
return;
userLocation.heading = newHeading;
if (_delegateHasDidUpdateUserLocation)
[_delegate mapView:self didUpdateUserLocation:userLocation];
if (newHeading.trueHeading != 0 && self.userTrackingMode == RMUserTrackingModeFollowWithHeading)
{
if (userHeadingTrackingView.alpha < 1.0)
[UIView animateWithDuration:0.5 animations:^(void) { userHeadingTrackingView.alpha = 1.0; }];
[CATransaction begin];
[CATransaction setAnimationDuration:1.0];
[CATransaction setAnimationDuration:0.5];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[UIView animateWithDuration:1.0
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationCurveEaseInOut
animations:^(void)
{
CGFloat angle = (M_PI / -180) * newHeading.trueHeading;
_mapScrollView.transform = CGAffineTransformMakeRotation(angle);
_overlayView.transform = CGAffineTransformMakeRotation(angle);
_mapTransform = CGAffineTransformMakeRotation(angle);
_annotationTransform = CATransform3DMakeAffineTransform(CGAffineTransformMakeRotation(-angle));
_mapScrollView.transform = _mapTransform;
_overlayView.transform = _mapTransform;
for (RMAnnotation *annotation in _annotations)
if ( ! annotation.isUserLocationAnnotation)
annotation.layer.transform = CATransform3DMakeAffineTransform(CGAffineTransformMakeRotation(-angle));
if ([annotation.layer isKindOfClass:[RMMarker class]] && ! annotation.isUserLocationAnnotation)
annotation.layer.transform = _annotationTransform;
}
completion:nil];
[CATransaction commit];
}
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
if ([error code] != kCLErrorLocationUnknown)
self.userTrackingMode = RMUserTrackingModeNone;
if (_delegateHasDidFailToLocateUserWithError)
[_delegate mapView:self didFailToLocateUserWithError:error];
}
- (void)updateHeadingForDeviceOrientation
{
if (locationManager)
{
self.userTrackingMode = RMUserTrackingModeNone;
if (_delegateHasDidFailToLocateUserWithError)
[_delegate mapView:self didFailToLocateUserWithError:error];
// note that right/left device and interface orientations are opposites (see UIApplication.h)
//
switch ([[UIApplication sharedApplication] statusBarOrientation])
{
case (UIInterfaceOrientationLandscapeLeft):
{
locationManager.headingOrientation = CLDeviceOrientationLandscapeRight;
break;
}
case (UIInterfaceOrientationLandscapeRight):
{
locationManager.headingOrientation = CLDeviceOrientationLandscapeLeft;
break;
}
case (UIInterfaceOrientationPortraitUpsideDown):
{
locationManager.headingOrientation = CLDeviceOrientationPortraitUpsideDown;
break;
}
case (UIInterfaceOrientationPortrait):
default:
{
locationManager.headingOrientation = CLDeviceOrientationPortrait;
break;
}
}
}
}
... ... @@ -2824,39 +2963,39 @@
- (UIViewController *)viewControllerPresentingAttribution
{
return viewControllerPresentingAttribution;
return _viewControllerPresentingAttribution;
}
- (void)setViewControllerPresentingAttribution:(UIViewController *)viewController
{
viewControllerPresentingAttribution = viewController;
_viewControllerPresentingAttribution = viewController;
if (self.viewControllerPresentingAttribution && ! attributionButton)
if (_viewControllerPresentingAttribution && ! _attributionButton)
{
attributionButton = [[UIButton buttonWithType:UIButtonTypeInfoLight] retain];
_attributionButton = [[UIButton buttonWithType:UIButtonTypeInfoLight] retain];
attributionButton.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin;
_attributionButton.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin;
[attributionButton addTarget:self action:@selector(showAttribution:) forControlEvents:UIControlEventTouchUpInside];
[_attributionButton addTarget:self action:@selector(showAttribution:) forControlEvents:UIControlEventTouchDown];
attributionButton.frame = CGRectMake(self.bounds.size.width - 30,
self.bounds.size.height - 30,
attributionButton.bounds.size.width,
attributionButton.bounds.size.height);
_attributionButton.frame = CGRectMake(self.bounds.size.width - 30,
self.bounds.size.height - 30,
_attributionButton.bounds.size.width,
_attributionButton.bounds.size.height);
[self addSubview:attributionButton];
[self addSubview:_attributionButton];
}
}
- (void)showAttribution:(id)sender
{
if (self.viewControllerPresentingAttribution)
if (_viewControllerPresentingAttribution)
{
RMAttributionViewController *attributionViewController = [[[RMAttributionViewController alloc] initWithMapView:self] autorelease];
attributionViewController.modalTransitionStyle = UIModalTransitionStylePartialCurl;
[self.viewControllerPresentingAttribution presentModalViewController:attributionViewController animated:YES];
[_viewControllerPresentingAttribution presentModalViewController:attributionViewController animated:YES];
}
}
... ...
... ... @@ -31,7 +31,7 @@
@class RMAnnotation;
@class RMUserLocation;
typedef enum {
typedef enum : NSUInteger {
RMUserTrackingModeNone = 0,
RMUserTrackingModeFollow = 1,
RMUserTrackingModeFollowWithHeading = 2
... ... @@ -45,11 +45,11 @@ typedef enum {
- (void)mapView:(RMMapView *)mapView willHideLayerForAnnotation:(RMAnnotation *)annotation;
- (void)mapView:(RMMapView *)mapView didHideLayerForAnnotation:(RMAnnotation *)annotation;
- (void)beforeMapMove:(RMMapView *)map;
- (void)afterMapMove:(RMMapView *)map;
- (void)beforeMapMove:(RMMapView *)map byUser:(BOOL)wasUserAction;
- (void)afterMapMove:(RMMapView *)map byUser:(BOOL)wasUserAction;
- (void)beforeMapZoom:(RMMapView *)map;
- (void)afterMapZoom:(RMMapView *)map;
- (void)beforeMapZoom:(RMMapView *)map byUser:(BOOL)wasUserAction;
- (void)afterMapZoom:(RMMapView *)map byUser:(BOOL)wasUserAction;
/*
\brief Tells the delegate that the region displayed by the map view just changed.
... ... @@ -57,7 +57,7 @@ typedef enum {
During scrolling and zooming, this method may be called many times to report updates to the map position.
Therefore, your implementation of this method should be as lightweight as possible to avoid affecting scrolling and zooming performance.
*/
- (void)mapViewRegionDidChange:(RMMapView *)mapView;
- (void)mapViewRegionDidChange:(RMMapView *)mapView byUser:(BOOL)wasUserAction;
- (void)doubleTapOnMap:(RMMapView *)map at:(CGPoint)point;
- (void)singleTapOnMap:(RMMapView *)map at:(CGPoint)point;
... ...
... ... @@ -135,8 +135,8 @@
NSURL *imageURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://a.tiles.mapbox.com/v3/marker/pin-%@%@%@%@.png",
(sizeString ? [sizeString substringToIndex:1] : @"m"),
(symbolName ? [@"-" stringByAppendingString:symbolName] : nil),
(colorHex ? [@"+" stringByAppendingString:[colorHex stringByReplacingOccurrencesOfString:@"#" withString:@""]] : nil),
(symbolName ? [@"-" stringByAppendingString:symbolName] : @""),
(colorHex ? [@"+" stringByAppendingString:[colorHex stringByReplacingOccurrencesOfString:@"#" withString:@""]] : @""),
(useRetina ? @"@2x" : @"")]];
UIImage *image;
... ...
... ... @@ -25,7 +25,7 @@
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#include <sys/sysctl.h>
#import <sys/utsname.h>
#import "RMTileCache.h"
#import "RMMemoryCache.h"
... ... @@ -213,51 +213,46 @@
@implementation RMTileCache (Configuration)
+ (NSString *)sysctlbyname:(NSString *)name
{
size_t len;
sysctlbyname([name UTF8String], NULL, &len, NULL, 0);
char *sysctlResult = malloc(len);
sysctlbyname([name UTF8String], sysctlResult, &len, NULL, 0);
NSString *result = [NSString stringWithCString:sysctlResult encoding:NSASCIIStringEncoding];
free(sysctlResult);
return result;
}
static NSMutableDictionary *predicateValues = nil;
- (NSDictionary *)predicateValues
{
NSString *machine = [RMTileCache sysctlbyname:@"hw.machine"];
static dispatch_once_t predicateValuesOnceToken;
NSMutableDictionary *predicateValues = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[[UIDevice currentDevice] model], @"model",
machine, @"machine",
[[UIDevice currentDevice] systemName], @"systemName",
[NSNumber numberWithFloat:[[[UIDevice currentDevice] systemVersion] floatValue]], @"systemVersion",
[NSNumber numberWithInt:[[UIDevice currentDevice] userInterfaceIdiom]], @"userInterfaceIdiom",
nil];
dispatch_once(&predicateValuesOnceToken, ^{
struct utsname systemInfo;
uname(&systemInfo);
if ( ! ([machine isEqualToString:@"i386"] || [machine isEqualToString:@"x86_64"]))
{
NSNumber *machineNumber = [NSNumber numberWithFloat:[[[machine stringByTrimmingCharactersInSet:[NSCharacterSet letterCharacterSet]] stringByReplacingOccurrencesOfString:@"," withString:@"."] floatValue]];
NSString *machine = [NSString stringWithCString:systemInfo.machine encoding:NSASCIIStringEncoding];
if ( ! machineNumber)
machineNumber = [NSNumber numberWithFloat:0.0];
predicateValues = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
[[UIDevice currentDevice] model], @"model",
machine, @"machine",
[[UIDevice currentDevice] systemName], @"systemName",
[NSNumber numberWithFloat:[[[UIDevice currentDevice] systemVersion] floatValue]], @"systemVersion",
[NSNumber numberWithInt:[[UIDevice currentDevice] userInterfaceIdiom]], @"userInterfaceIdiom",
nil];
[predicateValues setObject:machineNumber forKey:@"machineNumber"];
}
else
{
[predicateValues setObject:[NSNumber numberWithFloat:0.0] forKey:@"machineNumber"];
}
if ( ! ([machine isEqualToString:@"i386"] || [machine isEqualToString:@"x86_64"]))
{
NSNumber *machineNumber = [NSNumber numberWithFloat:[[[machine stringByTrimmingCharactersInSet:[NSCharacterSet letterCharacterSet]] stringByReplacingOccurrencesOfString:@"," withString:@"."] floatValue]];
// A predicate might be:
// (self.model = 'iPad' and self.machineNumber >= 3) or (self.machine = 'x86_64')
// See NSPredicate
if ( ! machineNumber)
machineNumber = [NSNumber numberWithFloat:0.0];
// NSLog(@"Predicate values:\n%@", [predicateValues description]);
[predicateValues setObject:machineNumber forKey:@"machineNumber"];
}
else
{
[predicateValues setObject:[NSNumber numberWithFloat:0.0] forKey:@"machineNumber"];
}
// A predicate might be:
// (self.model = 'iPad' and self.machineNumber >= 3) or (self.machine = 'x86_64')
// See NSPredicate
// NSLog(@"Predicate values:\n%@", [predicateValues description]);
});
return predicateValues;
}
... ...
... ... @@ -124,6 +124,9 @@
- (BOOL)addTileSource:(id<RMTileSource>)tileSource atIndex:(NSUInteger)index
{
if ( ! tileSource)
return NO;
[_tileSourcesLock lock];
RMProjection *newProjection = [tileSource projection];
... ...
... ... @@ -11,7 +11,7 @@
@interface RMUserLocation : RMAnnotation
@property (nonatomic, readonly, getter=isUpdating) BOOL updating;
@property (nonatomic, readonly, retain) CLLocation *location;
@property (nonatomic, readonly, retain) CLHeading *heading;
@property (nonatomic, readonly) CLLocation *location;
@property (nonatomic, readonly) CLHeading *heading;
@end
... ...
... ... @@ -26,7 +26,7 @@
layer = [[RMMarker alloc] initWithUIImage:[UIImage imageNamed:@"TrackingDot.png"]];
annotationType = [kRMUserLocationAnnotationTypeName retain];
clusteringEnabled = NO;
return self;
... ...
... ... @@ -22,7 +22,7 @@ typedef enum {
@property (nonatomic, retain) UISegmentedControl *segmentedControl;
@property (nonatomic, retain) UIImageView *buttonImageView;
@property (nonatomic, retain) UIActivityIndicatorView *activityView;
@property (nonatomic) RMUserTrackingButtonState state;
@property (nonatomic, assign) RMUserTrackingButtonState state;
- (void)updateAppearance;
- (void)changeMode:(id)sender;
... ... @@ -33,7 +33,7 @@ typedef enum {
@implementation RMUserTrackingBarButtonItem
@synthesize mapView=_mapView;
@synthesize mapView = _mapView;
@synthesize segmentedControl;
@synthesize buttonImageView;
@synthesize activityView;
... ... @@ -41,44 +41,44 @@ typedef enum {
- (id)initWithMapView:(RMMapView *)mapView
{
if (!(self = [super initWithCustomView:[[UIControl alloc] initWithFrame:CGRectMake(0, 0, 32, 32)]]))
if ( ! (self = [super initWithCustomView:[[UIControl alloc] initWithFrame:CGRectMake(0, 0, 32, 32)]]))
return nil;
segmentedControl = [[[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObject:@""]] retain];
segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
[segmentedControl setWidth:32.0 forSegmentAtIndex:0];
segmentedControl.userInteractionEnabled = NO;
segmentedControl.tintColor = self.tintColor;
segmentedControl.center = self.customView.center;
[self.customView addSubview:segmentedControl];
buttonImageView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"TrackingLocation.png"]] retain];
buttonImageView.contentMode = UIViewContentModeCenter;
buttonImageView.frame = CGRectMake(0, 0, 32, 32);
buttonImageView.center = self.customView.center;
buttonImageView.userInteractionEnabled = NO;
[self.customView addSubview:buttonImageView];
activityView = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite] retain];
activityView.hidesWhenStopped = YES;
activityView.center = self.customView.center;
activityView.userInteractionEnabled = NO;
[self.customView addSubview:activityView];
[((UIControl *)self.customView) addTarget:self action:@selector(changeMode:) forControlEvents:UIControlEventTouchUpInside];
_mapView = [mapView retain];
[_mapView addObserver:self forKeyPath:@"userTrackingMode" options:NSKeyValueObservingOptionNew context:nil];
[_mapView addObserver:self forKeyPath:@"userLocation.location" options:NSKeyValueObservingOptionNew context:nil];
state = RMUserTrackingButtonStateLocation;
[self updateAppearance];
return self;
}
... ... @@ -107,7 +107,7 @@ typedef enum {
_mapView = [newMapView retain];
[_mapView addObserver:self forKeyPath:@"userTrackingMode" options:NSKeyValueObservingOptionNew context:nil];
[_mapView addObserver:self forKeyPath:@"userLocation.location" options:NSKeyValueObservingOptionNew context:nil];
[self updateAppearance];
}
}
... ... @@ -115,7 +115,7 @@ typedef enum {
- (void)setTintColor:(UIColor *)newTintColor
{
[super setTintColor:newTintColor];
segmentedControl.tintColor = newTintColor;
}
... ... @@ -133,7 +133,7 @@ typedef enum {
// "selection" state
//
segmentedControl.selectedSegmentIndex = (_mapView.userTrackingMode == RMUserTrackingModeNone ? UISegmentedControlNoSegment : 0);
// activity/image state
//
if (_mapView.userTrackingMode != RMUserTrackingModeNone && ( ! _mapView.userLocation || ! _mapView.userLocation.location || (_mapView.userLocation.location.coordinate.latitude == 0 && _mapView.userLocation.location.coordinate.longitude == 0)))
... ... @@ -151,16 +151,16 @@ typedef enum {
completion:^(BOOL finished)
{
buttonImageView.hidden = YES;
[activityView startAnimating];
[UIView animateWithDuration:0.25 animations:^(void)
{
buttonImageView.transform = CGAffineTransformIdentity;
activityView.transform = CGAffineTransformIdentity;
}];
}];
state = RMUserTrackingButtonStateActivity;
}
else
... ... @@ -182,16 +182,16 @@ typedef enum {
{
buttonImageView.image = [UIImage imageNamed:(_mapView.userTrackingMode == RMUserTrackingModeFollowWithHeading ? @"TrackingHeading.png" : @"TrackingLocation.png")];
buttonImageView.hidden = NO;
[activityView stopAnimating];
[UIView animateWithDuration:0.25 animations:^(void)
{
buttonImageView.transform = CGAffineTransformIdentity;
activityView.transform = CGAffineTransformIdentity;
}];
}];
state = (_mapView.userTrackingMode == RMUserTrackingModeFollowWithHeading ? RMUserTrackingButtonStateHeading : RMUserTrackingButtonStateLocation);
}
}
... ... @@ -216,7 +216,7 @@ typedef enum {
_mapView.userTrackingMode = RMUserTrackingModeFollowWithHeading;
else
_mapView.userTrackingMode = RMUserTrackingModeNone;
break;
}
case RMUserTrackingModeFollowWithHeading:
... ... @@ -227,7 +227,7 @@ typedef enum {
}
}
}
[self updateAppearance];
}
... ...
... ... @@ -107,7 +107,8 @@
DD8CDB4B14E0507100B73EB9 /* RMMapQuestOSMSource.m in Sources */ = {isa = PBXBuildFile; fileRef = DD8CDB4914E0507100B73EB9 /* RMMapQuestOSMSource.m */; };
DD8FD7541559E4A40044D96F /* RMUserLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = DD8FD7521559E4A40044D96F /* RMUserLocation.h */; };
DD8FD7551559E4A40044D96F /* RMUserLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = DD8FD7531559E4A40044D96F /* RMUserLocation.m */; };
DD96559215264C810008517A /* RMMapBoxSource.h in Headers */ = {isa = PBXBuildFile; fileRef = DD96559015264C810008517A /* RMMapBoxSource.h */; };
DD98B6FA14D76B930092882F /* RMMapBoxSource.h in Headers */ = {isa = PBXBuildFile; fileRef = DD98B6F814D76B930092882F /* RMMapBoxSource.h */; };
DD98B6FB14D76B930092882F /* RMMapBoxSource.m in Sources */ = {isa = PBXBuildFile; fileRef = DD98B6F914D76B930092882F /* RMMapBoxSource.m */; };
DDA6B8BD155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.h in Headers */ = {isa = PBXBuildFile; fileRef = DDA6B8BB155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.h */; };
DDA6B8BE155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DDA6B8BC155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.m */; };
DDC4BED5152E3BD700089409 /* RMInteractiveSource.h in Headers */ = {isa = PBXBuildFile; fileRef = DDC4BED3152E3BD700089409 /* RMInteractiveSource.h */; };
... ... @@ -230,8 +231,6 @@
DD5A200815CAD03700FE4157 /* libGRMustache4-iOS.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libGRMustache4-iOS.a"; path = "GRMustache/lib/libGRMustache4-iOS.a"; sourceTree = "<group>"; };
DD5A200A15CAD09400FE4157 /* GRMustache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GRMustache.h; path = GRMustache/include/GRMustache.h; sourceTree = "<group>"; };
DD624B4315B4EB53004524C4 /* loading.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = loading.png; path = Resources/loading.png; sourceTree = "<group>"; };
DD6380DB152E72880074E66E /* RMMapBoxSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMMapBoxSource.h; sourceTree = "<group>"; };
DD6380DC152E72880074E66E /* RMMapBoxSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMMapBoxSource.m; sourceTree = "<group>"; };
DD8CDB4814E0507100B73EB9 /* RMMapQuestOSMSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMMapQuestOSMSource.h; sourceTree = "<group>"; };
DD8CDB4914E0507100B73EB9 /* RMMapQuestOSMSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMMapQuestOSMSource.m; sourceTree = "<group>"; };
DD8FD7521559E4A40044D96F /* RMUserLocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMUserLocation.h; sourceTree = "<group>"; };
... ... @@ -242,7 +241,8 @@
DD8FD7661559EE120044D96F /* TrackingDot@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "TrackingDot@2x.png"; path = "Resources/TrackingDot@2x.png"; sourceTree = "<group>"; };
DD8FD7691559EE120044D96F /* TrackingDotHalo@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "TrackingDotHalo@2x.png"; path = "Resources/TrackingDotHalo@2x.png"; sourceTree = "<group>"; };
DD8FD76C1559EE120044D96F /* TrackingDotHalo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = TrackingDotHalo.png; path = Resources/TrackingDotHalo.png; sourceTree = "<group>"; };
DD96559015264C810008517A /* RMMapBoxSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMMapBoxSource.h; sourceTree = "<group>"; };
DD98B6F814D76B930092882F /* RMMapBoxSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMMapBoxSource.h; sourceTree = "<group>"; };
DD98B6F914D76B930092882F /* RMMapBoxSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMMapBoxSource.m; sourceTree = "<group>"; };
DDA6B8BB155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMUserTrackingBarButtonItem.h; sourceTree = "<group>"; };
DDA6B8BC155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMUserTrackingBarButtonItem.m; sourceTree = "<group>"; };
DDA6B8C0155CAB9A003DB5D8 /* TrackingLocation.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = TrackingLocation.png; path = Resources/TrackingLocation.png; sourceTree = "<group>"; };
... ... @@ -290,6 +290,8 @@
D1437B32122869E400888DAE /* RMDBMapSource.m */,
1607499314E120A100D535F5 /* RMGenericMapSource.h */,
1607499414E120A100D535F5 /* RMGenericMapSource.m */,
DD98B6F814D76B930092882F /* RMMapBoxSource.h */,
DD98B6F914D76B930092882F /* RMMapBoxSource.m */,
16FFF2C914E3DBF700A170EC /* RMMapQuestOpenAerialSource.h */,
16FFF2CA14E3DBF700A170EC /* RMMapQuestOpenAerialSource.m */,
DD8CDB4814E0507100B73EB9 /* RMMapQuestOSMSource.h */,
... ... @@ -304,10 +306,6 @@
161E56391594664E00B00BB6 /* RMOpenSeaMapLayer.m */,
B83E64ED0E80E73F001663B6 /* RMOpenStreetMapSource.h */,
B83E64EE0E80E73F001663B6 /* RMOpenStreetMapSource.m */,
DD2B375314CF8197008DE8CB /* RMMBTilesSource.h */,
DD2B375414CF8197008DE8CB /* RMMBTilesSource.m */,
DD6380DB152E72880074E66E /* RMMapBoxSource.h */,
DD6380DC152E72880074E66E /* RMMapBoxSource.m */,
);
name = "Map sources";
sourceTree = "<group>";
... ... @@ -503,6 +501,7 @@
DD8FD7531559E4A40044D96F /* RMUserLocation.m */,
DDA6B8BB155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.h */,
DDA6B8BC155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.m */,
DD8FD7581559EDA80044D96F /* Resources */,
);
name = "User Location";
sourceTree = "<group>";
... ... @@ -588,15 +587,15 @@
DD2B374F14CF814F008DE8CB /* FMDatabasePool.h in Headers */,
DD2B375114CF814F008DE8CB /* FMDatabaseQueue.h in Headers */,
DD2B375514CF8197008DE8CB /* RMMBTilesSource.h in Headers */,
DD98B6FA14D76B930092882F /* RMMapBoxSource.h in Headers */,
DD8CDB4A14E0507100B73EB9 /* RMMapQuestOSMSource.h in Headers */,
1607499514E120A100D535F5 /* RMGenericMapSource.h in Headers */,
16FFF2CB14E3DBF700A170EC /* RMMapQuestOpenAerialSource.h in Headers */,
DD96559215264C810008517A /* RMMapBoxSource.h in Headers */,
DDC4BED5152E3BD700089409 /* RMInteractiveSource.h in Headers */,
DD6380DD152E72880074E66E /* RMMapBoxSource.h in Headers */,
DD3BEF7915913C55007892D8 /* RMAttributionViewController.h in Headers */,
DD8FD7541559E4A40044D96F /* RMUserLocation.h in Headers */,
DDA6B8BD155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.h in Headers */,
DD3BEF7915913C55007892D8 /* RMAttributionViewController.h in Headers */,
16F3581B15864135003A3AD9 /* RMMapScrollView.h in Headers */,
16F98C961590CFF000FF90CE /* RMShape.h in Headers */,
16FBF07615936BF1004ECAD1 /* RMTileSourcesContainer.h in Headers */,
... ... @@ -707,14 +706,14 @@
DD2B375014CF814F008DE8CB /* FMDatabasePool.m in Sources */,
DD2B375214CF814F008DE8CB /* FMDatabaseQueue.m in Sources */,
DD2B375614CF8197008DE8CB /* RMMBTilesSource.m in Sources */,
DD98B6FB14D76B930092882F /* RMMapBoxSource.m in Sources */,
DD8CDB4B14E0507100B73EB9 /* RMMapQuestOSMSource.m in Sources */,
1607499614E120A100D535F5 /* RMGenericMapSource.m in Sources */,
16FFF2CC14E3DBF700A170EC /* RMMapQuestOpenAerialSource.m in Sources */,
DD6380DE152E72880074E66E /* RMMapBoxSource.m in Sources */,
DDC4BEF2152E3FAE00089409 /* RMInteractiveSource.m in Sources */,
DD3BEF7A15913C55007892D8 /* RMAttributionViewController.m in Sources */,
DD8FD7551559E4A40044D96F /* RMUserLocation.m in Sources */,
DDA6B8BE155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.m in Sources */,
DD3BEF7A15913C55007892D8 /* RMAttributionViewController.m in Sources */,
16F3581C15864135003A3AD9 /* RMMapScrollView.m in Sources */,
16F98C971590CFF000FF90CE /* RMShape.m in Sources */,
16FBF07715936BF1004ECAD1 /* RMTileSourcesContainer.m in Sources */,
... ...
... ... @@ -10,14 +10,16 @@ Undergoing rapid development, so the `develop` branch is currently recommended.
Major differences from [Alpstein fork of Route-Me](https://github.com/Alpstein/route-me):
* Requires iOS 5.0 and above.
* Canonical source for [MapBox](http://mapbox.com) & [MBTiles](http://mbtiles.org) tile source integration code.
* [MapBox](http://mapbox.com) & [MBTiles](http://mbtiles.org) tile source integration code.
* [UTFGrid interactivity](http://mapbox.com/mbtiles-spec/utfgrid/).
* [User location services](http://mapbox.com/blog/ios-user-location-services/).
* Removal of two-finger double-tap gesture for zoom out (to speed up two-finger single-tap recognition like MapKit).
* Different default starting location for maps.
* Built-in attribution view controller with button on map views & default OpenStreetMap attribution.
* [MapBox Markers](http://mapbox.com/blog/markers/) support.
* Prepackaged [binary framework](http://mapbox.com/blog/ios-sdk-framework/).
* Removed of included example projects in favor of separate examples on GitHub.
* A few added defaults for convenience.
* Improved documentation.
[mapbox]: http://mapbox.com
... ...