added user location services
Showing
22 changed files
with
913 additions
and
28 deletions
@@ -73,6 +73,9 @@ | @@ -73,6 +73,9 @@ | ||
73 | // This is for the QuadTree. Don't mess this up. | 73 | // This is for the QuadTree. Don't mess this up. |
74 | @property (nonatomic, assign) RMQuadTreeNode *quadTreeNode; | 74 | @property (nonatomic, assign) RMQuadTreeNode *quadTreeNode; |
75 | 75 | ||
76 | +// This is for filtering framework-provided annotations. | ||
77 | +@property (nonatomic, assign, readonly) BOOL isUserLocationAnnotation; | ||
78 | + | ||
76 | #pragma mark - | 79 | #pragma mark - |
77 | 80 | ||
78 | + (id)annotationWithMapView:(RMMapView *)aMapView coordinate:(CLLocationCoordinate2D)aCoordinate andTitle:(NSString *)aTitle; | 81 | + (id)annotationWithMapView:(RMMapView *)aMapView coordinate:(CLLocationCoordinate2D)aCoordinate andTitle:(NSString *)aTitle; |
@@ -50,6 +50,7 @@ | @@ -50,6 +50,7 @@ | ||
50 | @synthesize enabled, clusteringEnabled; | 50 | @synthesize enabled, clusteringEnabled; |
51 | @synthesize position; | 51 | @synthesize position; |
52 | @synthesize quadTreeNode; | 52 | @synthesize quadTreeNode; |
53 | +@synthesize isUserLocationAnnotation; | ||
53 | 54 | ||
54 | + (id)annotationWithMapView:(RMMapView *)aMapView coordinate:(CLLocationCoordinate2D)aCoordinate andTitle:(NSString *)aTitle | 55 | + (id)annotationWithMapView:(RMMapView *)aMapView coordinate:(CLLocationCoordinate2D)aCoordinate andTitle:(NSString *)aTitle |
55 | { | 56 | { |
@@ -178,6 +179,11 @@ | @@ -178,6 +179,11 @@ | ||
178 | return (layer != nil); | 179 | return (layer != nil); |
179 | } | 180 | } |
180 | 181 | ||
182 | +- (void)setIsUserLocationAnnotation:(BOOL)flag | ||
183 | +{ | ||
184 | + isUserLocationAnnotation = flag; | ||
185 | +} | ||
186 | + | ||
181 | #pragma mark - | 187 | #pragma mark - |
182 | 188 | ||
183 | - (void)setBoundingBoxCoordinatesSouthWest:(CLLocationCoordinate2D)southWest northEast:(CLLocationCoordinate2D)northEast | 189 | - (void)setBoundingBoxCoordinatesSouthWest:(CLLocationCoordinate2D)southWest northEast:(CLLocationCoordinate2D)northEast |
@@ -38,6 +38,10 @@ | @@ -38,6 +38,10 @@ | ||
38 | #import "RMMapScrollView.h" | 38 | #import "RMMapScrollView.h" |
39 | #import "RMTileSourcesContainer.h" | 39 | #import "RMTileSourcesContainer.h" |
40 | 40 | ||
41 | +#define kRMUserLocationAnnotationTypeName @"RMUserLocationAnnotation" | ||
42 | +#define kRMTrackingHaloAnnotationTypeName @"RMTrackingHaloAnnotation" | ||
43 | +#define kRMAccuracyCircleAnnotationTypeName @"RMAccuracyCircleAnnotation" | ||
44 | + | ||
41 | @class RMProjection; | 45 | @class RMProjection; |
42 | @class RMFractalTileProjection; | 46 | @class RMFractalTileProjection; |
43 | @class RMTileCache; | 47 | @class RMTileCache; |
@@ -47,6 +51,7 @@ | @@ -47,6 +51,7 @@ | ||
47 | @class RMMarker; | 51 | @class RMMarker; |
48 | @class RMAnnotation; | 52 | @class RMAnnotation; |
49 | @class RMQuadTree; | 53 | @class RMQuadTree; |
54 | +@class RMUserLocation; | ||
50 | 55 | ||
51 | 56 | ||
52 | // constants for boundingMask | 57 | // constants for boundingMask |
@@ -64,7 +69,7 @@ typedef enum : NSUInteger { | @@ -64,7 +69,7 @@ typedef enum : NSUInteger { | ||
64 | } RMMapDecelerationMode; | 69 | } RMMapDecelerationMode; |
65 | 70 | ||
66 | 71 | ||
67 | -@interface RMMapView : UIView <UIScrollViewDelegate, UIGestureRecognizerDelegate, RMMapScrollViewDelegate> | 72 | +@interface RMMapView : UIView <UIScrollViewDelegate, UIGestureRecognizerDelegate, RMMapScrollViewDelegate, CLLocationManagerDelegate> |
68 | 73 | ||
69 | @property (nonatomic, assign) id <RMMapViewDelegate> delegate; | 74 | @property (nonatomic, assign) id <RMMapViewDelegate> delegate; |
70 | 75 | ||
@@ -83,6 +88,11 @@ typedef enum : NSUInteger { | @@ -83,6 +88,11 @@ typedef enum : NSUInteger { | ||
83 | @property (nonatomic, assign) BOOL adjustTilesForRetinaDisplay; | 88 | @property (nonatomic, assign) BOOL adjustTilesForRetinaDisplay; |
84 | @property (nonatomic, readonly) float adjustedZoomForRetinaDisplay; // takes adjustTilesForRetinaDisplay and screen scale into account | 89 | @property (nonatomic, readonly) float adjustedZoomForRetinaDisplay; // takes adjustTilesForRetinaDisplay and screen scale into account |
85 | 90 | ||
91 | +@property (nonatomic) BOOL showsUserLocation; | ||
92 | +@property (nonatomic, readonly, retain) RMUserLocation *userLocation; | ||
93 | +@property (nonatomic, readonly, getter=isUserLocationVisible) BOOL userLocationVisible; | ||
94 | +@property (nonatomic) RMUserTrackingMode userTrackingMode; | ||
95 | + | ||
86 | // take missing tiles from lower zoom levels, up to #missingTilesDepth zoom levels (defaults to 0, which disables this feature) | 96 | // take missing tiles from lower zoom levels, up to #missingTilesDepth zoom levels (defaults to 0, which disables this feature) |
87 | @property (nonatomic, assign) NSUInteger missingTilesDepth; | 97 | @property (nonatomic, assign) NSUInteger missingTilesDepth; |
88 | 98 | ||
@@ -235,4 +245,9 @@ typedef enum : NSUInteger { | @@ -235,4 +245,9 @@ typedef enum : NSUInteger { | ||
235 | 245 | ||
236 | - (RMSphericalTrapezium)latitudeLongitudeBoundingBoxForTile:(RMTile)aTile; | 246 | - (RMSphericalTrapezium)latitudeLongitudeBoundingBoxForTile:(RMTile)aTile; |
237 | 247 | ||
248 | +#pragma mark - | ||
249 | +#pragma mark User Location | ||
250 | + | ||
251 | +- (void)setUserTrackingMode:(RMUserTrackingMode)mode animated:(BOOL)animated; | ||
252 | + | ||
238 | @end | 253 | @end |
@@ -33,6 +33,7 @@ | @@ -33,6 +33,7 @@ | ||
33 | #import "RMProjection.h" | 33 | #import "RMProjection.h" |
34 | #import "RMMarker.h" | 34 | #import "RMMarker.h" |
35 | #import "RMPath.h" | 35 | #import "RMPath.h" |
36 | +#import "RMCircle.h" | ||
36 | #import "RMShape.h" | 37 | #import "RMShape.h" |
37 | #import "RMAnnotation.h" | 38 | #import "RMAnnotation.h" |
38 | #import "RMQuadTree.h" | 39 | #import "RMQuadTree.h" |
@@ -46,6 +47,8 @@ | @@ -46,6 +47,8 @@ | ||
46 | #import "RMMapTiledLayerView.h" | 47 | #import "RMMapTiledLayerView.h" |
47 | #import "RMMapOverlayView.h" | 48 | #import "RMMapOverlayView.h" |
48 | 49 | ||
50 | +#import "RMUserLocation.h" | ||
51 | + | ||
49 | #pragma mark --- begin constants ---- | 52 | #pragma mark --- begin constants ---- |
50 | 53 | ||
51 | #define kZoomRectPixelBuffer 150.0 | 54 | #define kZoomRectPixelBuffer 150.0 |
@@ -61,6 +64,8 @@ | @@ -61,6 +64,8 @@ | ||
61 | 64 | ||
62 | @interface RMMapView (PrivateMethods) | 65 | @interface RMMapView (PrivateMethods) |
63 | 66 | ||
67 | +@property (nonatomic, retain) RMUserLocation *userLocation; | ||
68 | + | ||
64 | - (void)createMapView; | 69 | - (void)createMapView; |
65 | 70 | ||
66 | - (void)correctPositionOfAllAnnotations; | 71 | - (void)correctPositionOfAllAnnotations; |
@@ -72,6 +77,24 @@ | @@ -72,6 +77,24 @@ | ||
72 | 77 | ||
73 | #pragma mark - | 78 | #pragma mark - |
74 | 79 | ||
80 | +@interface RMUserLocation (PrivateMethods) | ||
81 | + | ||
82 | +@property (nonatomic, getter=isUpdating) BOOL updating; | ||
83 | +@property (nonatomic, retain) CLLocation *location; | ||
84 | +@property (nonatomic, retain) CLHeading *heading; | ||
85 | + | ||
86 | +@end | ||
87 | + | ||
88 | +#pragma mark - | ||
89 | + | ||
90 | +@interface RMAnnotation (PrivateMethods) | ||
91 | + | ||
92 | +@property (nonatomic, assign) BOOL isUserLocationAnnotation; | ||
93 | + | ||
94 | +@end | ||
95 | + | ||
96 | +#pragma mark - | ||
97 | + | ||
75 | @implementation RMMapView | 98 | @implementation RMMapView |
76 | { | 99 | { |
77 | id <RMMapViewDelegate> _delegate; | 100 | id <RMMapViewDelegate> _delegate; |
@@ -96,6 +119,11 @@ | @@ -96,6 +119,11 @@ | ||
96 | BOOL _delegateHasLayerForAnnotation; | 119 | BOOL _delegateHasLayerForAnnotation; |
97 | BOOL _delegateHasWillHideLayerForAnnotation; | 120 | BOOL _delegateHasWillHideLayerForAnnotation; |
98 | BOOL _delegateHasDidHideLayerForAnnotation; | 121 | BOOL _delegateHasDidHideLayerForAnnotation; |
122 | + BOOL _delegateHasWillStartLocatingUser; | ||
123 | + BOOL _delegateHasDidStopLocatingUser; | ||
124 | + BOOL _delegateHasDidUpdateUserLocation; | ||
125 | + BOOL _delegateHasDidFailToLocateUserWithError; | ||
126 | + BOOL _delegateHasDidChangeUserTrackingMode; | ||
99 | 127 | ||
100 | UIView *_backgroundView; | 128 | UIView *_backgroundView; |
101 | RMMapScrollView *_mapScrollView; | 129 | RMMapScrollView *_mapScrollView; |
@@ -122,6 +150,14 @@ | @@ -122,6 +150,14 @@ | ||
122 | 150 | ||
123 | CGPoint _lastDraggingTranslation; | 151 | CGPoint _lastDraggingTranslation; |
124 | RMAnnotation *_draggedAnnotation; | 152 | RMAnnotation *_draggedAnnotation; |
153 | + | ||
154 | + CLLocationManager *locationManager; | ||
155 | + RMUserLocation *userLocation; | ||
156 | + BOOL showsUserLocation; | ||
157 | + RMUserTrackingMode userTrackingMode; | ||
158 | + | ||
159 | + UIImageView *userLocationTrackingView; | ||
160 | + UIImageView *userHeadingTrackingView; | ||
125 | } | 161 | } |
126 | 162 | ||
127 | @synthesize decelerationMode = _decelerationMode; | 163 | @synthesize decelerationMode = _decelerationMode; |
@@ -136,6 +172,7 @@ | @@ -136,6 +172,7 @@ | ||
136 | @synthesize positionClusterMarkersAtTheGravityCenter = _positionClusterMarkersAtTheGravityCenter; | 172 | @synthesize positionClusterMarkersAtTheGravityCenter = _positionClusterMarkersAtTheGravityCenter; |
137 | @synthesize clusterMarkerSize = _clusterMarkerSize, clusterAreaSize = _clusterAreaSize; | 173 | @synthesize clusterMarkerSize = _clusterMarkerSize, clusterAreaSize = _clusterAreaSize; |
138 | @synthesize adjustTilesForRetinaDisplay = _adjustTilesForRetinaDisplay; | 174 | @synthesize adjustTilesForRetinaDisplay = _adjustTilesForRetinaDisplay; |
175 | +@synthesize userLocation, showsUserLocation, userTrackingMode; | ||
139 | @synthesize missingTilesDepth = _missingTilesDepth; | 176 | @synthesize missingTilesDepth = _missingTilesDepth; |
140 | @synthesize debugTiles = _debugTiles; | 177 | @synthesize debugTiles = _debugTiles; |
141 | 178 | ||
@@ -157,6 +194,8 @@ | @@ -157,6 +194,8 @@ | ||
157 | 194 | ||
158 | self.backgroundColor = [UIColor grayColor]; | 195 | self.backgroundColor = [UIColor grayColor]; |
159 | 196 | ||
197 | + self.clipsToBounds = YES; | ||
198 | + | ||
160 | _tileSourcesContainer = [RMTileSourcesContainer new]; | 199 | _tileSourcesContainer = [RMTileSourcesContainer new]; |
161 | _tiledLayersSuperview = nil; | 200 | _tiledLayersSuperview = nil; |
162 | 201 | ||
@@ -311,6 +350,10 @@ | @@ -311,6 +350,10 @@ | ||
311 | [_projection release]; _projection = nil; | 350 | [_projection release]; _projection = nil; |
312 | [_mercatorToTileProjection release]; _mercatorToTileProjection = nil; | 351 | [_mercatorToTileProjection release]; _mercatorToTileProjection = nil; |
313 | [self setTileCache:nil]; | 352 | [self setTileCache:nil]; |
353 | + [locationManager release]; locationManager = nil; | ||
354 | + [userLocation release]; userLocation = nil; | ||
355 | + [userLocationTrackingView release]; userLocationTrackingView = nil; | ||
356 | + [userHeadingTrackingView release]; userHeadingTrackingView = nil; | ||
314 | [super dealloc]; | 357 | [super dealloc]; |
315 | } | 358 | } |
316 | 359 | ||
@@ -375,6 +418,12 @@ | @@ -375,6 +418,12 @@ | ||
375 | _delegateHasLayerForAnnotation = [_delegate respondsToSelector:@selector(mapView:layerForAnnotation:)]; | 418 | _delegateHasLayerForAnnotation = [_delegate respondsToSelector:@selector(mapView:layerForAnnotation:)]; |
376 | _delegateHasWillHideLayerForAnnotation = [_delegate respondsToSelector:@selector(mapView:willHideLayerForAnnotation:)]; | 419 | _delegateHasWillHideLayerForAnnotation = [_delegate respondsToSelector:@selector(mapView:willHideLayerForAnnotation:)]; |
377 | _delegateHasDidHideLayerForAnnotation = [_delegate respondsToSelector:@selector(mapView:didHideLayerForAnnotation:)]; | 420 | _delegateHasDidHideLayerForAnnotation = [_delegate respondsToSelector:@selector(mapView:didHideLayerForAnnotation:)]; |
421 | + | ||
422 | + _delegateHasWillStartLocatingUser = [_delegate respondsToSelector:@selector(mapViewWillStartLocatingUser:)]; | ||
423 | + _delegateHasDidStopLocatingUser = [_delegate respondsToSelector:@selector(mapViewDidStopLocatingUser:)]; | ||
424 | + _delegateHasDidUpdateUserLocation = [_delegate respondsToSelector:@selector(mapView:didUpdateUserLocation:)]; | ||
425 | + _delegateHasDidFailToLocateUserWithError = [_delegate respondsToSelector:@selector(mapView:didFailToLocateUserWithError:)]; | ||
426 | + _delegateHasDidChangeUserTrackingMode = [_delegate respondsToSelector:@selector(mapView:didChangeUserTrackingMode:animated:)]; | ||
378 | } | 427 | } |
379 | 428 | ||
380 | #pragma mark - | 429 | #pragma mark - |
@@ -768,6 +817,9 @@ | @@ -768,6 +817,9 @@ | ||
768 | 817 | ||
769 | - (void)zoomInToNextNativeZoomAt:(CGPoint)pivot animated:(BOOL)animated | 818 | - (void)zoomInToNextNativeZoomAt:(CGPoint)pivot animated:(BOOL)animated |
770 | { | 819 | { |
820 | + if (self.userTrackingMode != RMUserTrackingModeNone && ! CGPointEqualToPoint(pivot, self.center)) | ||
821 | + self.userTrackingMode = RMUserTrackingModeNone; | ||
822 | + | ||
771 | // Calculate rounded zoom | 823 | // Calculate rounded zoom |
772 | float newZoom = fmin(ceilf([self zoom]) + 0.99, [self maxZoom]); | 824 | float newZoom = fmin(ceilf([self zoom]) + 0.99, [self maxZoom]); |
773 | 825 | ||
@@ -930,6 +982,7 @@ | @@ -930,6 +982,7 @@ | ||
930 | _mapScrollView.minimumZoomScale = exp2f([self minZoom]); | 982 | _mapScrollView.minimumZoomScale = exp2f([self minZoom]); |
931 | _mapScrollView.maximumZoomScale = exp2f([self maxZoom]); | 983 | _mapScrollView.maximumZoomScale = exp2f([self maxZoom]); |
932 | _mapScrollView.contentOffset = CGPointMake(0.0, 0.0); | 984 | _mapScrollView.contentOffset = CGPointMake(0.0, 0.0); |
985 | + _mapScrollView.clipsToBounds = NO; | ||
933 | 986 | ||
934 | _tiledLayersSuperview = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, contentSize.width, contentSize.height)]; | 987 | _tiledLayersSuperview = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, contentSize.width, contentSize.height)]; |
935 | _tiledLayersSuperview.userInteractionEnabled = NO; | 988 | _tiledLayersSuperview.userInteractionEnabled = NO; |
@@ -1022,6 +1075,9 @@ | @@ -1022,6 +1075,9 @@ | ||
1022 | 1075 | ||
1023 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView | 1076 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView |
1024 | { | 1077 | { |
1078 | + if (self.userTrackingMode != RMUserTrackingModeNone) | ||
1079 | + self.userTrackingMode = RMUserTrackingModeNone; | ||
1080 | + | ||
1025 | if (_delegateHasBeforeMapMove) | 1081 | if (_delegateHasBeforeMapMove) |
1026 | [_delegate beforeMapMove:self]; | 1082 | [_delegate beforeMapMove:self]; |
1027 | } | 1083 | } |
@@ -1067,8 +1123,14 @@ | @@ -1067,8 +1123,14 @@ | ||
1067 | 1123 | ||
1068 | - (void)scrollViewDidZoom:(UIScrollView *)scrollView | 1124 | - (void)scrollViewDidZoom:(UIScrollView *)scrollView |
1069 | { | 1125 | { |
1126 | + if (self.userTrackingMode != RMUserTrackingModeNone && scrollView.pinchGestureRecognizer.state == UIGestureRecognizerStateChanged) | ||
1127 | + self.userTrackingMode = RMUserTrackingModeNone; | ||
1128 | + | ||
1070 | [self correctPositionOfAllAnnotations]; | 1129 | [self correctPositionOfAllAnnotations]; |
1071 | 1130 | ||
1131 | + if (_zoom < 3 && self.userTrackingMode == RMUserTrackingModeFollowWithHeading) | ||
1132 | + self.userTrackingMode = RMUserTrackingModeFollow; | ||
1133 | + | ||
1072 | if (_delegateHasAfterMapZoom) | 1134 | if (_delegateHasAfterMapZoom) |
1073 | [_delegate afterMapZoom:self]; | 1135 | [_delegate afterMapZoom:self]; |
1074 | } | 1136 | } |
@@ -2200,15 +2262,18 @@ | @@ -2200,15 +2262,18 @@ | ||
2200 | 2262 | ||
2201 | for (RMAnnotation *annotation in previousVisibleAnnotations) | 2263 | for (RMAnnotation *annotation in previousVisibleAnnotations) |
2202 | { | 2264 | { |
2203 | - if (_delegateHasWillHideLayerForAnnotation) | ||
2204 | - [_delegate mapView:self willHideLayerForAnnotation:annotation]; | 2265 | + if ( ! annotation.isUserLocationAnnotation) |
2266 | + { | ||
2267 | + if (_delegateHasWillHideLayerForAnnotation) | ||
2268 | + [_delegate mapView:self willHideLayerForAnnotation:annotation]; | ||
2205 | 2269 | ||
2206 | - annotation.layer = nil; | 2270 | + annotation.layer = nil; |
2207 | 2271 | ||
2208 | - if (_delegateHasDidHideLayerForAnnotation) | ||
2209 | - [_delegate mapView:self didHideLayerForAnnotation:annotation]; | 2272 | + if (_delegateHasDidHideLayerForAnnotation) |
2273 | + [_delegate mapView:self didHideLayerForAnnotation:annotation]; | ||
2210 | 2274 | ||
2211 | - [_visibleAnnotations removeObject:annotation]; | 2275 | + [_visibleAnnotations removeObject:annotation]; |
2276 | + } | ||
2212 | } | 2277 | } |
2213 | 2278 | ||
2214 | [previousVisibleAnnotations release]; | 2279 | [previousVisibleAnnotations release]; |
@@ -2248,14 +2313,17 @@ | @@ -2248,14 +2313,17 @@ | ||
2248 | } | 2313 | } |
2249 | else | 2314 | else |
2250 | { | 2315 | { |
2251 | - if (_delegateHasWillHideLayerForAnnotation) | ||
2252 | - [_delegate mapView:self willHideLayerForAnnotation:annotation]; | 2316 | + if ( ! annotation.isUserLocationAnnotation) |
2317 | + { | ||
2318 | + if (_delegateHasWillHideLayerForAnnotation) | ||
2319 | + [_delegate mapView:self willHideLayerForAnnotation:annotation]; | ||
2253 | 2320 | ||
2254 | - annotation.layer = nil; | ||
2255 | - [_visibleAnnotations removeObject:annotation]; | 2321 | + annotation.layer = nil; |
2322 | + [_visibleAnnotations removeObject:annotation]; | ||
2256 | 2323 | ||
2257 | - if (_delegateHasDidHideLayerForAnnotation) | ||
2258 | - [_delegate mapView:self didHideLayerForAnnotation:annotation]; | 2324 | + if (_delegateHasDidHideLayerForAnnotation) |
2325 | + [_delegate mapView:self didHideLayerForAnnotation:annotation]; | ||
2326 | + } | ||
2259 | } | 2327 | } |
2260 | } | 2328 | } |
2261 | // RMLog(@"%d annotations on screen, %d total", [overlayView sublayersCount], [annotations count]); | 2329 | // RMLog(@"%d annotations on screen, %d total", [overlayView sublayersCount], [annotations count]); |
@@ -2346,10 +2414,13 @@ | @@ -2346,10 +2414,13 @@ | ||
2346 | { | 2414 | { |
2347 | for (RMAnnotation *annotation in annotationsToRemove) | 2415 | for (RMAnnotation *annotation in annotationsToRemove) |
2348 | { | 2416 | { |
2349 | - [_annotations removeObject:annotation]; | ||
2350 | - [_visibleAnnotations removeObject:annotation]; | ||
2351 | - [self.quadTree removeAnnotation:annotation]; | ||
2352 | - annotation.layer = nil; | 2417 | + if ( ! annotation.isUserLocationAnnotation) |
2418 | + { | ||
2419 | + [_annotations removeObject:annotation]; | ||
2420 | + [_visibleAnnotations removeObject:annotation]; | ||
2421 | + [self.quadTree removeAnnotation:annotation]; | ||
2422 | + annotation.layer = nil; | ||
2423 | + } | ||
2353 | } | 2424 | } |
2354 | } | 2425 | } |
2355 | 2426 | ||
@@ -2358,25 +2429,401 @@ | @@ -2358,25 +2429,401 @@ | ||
2358 | 2429 | ||
2359 | - (void)removeAllAnnotations | 2430 | - (void)removeAllAnnotations |
2360 | { | 2431 | { |
2361 | - @synchronized (_annotations) | 2432 | + [self removeAnnotations:[_annotations allObjects]]; |
2433 | +} | ||
2434 | + | ||
2435 | +- (CGPoint)mapPositionForAnnotation:(RMAnnotation *)annotation | ||
2436 | +{ | ||
2437 | + [self correctScreenPosition:annotation animated:NO]; | ||
2438 | + return annotation.position; | ||
2439 | +} | ||
2440 | + | ||
2441 | +#pragma mark - | ||
2442 | +#pragma mark User Location | ||
2443 | + | ||
2444 | +- (void)setShowsUserLocation:(BOOL)newShowsUserLocation | ||
2445 | +{ | ||
2446 | + if (newShowsUserLocation == showsUserLocation) | ||
2447 | + return; | ||
2448 | + | ||
2449 | + showsUserLocation = newShowsUserLocation; | ||
2450 | + | ||
2451 | + if (newShowsUserLocation) | ||
2452 | + { | ||
2453 | + if (_delegateHasWillStartLocatingUser) | ||
2454 | + [_delegate mapViewWillStartLocatingUser:self]; | ||
2455 | + | ||
2456 | + self.userLocation = [RMUserLocation annotationWithMapView:self coordinate:CLLocationCoordinate2DMake(0, 0) andTitle:nil]; | ||
2457 | + | ||
2458 | + locationManager = [[CLLocationManager alloc] init]; | ||
2459 | + locationManager.headingFilter = 5; | ||
2460 | + locationManager.delegate = self; | ||
2461 | + [locationManager startUpdatingLocation]; | ||
2462 | + } | ||
2463 | + else | ||
2362 | { | 2464 | { |
2465 | + [locationManager stopUpdatingLocation]; | ||
2466 | + [locationManager stopUpdatingHeading]; | ||
2467 | + locationManager.delegate = nil; | ||
2468 | + [locationManager release]; | ||
2469 | + locationManager = nil; | ||
2470 | + | ||
2471 | + if (_delegateHasDidStopLocatingUser) | ||
2472 | + [_delegate mapViewDidStopLocatingUser:self]; | ||
2473 | + | ||
2474 | + [self setUserTrackingMode:RMUserTrackingModeNone animated:YES]; | ||
2475 | + | ||
2476 | + NSMutableArray *annotationsToRemove = [NSMutableArray array]; | ||
2477 | + | ||
2363 | for (RMAnnotation *annotation in _annotations) | 2478 | for (RMAnnotation *annotation in _annotations) |
2479 | + if (annotation.isUserLocationAnnotation) | ||
2480 | + [annotationsToRemove addObject:annotation]; | ||
2481 | + | ||
2482 | + for (RMAnnotation *annotationToRemove in annotationsToRemove) | ||
2483 | + [self removeAnnotation:annotationToRemove]; | ||
2484 | + | ||
2485 | + self.userLocation = nil; | ||
2486 | + } | ||
2487 | +} | ||
2488 | + | ||
2489 | +- (void)setUserLocation:(RMUserLocation *)newUserLocation | ||
2490 | +{ | ||
2491 | + if ( ! [newUserLocation isEqual:userLocation]) | ||
2492 | + { | ||
2493 | + [userLocation release]; | ||
2494 | + userLocation = [newUserLocation retain]; | ||
2495 | + } | ||
2496 | +} | ||
2497 | + | ||
2498 | +- (BOOL)isUserLocationVisible | ||
2499 | +{ | ||
2500 | + if (userLocation) | ||
2501 | + { | ||
2502 | + CGPoint locationPoint = [self mapPositionForAnnotation:userLocation]; | ||
2503 | + | ||
2504 | + CGRect locationRect = CGRectMake(locationPoint.x - userLocation.location.horizontalAccuracy, | ||
2505 | + locationPoint.y - userLocation.location.horizontalAccuracy, | ||
2506 | + userLocation.location.horizontalAccuracy * 2, | ||
2507 | + userLocation.location.horizontalAccuracy * 2); | ||
2508 | + | ||
2509 | + return CGRectIntersectsRect([self bounds], locationRect); | ||
2510 | + } | ||
2511 | + | ||
2512 | + return NO; | ||
2513 | +} | ||
2514 | + | ||
2515 | +- (void)setUserTrackingMode:(RMUserTrackingMode)mode | ||
2516 | +{ | ||
2517 | + [self setUserTrackingMode:mode animated:YES]; | ||
2518 | +} | ||
2519 | + | ||
2520 | +- (void)setUserTrackingMode:(RMUserTrackingMode)mode animated:(BOOL)animated | ||
2521 | +{ | ||
2522 | + if (mode == userTrackingMode) | ||
2523 | + return; | ||
2524 | + | ||
2525 | + userTrackingMode = mode; | ||
2526 | + | ||
2527 | + switch (userTrackingMode) | ||
2528 | + { | ||
2529 | + case RMUserTrackingModeNone: | ||
2530 | + default: | ||
2531 | + { | ||
2532 | + [locationManager stopUpdatingHeading]; | ||
2533 | + | ||
2534 | + [UIView animateWithDuration:(animated ? 0.5 : 0.0) | ||
2535 | + delay:0.0 | ||
2536 | + options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationCurveEaseInOut | ||
2537 | + animations:^(void) | ||
2538 | + { | ||
2539 | + _mapScrollView.transform = CGAffineTransformIdentity; | ||
2540 | + _overlayView.transform = CGAffineTransformIdentity; | ||
2541 | + | ||
2542 | + for (RMAnnotation *annotation in _annotations) | ||
2543 | + if ( ! annotation.isUserLocationAnnotation) | ||
2544 | + annotation.layer.transform = CATransform3DIdentity; | ||
2545 | + } | ||
2546 | + completion:nil]; | ||
2547 | + | ||
2548 | + if (userLocationTrackingView || userHeadingTrackingView) | ||
2549 | + { | ||
2550 | + [userLocationTrackingView removeFromSuperview]; | ||
2551 | + userLocationTrackingView = nil; | ||
2552 | + [userHeadingTrackingView removeFromSuperview]; | ||
2553 | + userHeadingTrackingView = nil; | ||
2554 | + } | ||
2555 | + | ||
2556 | + userLocation.layer.hidden = NO; | ||
2557 | + | ||
2558 | + break; | ||
2559 | + } | ||
2560 | + case RMUserTrackingModeFollow: | ||
2561 | + { | ||
2562 | + self.showsUserLocation = YES; | ||
2563 | + | ||
2564 | + [locationManager stopUpdatingHeading]; | ||
2565 | + | ||
2566 | + if (self.userLocation) | ||
2567 | + [self locationManager:locationManager didUpdateToLocation:self.userLocation.location fromLocation:self.userLocation.location]; | ||
2568 | + | ||
2569 | + if (userLocationTrackingView || userHeadingTrackingView) | ||
2570 | + { | ||
2571 | + [userLocationTrackingView removeFromSuperview]; | ||
2572 | + userLocationTrackingView = nil; | ||
2573 | + [userHeadingTrackingView removeFromSuperview]; | ||
2574 | + userHeadingTrackingView = nil; | ||
2575 | + } | ||
2576 | + | ||
2577 | + [UIView animateWithDuration:(animated ? 0.5 : 0.0) | ||
2578 | + delay:0.0 | ||
2579 | + options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationCurveEaseInOut | ||
2580 | + animations:^(void) | ||
2581 | + { | ||
2582 | + _mapScrollView.transform = CGAffineTransformIdentity; | ||
2583 | + _overlayView.transform = CGAffineTransformIdentity; | ||
2584 | + | ||
2585 | + for (RMAnnotation *annotation in _annotations) | ||
2586 | + if ( ! annotation.isUserLocationAnnotation) | ||
2587 | + annotation.layer.transform = CATransform3DIdentity; | ||
2588 | + } | ||
2589 | + completion:nil]; | ||
2590 | + | ||
2591 | + userLocation.layer.hidden = NO; | ||
2592 | + | ||
2593 | + break; | ||
2594 | + } | ||
2595 | + case RMUserTrackingModeFollowWithHeading: | ||
2364 | { | 2596 | { |
2365 | - // Remove the layer from the screen | ||
2366 | - annotation.layer = nil; | 2597 | + self.showsUserLocation = YES; |
2598 | + | ||
2599 | + userLocation.layer.hidden = YES; | ||
2600 | + | ||
2601 | + userHeadingTrackingView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"HeadingAngleSmall.png"]]; | ||
2602 | + | ||
2603 | + userHeadingTrackingView.center = CGPointMake(round([self bounds].size.width / 2), | ||
2604 | + round([self bounds].size.height / 2) - (userHeadingTrackingView.bounds.size.height / 2) - 4); | ||
2605 | + | ||
2606 | + userHeadingTrackingView.alpha = 0.0; | ||
2607 | + | ||
2608 | + [self addSubview:userHeadingTrackingView]; | ||
2609 | + | ||
2610 | + [UIView animateWithDuration:0.5 animations:^(void) { userHeadingTrackingView.alpha = 1.0; }]; | ||
2611 | + | ||
2612 | + userLocationTrackingView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"TrackingDot.png"]]; | ||
2613 | + | ||
2614 | + userLocationTrackingView.center = CGPointMake(round([self bounds].size.width / 2), | ||
2615 | + round([self bounds].size.height / 2)); | ||
2616 | + | ||
2617 | + [self addSubview:userLocationTrackingView]; | ||
2618 | + | ||
2619 | + if (self.zoom < 3) | ||
2620 | + [self zoomByFactor:exp2f(3 - [self zoom]) near:self.center animated:YES]; | ||
2621 | + | ||
2622 | + if (self.userLocation) | ||
2623 | + [self locationManager:locationManager didUpdateToLocation:self.userLocation.location fromLocation:self.userLocation.location]; | ||
2624 | + | ||
2625 | + [locationManager startUpdatingHeading]; | ||
2626 | + | ||
2627 | + break; | ||
2367 | } | 2628 | } |
2368 | } | 2629 | } |
2369 | 2630 | ||
2370 | - [_annotations removeAllObjects]; | ||
2371 | - [_visibleAnnotations removeAllObjects]; | ||
2372 | - [self.quadTree removeAllObjects]; | ||
2373 | - [self correctPositionOfAllAnnotations]; | 2631 | + if (_delegateHasDidChangeUserTrackingMode) |
2632 | + [_delegate mapView:self didChangeUserTrackingMode:userTrackingMode animated:animated]; | ||
2374 | } | 2633 | } |
2375 | 2634 | ||
2376 | -- (CGPoint)mapPositionForAnnotation:(RMAnnotation *)annotation | 2635 | +- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation |
2377 | { | 2636 | { |
2378 | - [self correctScreenPosition:annotation animated:NO]; | ||
2379 | - return annotation.position; | 2637 | + if ( ! showsUserLocation || _mapScrollView.isDragging) |
2638 | + return; | ||
2639 | + | ||
2640 | + if ([newLocation distanceFromLocation:oldLocation]) | ||
2641 | + { | ||
2642 | + userLocation.location = newLocation; | ||
2643 | + | ||
2644 | + if (_delegateHasDidUpdateUserLocation) | ||
2645 | + [_delegate mapView:self didUpdateUserLocation:userLocation]; | ||
2646 | + } | ||
2647 | + | ||
2648 | + if (self.userTrackingMode != RMUserTrackingModeNone) | ||
2649 | + { | ||
2650 | + // zoom centered on user location unless we're already centered there (or very close) | ||
2651 | + // | ||
2652 | + CGPoint mapCenterPoint = [self convertPoint:self.center fromView:self.superview]; | ||
2653 | + CGPoint userLocationPoint = [self mapPositionForAnnotation:userLocation]; | ||
2654 | + | ||
2655 | + if (fabsf(userLocationPoint.x - mapCenterPoint.x) > 2 || fabsf(userLocationPoint.y - mapCenterPoint.y > 2)) | ||
2656 | + { | ||
2657 | + float delta = newLocation.horizontalAccuracy / 110000; // approx. meter per degree latitude | ||
2658 | + | ||
2659 | + CLLocationCoordinate2D southWest = CLLocationCoordinate2DMake(newLocation.coordinate.latitude - delta, | ||
2660 | + newLocation.coordinate.longitude - delta); | ||
2661 | + | ||
2662 | + CLLocationCoordinate2D northEast = CLLocationCoordinate2DMake(newLocation.coordinate.latitude + delta, | ||
2663 | + newLocation.coordinate.longitude + delta); | ||
2664 | + | ||
2665 | + if (northEast.latitude != [self latitudeLongitudeBoundingBox].northEast.latitude || | ||
2666 | + northEast.longitude != [self latitudeLongitudeBoundingBox].northEast.longitude || | ||
2667 | + southWest.latitude != [self latitudeLongitudeBoundingBox].southWest.latitude || | ||
2668 | + southWest.longitude != [self latitudeLongitudeBoundingBox].southWest.longitude) | ||
2669 | + [self zoomWithLatitudeLongitudeBoundsSouthWest:southWest northEast:northEast animated:YES]; | ||
2670 | + } | ||
2671 | + } | ||
2672 | + | ||
2673 | + RMAnnotation *accuracyCircleAnnotation = nil; | ||
2674 | + | ||
2675 | + for (RMAnnotation *annotation in _annotations) | ||
2676 | + if ([annotation.annotationType isEqualToString:kRMAccuracyCircleAnnotationTypeName]) | ||
2677 | + accuracyCircleAnnotation = annotation; | ||
2678 | + | ||
2679 | + if ( ! accuracyCircleAnnotation) | ||
2680 | + { | ||
2681 | + accuracyCircleAnnotation = [RMAnnotation annotationWithMapView:self coordinate:newLocation.coordinate andTitle:nil]; | ||
2682 | + | ||
2683 | + accuracyCircleAnnotation.annotationType = kRMAccuracyCircleAnnotationTypeName; | ||
2684 | + | ||
2685 | + accuracyCircleAnnotation.clusteringEnabled = NO; | ||
2686 | + | ||
2687 | + accuracyCircleAnnotation.layer = [[RMCircle alloc] initWithView:self radiusInMeters:newLocation.horizontalAccuracy]; | ||
2688 | + | ||
2689 | + accuracyCircleAnnotation.isUserLocationAnnotation = YES; | ||
2690 | + | ||
2691 | + ((RMCircle *)accuracyCircleAnnotation.layer).lineColor = [UIColor colorWithRed:0.378 green:0.552 blue:0.827 alpha:0.7]; | ||
2692 | + ((RMCircle *)accuracyCircleAnnotation.layer).fillColor = [UIColor colorWithRed:0.378 green:0.552 blue:0.827 alpha:0.15]; | ||
2693 | + | ||
2694 | + ((RMCircle *)accuracyCircleAnnotation.layer).lineWidthInPixels = 2.0; | ||
2695 | + | ||
2696 | + [self addAnnotation:accuracyCircleAnnotation]; | ||
2697 | + } | ||
2698 | + | ||
2699 | + if ([newLocation distanceFromLocation:oldLocation]) | ||
2700 | + accuracyCircleAnnotation.coordinate = newLocation.coordinate; | ||
2701 | + | ||
2702 | + if (newLocation.horizontalAccuracy != oldLocation.horizontalAccuracy) | ||
2703 | + ((RMCircle *)accuracyCircleAnnotation.layer).radiusInMeters = newLocation.horizontalAccuracy; | ||
2704 | + | ||
2705 | + RMAnnotation *trackingHaloAnnotation = nil; | ||
2706 | + | ||
2707 | + for (RMAnnotation *annotation in _annotations) | ||
2708 | + if ([annotation.annotationType isEqualToString:kRMTrackingHaloAnnotationTypeName]) | ||
2709 | + trackingHaloAnnotation = annotation; | ||
2710 | + | ||
2711 | + if ( ! trackingHaloAnnotation) | ||
2712 | + { | ||
2713 | + trackingHaloAnnotation = [RMAnnotation annotationWithMapView:self coordinate:newLocation.coordinate andTitle:nil]; | ||
2714 | + | ||
2715 | + trackingHaloAnnotation.annotationType = kRMTrackingHaloAnnotationTypeName; | ||
2716 | + | ||
2717 | + trackingHaloAnnotation.clusteringEnabled = NO; | ||
2718 | + | ||
2719 | + // create image marker | ||
2720 | + // | ||
2721 | + trackingHaloAnnotation.layer = [[RMMarker alloc] initWithUIImage:[UIImage imageNamed:@"TrackingDotHalo.png"]]; | ||
2722 | + | ||
2723 | + trackingHaloAnnotation.isUserLocationAnnotation = YES; | ||
2724 | + | ||
2725 | + [CATransaction begin]; | ||
2726 | + [CATransaction setAnimationDuration:2.5]; | ||
2727 | + [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; | ||
2728 | + | ||
2729 | + // scale out radially | ||
2730 | + // | ||
2731 | + CABasicAnimation *boundsAnimation = [CABasicAnimation animationWithKeyPath:@"transform"]; | ||
2732 | + | ||
2733 | + boundsAnimation.repeatCount = MAXFLOAT; | ||
2734 | + | ||
2735 | + boundsAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1, 0.1, 1.0)]; | ||
2736 | + boundsAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(2.0, 2.0, 1.0)]; | ||
2737 | + | ||
2738 | + boundsAnimation.removedOnCompletion = NO; | ||
2739 | + | ||
2740 | + boundsAnimation.fillMode = kCAFillModeForwards; | ||
2741 | + | ||
2742 | + [trackingHaloAnnotation.layer addAnimation:boundsAnimation forKey:@"animateScale"]; | ||
2743 | + | ||
2744 | + // go transparent as scaled out | ||
2745 | + // | ||
2746 | + CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; | ||
2747 | + | ||
2748 | + opacityAnimation.repeatCount = MAXFLOAT; | ||
2749 | + | ||
2750 | + opacityAnimation.fromValue = [NSNumber numberWithFloat:1.0]; | ||
2751 | + opacityAnimation.toValue = [NSNumber numberWithFloat:-1.0]; | ||
2752 | + | ||
2753 | + opacityAnimation.removedOnCompletion = NO; | ||
2754 | + | ||
2755 | + opacityAnimation.fillMode = kCAFillModeForwards; | ||
2756 | + | ||
2757 | + [trackingHaloAnnotation.layer addAnimation:opacityAnimation forKey:@"animateOpacity"]; | ||
2758 | + | ||
2759 | + [CATransaction commit]; | ||
2760 | + | ||
2761 | + [self addAnnotation:trackingHaloAnnotation]; | ||
2762 | + } | ||
2763 | + | ||
2764 | + if ([newLocation distanceFromLocation:oldLocation]) | ||
2765 | + trackingHaloAnnotation.coordinate = newLocation.coordinate; | ||
2766 | + | ||
2767 | + userLocation.layer.hidden = ((trackingHaloAnnotation.coordinate.latitude == 0 && trackingHaloAnnotation.coordinate.longitude == 0) || self.userTrackingMode == RMUserTrackingModeFollowWithHeading); | ||
2768 | + | ||
2769 | + accuracyCircleAnnotation.layer.hidden = newLocation.horizontalAccuracy <= 10; | ||
2770 | + | ||
2771 | + trackingHaloAnnotation.layer.hidden = ((trackingHaloAnnotation.coordinate.latitude == 0 && trackingHaloAnnotation.coordinate.longitude == 0) || newLocation.horizontalAccuracy > 10); | ||
2772 | + | ||
2773 | + if ( ! [_annotations containsObject:userLocation]) | ||
2774 | + [self addAnnotation:userLocation]; | ||
2775 | +} | ||
2776 | + | ||
2777 | +- (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager | ||
2778 | +{ | ||
2779 | + return YES; | ||
2780 | +} | ||
2781 | + | ||
2782 | +- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading | ||
2783 | +{ | ||
2784 | + if ( ! showsUserLocation || _mapScrollView.isDragging) | ||
2785 | + return; | ||
2786 | + | ||
2787 | + userLocation.heading = newHeading; | ||
2788 | + | ||
2789 | + if (_delegateHasDidUpdateUserLocation) | ||
2790 | + [_delegate mapView:self didUpdateUserLocation:userLocation]; | ||
2791 | + | ||
2792 | + if (newHeading.trueHeading != 0 && self.userTrackingMode == RMUserTrackingModeFollowWithHeading) | ||
2793 | + { | ||
2794 | + [CATransaction begin]; | ||
2795 | + [CATransaction setAnimationDuration:1.0]; | ||
2796 | + [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]]; | ||
2797 | + | ||
2798 | + [UIView animateWithDuration:1.0 | ||
2799 | + delay:0.0 | ||
2800 | + options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationCurveEaseInOut | ||
2801 | + animations:^(void) | ||
2802 | + { | ||
2803 | + CGFloat angle = (M_PI / -180) * newHeading.trueHeading; | ||
2804 | + | ||
2805 | + _mapScrollView.transform = CGAffineTransformMakeRotation(angle); | ||
2806 | + _overlayView.transform = CGAffineTransformMakeRotation(angle); | ||
2807 | + | ||
2808 | + for (RMAnnotation *annotation in _annotations) | ||
2809 | + if ( ! annotation.isUserLocationAnnotation) | ||
2810 | + annotation.layer.transform = CATransform3DMakeAffineTransform(CGAffineTransformMakeRotation(-angle)); | ||
2811 | + } | ||
2812 | + completion:nil]; | ||
2813 | + | ||
2814 | + [CATransaction commit]; | ||
2815 | + } | ||
2816 | +} | ||
2817 | + | ||
2818 | +- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error | ||
2819 | +{ | ||
2820 | + if ([error code] != kCLErrorLocationUnknown) | ||
2821 | + { | ||
2822 | + self.userTrackingMode = RMUserTrackingModeNone; | ||
2823 | + | ||
2824 | + if (_delegateHasDidFailToLocateUserWithError) | ||
2825 | + [_delegate mapView:self didFailToLocateUserWithError:error]; | ||
2826 | + } | ||
2380 | } | 2827 | } |
2381 | 2828 | ||
2382 | @end | 2829 | @end |
@@ -29,6 +29,13 @@ | @@ -29,6 +29,13 @@ | ||
29 | @class RMMapLayer; | 29 | @class RMMapLayer; |
30 | @class RMMarker; | 30 | @class RMMarker; |
31 | @class RMAnnotation; | 31 | @class RMAnnotation; |
32 | +@class RMUserLocation; | ||
33 | + | ||
34 | +typedef enum { | ||
35 | + RMUserTrackingModeNone = 0, | ||
36 | + RMUserTrackingModeFollow = 1, | ||
37 | + RMUserTrackingModeFollowWithHeading = 2 | ||
38 | +} RMUserTrackingMode; | ||
32 | 39 | ||
33 | // Use this for notifications of map panning, zooming, and taps on the RMMapView. | 40 | // Use this for notifications of map panning, zooming, and taps on the RMMapView. |
34 | @protocol RMMapViewDelegate <NSObject> | 41 | @protocol RMMapViewDelegate <NSObject> |
@@ -67,4 +74,10 @@ | @@ -67,4 +74,10 @@ | ||
67 | - (void)mapView:(RMMapView *)map didDragAnnotation:(RMAnnotation *)annotation withDelta:(CGPoint)delta; | 74 | - (void)mapView:(RMMapView *)map didDragAnnotation:(RMAnnotation *)annotation withDelta:(CGPoint)delta; |
68 | - (void)mapView:(RMMapView *)map didEndDragAnnotation:(RMAnnotation *)annotation; | 75 | - (void)mapView:(RMMapView *)map didEndDragAnnotation:(RMAnnotation *)annotation; |
69 | 76 | ||
77 | +- (void)mapViewWillStartLocatingUser:(RMMapView *)mapView; | ||
78 | +- (void)mapViewDidStopLocatingUser:(RMMapView *)mapView; | ||
79 | +- (void)mapView:(RMMapView *)mapView didUpdateUserLocation:(RMUserLocation *)userLocation; | ||
80 | +- (void)mapView:(RMMapView *)mapView didFailToLocateUserWithError:(NSError *)error; | ||
81 | +- (void)mapView:(RMMapView *)mapView didChangeUserTrackingMode:(RMUserTrackingMode)mode animated:(BOOL)animated; | ||
82 | + | ||
70 | @end | 83 | @end |
MapView/Map/RMUserLocation.h
0 → 100644
1 | +// | ||
2 | +// RMUserLocation.h | ||
3 | +// MapView | ||
4 | +// | ||
5 | +// Created by Justin Miller on 5/8/12. | ||
6 | +// Copyright (c) 2012 MapBox / Development Seed. All rights reserved. | ||
7 | +// | ||
8 | + | ||
9 | +#import "RMAnnotation.h" | ||
10 | + | ||
11 | +@interface RMUserLocation : RMAnnotation | ||
12 | + | ||
13 | +@property (nonatomic, readonly, getter=isUpdating) BOOL updating; | ||
14 | +@property (nonatomic, readonly, retain) CLLocation *location; | ||
15 | +@property (nonatomic, readonly, retain) CLHeading *heading; | ||
16 | + | ||
17 | +@end |
MapView/Map/RMUserLocation.m
0 → 100644
1 | +// | ||
2 | +// RMUserLocation.m | ||
3 | +// MapView | ||
4 | +// | ||
5 | +// Created by Justin Miller on 5/8/12. | ||
6 | +// Copyright (c) 2012 MapBox / Development Seed. All rights reserved. | ||
7 | +// | ||
8 | + | ||
9 | +#import "RMUserLocation.h" | ||
10 | +#import "RMMarker.h" | ||
11 | +#import "RMMapView.h" | ||
12 | + | ||
13 | +@implementation RMUserLocation | ||
14 | + | ||
15 | +@synthesize updating; | ||
16 | +@synthesize location; | ||
17 | +@synthesize heading; | ||
18 | + | ||
19 | +- (id)initWithMapView:(RMMapView *)aMapView coordinate:(CLLocationCoordinate2D)aCoordinate andTitle:(NSString *)aTitle | ||
20 | +{ | ||
21 | + if ( ! (self = [super initWithMapView:aMapView coordinate:aCoordinate andTitle:aTitle])) | ||
22 | + return nil; | ||
23 | + | ||
24 | + NSAssert([[NSBundle mainBundle] pathForResource:@"TrackingDot" ofType:@"png"], @"Unable to find necessary user location graphical assets (copy from MapView/Map/Resources)"); | ||
25 | + | ||
26 | + layer = [[RMMarker alloc] initWithUIImage:[UIImage imageNamed:@"TrackingDot.png"]]; | ||
27 | + | ||
28 | + annotationType = [kRMUserLocationAnnotationTypeName retain]; | ||
29 | + | ||
30 | + clusteringEnabled = NO; | ||
31 | + | ||
32 | + return self; | ||
33 | +} | ||
34 | + | ||
35 | +- (void)dealloc | ||
36 | +{ | ||
37 | + [layer release]; layer = nil; | ||
38 | + [annotationType release]; annotationType = nil; | ||
39 | + [location release]; location = nil; | ||
40 | + [heading release]; heading = nil; | ||
41 | + [super dealloc]; | ||
42 | +} | ||
43 | + | ||
44 | +- (BOOL)isUpdating | ||
45 | +{ | ||
46 | + return (self.mapView.userTrackingMode != RMUserTrackingModeNone); | ||
47 | +} | ||
48 | + | ||
49 | +- (void)setLocation:(CLLocation *)newLocation | ||
50 | +{ | ||
51 | + if ([newLocation distanceFromLocation:location] && newLocation.coordinate.latitude != 0 && newLocation.coordinate.longitude != 0) | ||
52 | + { | ||
53 | + [self willChangeValueForKey:@"location"]; | ||
54 | + [location release]; | ||
55 | + location = [newLocation retain]; | ||
56 | + self.coordinate = location.coordinate; | ||
57 | + [self didChangeValueForKey:@"location"]; | ||
58 | + } | ||
59 | +} | ||
60 | + | ||
61 | +- (void)setHeading:(CLHeading *)newHeading | ||
62 | +{ | ||
63 | + if (newHeading.trueHeading != heading.trueHeading) | ||
64 | + { | ||
65 | + [self willChangeValueForKey:@"heading"]; | ||
66 | + [heading release]; | ||
67 | + heading = [newHeading retain]; | ||
68 | + [self didChangeValueForKey:@"heading"]; | ||
69 | + } | ||
70 | +} | ||
71 | + | ||
72 | +- (BOOL)isUserLocationAnnotation | ||
73 | +{ | ||
74 | + return YES; | ||
75 | +} | ||
76 | + | ||
77 | +@end |
MapView/Map/RMUserTrackingBarButtonItem.h
0 → 100644
1 | +// | ||
2 | +// RMUserTrackingBarButtonItem.h | ||
3 | +// MapView | ||
4 | +// | ||
5 | +// Created by Justin Miller on 5/10/12. | ||
6 | +// Copyright (c) 2012 MapBox / Development Seed. All rights reserved. | ||
7 | +// | ||
8 | + | ||
9 | +#import <UIKit/UIKit.h> | ||
10 | + | ||
11 | +@class RMMapView; | ||
12 | + | ||
13 | +@interface RMUserTrackingBarButtonItem : UIBarButtonItem | ||
14 | + | ||
15 | +- (id)initWithMapView:(RMMapView *)mapView; | ||
16 | + | ||
17 | +@property (nonatomic, retain) RMMapView *mapView; | ||
18 | + | ||
19 | +@end |
MapView/Map/RMUserTrackingBarButtonItem.m
0 → 100644
1 | +// | ||
2 | +// RMUserTrackingBarButtonItem.m | ||
3 | +// MapView | ||
4 | +// | ||
5 | +// Created by Justin Miller on 5/10/12. | ||
6 | +// Copyright (c) 2012 MapBox / Development Seed. All rights reserved. | ||
7 | +// | ||
8 | + | ||
9 | +#import "RMUserTrackingBarButtonItem.h" | ||
10 | + | ||
11 | +#import "RMMapView.h" | ||
12 | +#import "RMUserLocation.h" | ||
13 | + | ||
14 | +typedef enum { | ||
15 | + RMUserTrackingButtonStateActivity = 0, | ||
16 | + RMUserTrackingButtonStateLocation = 1, | ||
17 | + RMUserTrackingButtonStateHeading = 2 | ||
18 | +} RMUserTrackingButtonState; | ||
19 | + | ||
20 | +@interface RMUserTrackingBarButtonItem () | ||
21 | + | ||
22 | +@property (nonatomic, retain) UISegmentedControl *segmentedControl; | ||
23 | +@property (nonatomic, retain) UIImageView *buttonImageView; | ||
24 | +@property (nonatomic, retain) UIActivityIndicatorView *activityView; | ||
25 | +@property (nonatomic) RMUserTrackingButtonState state; | ||
26 | + | ||
27 | +- (void)updateAppearance; | ||
28 | +- (void)changeMode:(id)sender; | ||
29 | + | ||
30 | +@end | ||
31 | + | ||
32 | +#pragma mark - | ||
33 | + | ||
34 | +@implementation RMUserTrackingBarButtonItem | ||
35 | + | ||
36 | +@synthesize mapView=_mapView; | ||
37 | +@synthesize segmentedControl; | ||
38 | +@synthesize buttonImageView; | ||
39 | +@synthesize activityView; | ||
40 | +@synthesize state; | ||
41 | + | ||
42 | +- (id)initWithMapView:(RMMapView *)mapView | ||
43 | +{ | ||
44 | + if (!(self = [super initWithCustomView:[[UIControl alloc] initWithFrame:CGRectMake(0, 0, 32, 32)]])) | ||
45 | + return nil; | ||
46 | + | ||
47 | + segmentedControl = [[[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObject:@""]] retain]; | ||
48 | + segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar; | ||
49 | + [segmentedControl setWidth:32.0 forSegmentAtIndex:0]; | ||
50 | + segmentedControl.userInteractionEnabled = NO; | ||
51 | + segmentedControl.tintColor = self.tintColor; | ||
52 | + segmentedControl.center = self.customView.center; | ||
53 | + | ||
54 | + [self.customView addSubview:segmentedControl]; | ||
55 | + | ||
56 | + buttonImageView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"TrackingLocation.png"]] retain]; | ||
57 | + buttonImageView.contentMode = UIViewContentModeCenter; | ||
58 | + buttonImageView.frame = CGRectMake(0, 0, 32, 32); | ||
59 | + buttonImageView.center = self.customView.center; | ||
60 | + buttonImageView.userInteractionEnabled = NO; | ||
61 | + | ||
62 | + [self.customView addSubview:buttonImageView]; | ||
63 | + | ||
64 | + activityView = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite] retain]; | ||
65 | + activityView.hidesWhenStopped = YES; | ||
66 | + activityView.center = self.customView.center; | ||
67 | + activityView.userInteractionEnabled = NO; | ||
68 | + | ||
69 | + [self.customView addSubview:activityView]; | ||
70 | + | ||
71 | + [((UIControl *)self.customView) addTarget:self action:@selector(changeMode:) forControlEvents:UIControlEventTouchUpInside]; | ||
72 | + | ||
73 | + _mapView = [mapView retain]; | ||
74 | + | ||
75 | + [_mapView addObserver:self forKeyPath:@"userTrackingMode" options:NSKeyValueObservingOptionNew context:nil]; | ||
76 | + [_mapView addObserver:self forKeyPath:@"userLocation.location" options:NSKeyValueObservingOptionNew context:nil]; | ||
77 | + | ||
78 | + state = RMUserTrackingButtonStateLocation; | ||
79 | + | ||
80 | + [self updateAppearance]; | ||
81 | + | ||
82 | + return self; | ||
83 | +} | ||
84 | + | ||
85 | +- (void)dealloc | ||
86 | +{ | ||
87 | + [segmentedControl release]; segmentedControl = nil; | ||
88 | + [buttonImageView release]; buttonImageView = nil; | ||
89 | + [activityView release]; activityView = nil; | ||
90 | + [_mapView removeObserver:self forKeyPath:@"userTrackingMode"]; | ||
91 | + [_mapView removeObserver:self forKeyPath:@"userLocation.location"]; | ||
92 | + [_mapView release]; _mapView = nil; | ||
93 | + | ||
94 | + [super dealloc]; | ||
95 | +} | ||
96 | + | ||
97 | +#pragma mark - | ||
98 | + | ||
99 | +- (void)setMapView:(RMMapView *)newMapView | ||
100 | +{ | ||
101 | + if ( ! [newMapView isEqual:_mapView]) | ||
102 | + { | ||
103 | + [_mapView removeObserver:self forKeyPath:@"userTrackingMode"]; | ||
104 | + [_mapView removeObserver:self forKeyPath:@"userLocation.location"]; | ||
105 | + [_mapView release]; | ||
106 | + | ||
107 | + _mapView = [newMapView retain]; | ||
108 | + [_mapView addObserver:self forKeyPath:@"userTrackingMode" options:NSKeyValueObservingOptionNew context:nil]; | ||
109 | + [_mapView addObserver:self forKeyPath:@"userLocation.location" options:NSKeyValueObservingOptionNew context:nil]; | ||
110 | + | ||
111 | + [self updateAppearance]; | ||
112 | + } | ||
113 | +} | ||
114 | + | ||
115 | +- (void)setTintColor:(UIColor *)newTintColor | ||
116 | +{ | ||
117 | + [super setTintColor:newTintColor]; | ||
118 | + | ||
119 | + segmentedControl.tintColor = newTintColor; | ||
120 | +} | ||
121 | + | ||
122 | +#pragma mark - | ||
123 | + | ||
124 | +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context | ||
125 | +{ | ||
126 | + [self updateAppearance]; | ||
127 | +} | ||
128 | + | ||
129 | +#pragma mark - | ||
130 | + | ||
131 | +- (void)updateAppearance | ||
132 | +{ | ||
133 | + // "selection" state | ||
134 | + // | ||
135 | + segmentedControl.selectedSegmentIndex = (_mapView.userTrackingMode == RMUserTrackingModeNone ? UISegmentedControlNoSegment : 0); | ||
136 | + | ||
137 | + // activity/image state | ||
138 | + // | ||
139 | + if (_mapView.userTrackingMode != RMUserTrackingModeNone && ( ! _mapView.userLocation || ! _mapView.userLocation.location || (_mapView.userLocation.location.coordinate.latitude == 0 && _mapView.userLocation.location.coordinate.longitude == 0))) | ||
140 | + { | ||
141 | + // if we should be tracking but don't yet have a location, show activity | ||
142 | + // | ||
143 | + [UIView animateWithDuration:0.25 | ||
144 | + delay:0.0 | ||
145 | + options:UIViewAnimationOptionBeginFromCurrentState | ||
146 | + animations:^(void) | ||
147 | + { | ||
148 | + buttonImageView.transform = CGAffineTransformMakeScale(0.01, 0.01); | ||
149 | + activityView.transform = CGAffineTransformMakeScale(0.01, 0.01); | ||
150 | + } | ||
151 | + completion:^(BOOL finished) | ||
152 | + { | ||
153 | + buttonImageView.hidden = YES; | ||
154 | + | ||
155 | + [activityView startAnimating]; | ||
156 | + | ||
157 | + [UIView animateWithDuration:0.25 animations:^(void) | ||
158 | + { | ||
159 | + buttonImageView.transform = CGAffineTransformIdentity; | ||
160 | + activityView.transform = CGAffineTransformIdentity; | ||
161 | + }]; | ||
162 | + }]; | ||
163 | + | ||
164 | + state = RMUserTrackingButtonStateActivity; | ||
165 | + } | ||
166 | + else | ||
167 | + { | ||
168 | + if ((_mapView.userTrackingMode != RMUserTrackingModeFollowWithHeading && state != RMUserTrackingButtonStateLocation) || | ||
169 | + (_mapView.userTrackingMode == RMUserTrackingModeFollowWithHeading && state != RMUserTrackingButtonStateHeading)) | ||
170 | + { | ||
171 | + // if image state doesn't match mode, update it | ||
172 | + // | ||
173 | + [UIView animateWithDuration:0.25 | ||
174 | + delay:0.0 | ||
175 | + options:UIViewAnimationOptionBeginFromCurrentState | ||
176 | + animations:^(void) | ||
177 | + { | ||
178 | + buttonImageView.transform = CGAffineTransformMakeScale(0.01, 0.01); | ||
179 | + activityView.transform = CGAffineTransformMakeScale(0.01, 0.01); | ||
180 | + } | ||
181 | + completion:^(BOOL finished) | ||
182 | + { | ||
183 | + buttonImageView.image = [UIImage imageNamed:(_mapView.userTrackingMode == RMUserTrackingModeFollowWithHeading ? @"TrackingHeading.png" : @"TrackingLocation.png")]; | ||
184 | + buttonImageView.hidden = NO; | ||
185 | + | ||
186 | + [activityView stopAnimating]; | ||
187 | + | ||
188 | + [UIView animateWithDuration:0.25 animations:^(void) | ||
189 | + { | ||
190 | + buttonImageView.transform = CGAffineTransformIdentity; | ||
191 | + activityView.transform = CGAffineTransformIdentity; | ||
192 | + }]; | ||
193 | + }]; | ||
194 | + | ||
195 | + state = (_mapView.userTrackingMode == RMUserTrackingModeFollowWithHeading ? RMUserTrackingButtonStateHeading : RMUserTrackingButtonStateLocation); | ||
196 | + } | ||
197 | + } | ||
198 | +} | ||
199 | + | ||
200 | +- (void)changeMode:(id)sender | ||
201 | +{ | ||
202 | + if (_mapView) | ||
203 | + { | ||
204 | + switch (_mapView.userTrackingMode) | ||
205 | + { | ||
206 | + case RMUserTrackingModeNone: | ||
207 | + default: | ||
208 | + { | ||
209 | + _mapView.userTrackingMode = RMUserTrackingModeFollow; | ||
210 | + | ||
211 | + break; | ||
212 | + } | ||
213 | + case RMUserTrackingModeFollow: | ||
214 | + { | ||
215 | + if ([CLLocationManager headingAvailable]) | ||
216 | + _mapView.userTrackingMode = RMUserTrackingModeFollowWithHeading; | ||
217 | + else | ||
218 | + _mapView.userTrackingMode = RMUserTrackingModeNone; | ||
219 | + | ||
220 | + break; | ||
221 | + } | ||
222 | + case RMUserTrackingModeFollowWithHeading: | ||
223 | + { | ||
224 | + _mapView.userTrackingMode = RMUserTrackingModeNone; | ||
225 | + | ||
226 | + break; | ||
227 | + } | ||
228 | + } | ||
229 | + } | ||
230 | + | ||
231 | + [self updateAppearance]; | ||
232 | +} | ||
233 | + | ||
234 | +@end |
MapView/Map/Resources/HeadingAngleSmall.png
0 → 100644
2.66 KB
7.78 KB
MapView/Map/Resources/TrackingDot.png
0 → 100644
860 Bytes
MapView/Map/Resources/TrackingDot@2x.png
0 → 100644
2.33 KB
MapView/Map/Resources/TrackingDotHalo.png
0 → 100644
11.8 KB
MapView/Map/Resources/TrackingDotHalo@2x.png
0 → 100644
26.4 KB
MapView/Map/Resources/TrackingHeading.png
0 → 100644
691 Bytes
MapView/Map/Resources/TrackingHeading@2x.png
0 → 100644
1.6 KB
MapView/Map/Resources/TrackingLocation.png
0 → 100644
414 Bytes
856 Bytes
@@ -101,6 +101,10 @@ | @@ -101,6 +101,10 @@ | ||
101 | DD8CDB4B14E0507100B73EB9 /* RMMapQuestOSMSource.m in Sources */ = {isa = PBXBuildFile; fileRef = DD8CDB4914E0507100B73EB9 /* RMMapQuestOSMSource.m */; }; | 101 | DD8CDB4B14E0507100B73EB9 /* RMMapQuestOSMSource.m in Sources */ = {isa = PBXBuildFile; fileRef = DD8CDB4914E0507100B73EB9 /* RMMapQuestOSMSource.m */; }; |
102 | DD98B6FA14D76B930092882F /* RMMapBoxSource.h in Headers */ = {isa = PBXBuildFile; fileRef = DD98B6F814D76B930092882F /* RMMapBoxSource.h */; }; | 102 | DD98B6FA14D76B930092882F /* RMMapBoxSource.h in Headers */ = {isa = PBXBuildFile; fileRef = DD98B6F814D76B930092882F /* RMMapBoxSource.h */; }; |
103 | DD98B6FB14D76B930092882F /* RMMapBoxSource.m in Sources */ = {isa = PBXBuildFile; fileRef = DD98B6F914D76B930092882F /* RMMapBoxSource.m */; }; | 103 | DD98B6FB14D76B930092882F /* RMMapBoxSource.m in Sources */ = {isa = PBXBuildFile; fileRef = DD98B6F914D76B930092882F /* RMMapBoxSource.m */; }; |
104 | + DD8FD7541559E4A40044D96F /* RMUserLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = DD8FD7521559E4A40044D96F /* RMUserLocation.h */; }; | ||
105 | + DD8FD7551559E4A40044D96F /* RMUserLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = DD8FD7531559E4A40044D96F /* RMUserLocation.m */; }; | ||
106 | + DDA6B8BD155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.h in Headers */ = {isa = PBXBuildFile; fileRef = DDA6B8BB155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.h */; }; | ||
107 | + DDA6B8BE155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DDA6B8BC155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.m */; }; | ||
104 | /* End PBXBuildFile section */ | 108 | /* End PBXBuildFile section */ |
105 | 109 | ||
106 | /* Begin PBXContainerItemProxy section */ | 110 | /* Begin PBXContainerItemProxy section */ |
@@ -230,6 +234,20 @@ | @@ -230,6 +234,20 @@ | ||
230 | DD8CDB4914E0507100B73EB9 /* RMMapQuestOSMSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMMapQuestOSMSource.m; sourceTree = "<group>"; }; | 234 | DD8CDB4914E0507100B73EB9 /* RMMapQuestOSMSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMMapQuestOSMSource.m; sourceTree = "<group>"; }; |
231 | DD98B6F814D76B930092882F /* RMMapBoxSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMMapBoxSource.h; sourceTree = "<group>"; }; | 235 | DD98B6F814D76B930092882F /* RMMapBoxSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMMapBoxSource.h; sourceTree = "<group>"; }; |
232 | DD98B6F914D76B930092882F /* RMMapBoxSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMMapBoxSource.m; sourceTree = "<group>"; }; | 236 | DD98B6F914D76B930092882F /* RMMapBoxSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMMapBoxSource.m; sourceTree = "<group>"; }; |
237 | + DD8FD7521559E4A40044D96F /* RMUserLocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMUserLocation.h; sourceTree = "<group>"; }; | ||
238 | + DD8FD7531559E4A40044D96F /* RMUserLocation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMUserLocation.m; sourceTree = "<group>"; }; | ||
239 | + DD8FD7631559EE120044D96F /* HeadingAngleSmall.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = HeadingAngleSmall.png; path = Resources/HeadingAngleSmall.png; sourceTree = "<group>"; }; | ||
240 | + DD8FD7641559EE120044D96F /* HeadingAngleSmall@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "HeadingAngleSmall@2x.png"; path = "Resources/HeadingAngleSmall@2x.png"; sourceTree = "<group>"; }; | ||
241 | + DD8FD7651559EE120044D96F /* TrackingDot.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = TrackingDot.png; path = Resources/TrackingDot.png; sourceTree = "<group>"; }; | ||
242 | + DD8FD7661559EE120044D96F /* TrackingDot@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "TrackingDot@2x.png"; path = "Resources/TrackingDot@2x.png"; sourceTree = "<group>"; }; | ||
243 | + DD8FD7691559EE120044D96F /* TrackingDotHalo@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "TrackingDotHalo@2x.png"; path = "Resources/TrackingDotHalo@2x.png"; sourceTree = "<group>"; }; | ||
244 | + DD8FD76C1559EE120044D96F /* TrackingDotHalo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = TrackingDotHalo.png; path = Resources/TrackingDotHalo.png; sourceTree = "<group>"; }; | ||
245 | + DDA6B8BB155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMUserTrackingBarButtonItem.h; sourceTree = "<group>"; }; | ||
246 | + DDA6B8BC155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMUserTrackingBarButtonItem.m; sourceTree = "<group>"; }; | ||
247 | + DDA6B8C0155CAB9A003DB5D8 /* TrackingLocation.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = TrackingLocation.png; path = Resources/TrackingLocation.png; sourceTree = "<group>"; }; | ||
248 | + DDA6B8C1155CAB9A003DB5D8 /* TrackingLocation@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "TrackingLocation@2x.png"; path = "Resources/TrackingLocation@2x.png"; sourceTree = "<group>"; }; | ||
249 | + DDA6B8C2155CAB9A003DB5D8 /* TrackingHeading.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = TrackingHeading.png; path = Resources/TrackingHeading.png; sourceTree = "<group>"; }; | ||
250 | + DDA6B8C3155CAB9A003DB5D8 /* TrackingHeading@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "TrackingHeading@2x.png"; path = "Resources/TrackingHeading@2x.png"; sourceTree = "<group>"; }; | ||
233 | /* End PBXFileReference section */ | 251 | /* End PBXFileReference section */ |
234 | 252 | ||
235 | /* Begin PBXFrameworksBuildPhase section */ | 253 | /* Begin PBXFrameworksBuildPhase section */ |
@@ -476,6 +494,7 @@ | @@ -476,6 +494,7 @@ | ||
476 | B83E64EB0E80E73F001663B6 /* Tile Source */, | 494 | B83E64EB0E80E73F001663B6 /* Tile Source */, |
477 | B83E64CE0E80E73F001663B6 /* Tile Layer & Overlay */, | 495 | B83E64CE0E80E73F001663B6 /* Tile Layer & Overlay */, |
478 | B86F26A80E8742ED007A3773 /* Markers and other layers */, | 496 | B86F26A80E8742ED007A3773 /* Markers and other layers */, |
497 | + DD8FD7571559ED930044D96F /* User Location */, | ||
479 | 1266929E0EB75BEA00E002D5 /* Configuration */, | 498 | 1266929E0EB75BEA00E002D5 /* Configuration */, |
480 | B8474B8C0EB40094006A0BC1 /* FMDB */, | 499 | B8474B8C0EB40094006A0BC1 /* FMDB */, |
481 | ); | 500 | ); |
@@ -495,6 +514,35 @@ | @@ -495,6 +514,35 @@ | ||
495 | name = "Coordinate Systems"; | 514 | name = "Coordinate Systems"; |
496 | sourceTree = "<group>"; | 515 | sourceTree = "<group>"; |
497 | }; | 516 | }; |
517 | + DD8FD7571559ED930044D96F /* User Location */ = { | ||
518 | + isa = PBXGroup; | ||
519 | + children = ( | ||
520 | + DD8FD7521559E4A40044D96F /* RMUserLocation.h */, | ||
521 | + DD8FD7531559E4A40044D96F /* RMUserLocation.m */, | ||
522 | + DDA6B8BB155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.h */, | ||
523 | + DDA6B8BC155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.m */, | ||
524 | + DD8FD7581559EDA80044D96F /* Resources */, | ||
525 | + ); | ||
526 | + name = "User Location"; | ||
527 | + sourceTree = "<group>"; | ||
528 | + }; | ||
529 | + DD8FD7581559EDA80044D96F /* Resources */ = { | ||
530 | + isa = PBXGroup; | ||
531 | + children = ( | ||
532 | + DD8FD7631559EE120044D96F /* HeadingAngleSmall.png */, | ||
533 | + DD8FD7641559EE120044D96F /* HeadingAngleSmall@2x.png */, | ||
534 | + DD8FD7651559EE120044D96F /* TrackingDot.png */, | ||
535 | + DD8FD7661559EE120044D96F /* TrackingDot@2x.png */, | ||
536 | + DD8FD76C1559EE120044D96F /* TrackingDotHalo.png */, | ||
537 | + DD8FD7691559EE120044D96F /* TrackingDotHalo@2x.png */, | ||
538 | + DDA6B8C2155CAB9A003DB5D8 /* TrackingHeading.png */, | ||
539 | + DDA6B8C3155CAB9A003DB5D8 /* TrackingHeading@2x.png */, | ||
540 | + DDA6B8C0155CAB9A003DB5D8 /* TrackingLocation.png */, | ||
541 | + DDA6B8C1155CAB9A003DB5D8 /* TrackingLocation@2x.png */, | ||
542 | + ); | ||
543 | + name = Resources; | ||
544 | + sourceTree = "<group>"; | ||
545 | + }; | ||
498 | /* End PBXGroup section */ | 546 | /* End PBXGroup section */ |
499 | 547 | ||
500 | /* Begin PBXHeadersBuildPhase section */ | 548 | /* Begin PBXHeadersBuildPhase section */ |
@@ -543,6 +591,8 @@ | @@ -543,6 +591,8 @@ | ||
543 | DD8CDB4A14E0507100B73EB9 /* RMMapQuestOSMSource.h in Headers */, | 591 | DD8CDB4A14E0507100B73EB9 /* RMMapQuestOSMSource.h in Headers */, |
544 | 1607499514E120A100D535F5 /* RMGenericMapSource.h in Headers */, | 592 | 1607499514E120A100D535F5 /* RMGenericMapSource.h in Headers */, |
545 | 16FFF2CB14E3DBF700A170EC /* RMMapQuestOpenAerialSource.h in Headers */, | 593 | 16FFF2CB14E3DBF700A170EC /* RMMapQuestOpenAerialSource.h in Headers */, |
594 | + DD8FD7541559E4A40044D96F /* RMUserLocation.h in Headers */, | ||
595 | + DDA6B8BD155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.h in Headers */, | ||
546 | 16F3581B15864135003A3AD9 /* RMMapScrollView.h in Headers */, | 596 | 16F3581B15864135003A3AD9 /* RMMapScrollView.h in Headers */, |
547 | 16F98C961590CFF000FF90CE /* RMShape.h in Headers */, | 597 | 16F98C961590CFF000FF90CE /* RMShape.h in Headers */, |
548 | 16FBF07615936BF1004ECAD1 /* RMTileSourcesContainer.h in Headers */, | 598 | 16FBF07615936BF1004ECAD1 /* RMTileSourcesContainer.h in Headers */, |
@@ -656,6 +706,8 @@ | @@ -656,6 +706,8 @@ | ||
656 | DD8CDB4B14E0507100B73EB9 /* RMMapQuestOSMSource.m in Sources */, | 706 | DD8CDB4B14E0507100B73EB9 /* RMMapQuestOSMSource.m in Sources */, |
657 | 1607499614E120A100D535F5 /* RMGenericMapSource.m in Sources */, | 707 | 1607499614E120A100D535F5 /* RMGenericMapSource.m in Sources */, |
658 | 16FFF2CC14E3DBF700A170EC /* RMMapQuestOpenAerialSource.m in Sources */, | 708 | 16FFF2CC14E3DBF700A170EC /* RMMapQuestOpenAerialSource.m in Sources */, |
709 | + DD8FD7551559E4A40044D96F /* RMUserLocation.m in Sources */, | ||
710 | + DDA6B8BE155CAB67003DB5D8 /* RMUserTrackingBarButtonItem.m in Sources */, | ||
659 | 16F3581C15864135003A3AD9 /* RMMapScrollView.m in Sources */, | 711 | 16F3581C15864135003A3AD9 /* RMMapScrollView.m in Sources */, |
660 | 16F98C971590CFF000FF90CE /* RMShape.m in Sources */, | 712 | 16F98C971590CFF000FF90CE /* RMShape.m in Sources */, |
661 | 16FBF07715936BF1004ECAD1 /* RMTileSourcesContainer.m in Sources */, | 713 | 16FBF07715936BF1004ECAD1 /* RMTileSourcesContainer.m in Sources */, |
-
Please register or login to post a comment