PageRenderTime 148ms CodeModel.GetById 41ms RepoModel.GetById 0ms app.codeStats 1ms

/MapView/Map/RMMapView.m

http://github.com/route-me/route-me
Objective C | 777 lines | 548 code | 145 blank | 84 comment | 159 complexity | 7d1e2b6b132507527424f98cf2470f78 MD5 | raw file
  1. //
  2. // RMMapView.m
  3. //
  4. // Copyright (c) 2008-2009, Route-Me Contributors
  5. // All rights reserved.
  6. //
  7. // Redistribution and use in source and binary forms, with or without
  8. // modification, are permitted provided that the following conditions are met:
  9. //
  10. // * Redistributions of source code must retain the above copyright notice, this
  11. // list of conditions and the following disclaimer.
  12. // * Redistributions in binary form must reproduce the above copyright notice,
  13. // this list of conditions and the following disclaimer in the documentation
  14. // and/or other materials provided with the distribution.
  15. //
  16. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  17. // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  18. // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  19. // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  20. // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  21. // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  22. // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  23. // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  24. // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  25. // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  26. // POSSIBILITY OF SUCH DAMAGE.
  27. #import "RMMapView.h"
  28. #import "RMMapContents.h"
  29. #import "RMMapViewDelegate.h"
  30. #import "RMTileLoader.h"
  31. #import "RMMercatorToScreenProjection.h"
  32. #import "RMMarker.h"
  33. #import "RMProjection.h"
  34. #import "RMMarkerManager.h"
  35. @interface RMMapView (PrivateMethods)
  36. // methods for post-touch deceleration, ala UIScrollView
  37. - (void)startDecelerationWithDelta:(CGSize)delta;
  38. - (void)incrementDeceleration:(NSTimer *)timer;
  39. - (void)stopDeceleration;
  40. @end
  41. @implementation RMMapView
  42. @synthesize contents;
  43. @synthesize decelerationFactor;
  44. @synthesize deceleration;
  45. @synthesize rotation;
  46. @synthesize enableDragging;
  47. @synthesize enableZoom;
  48. @synthesize enableRotate;
  49. #pragma mark --- begin constants ----
  50. #define kDefaultDecelerationFactor .88f
  51. #define kMinDecelerationDelta 0.01f
  52. #pragma mark --- end constants ----
  53. - (RMMarkerManager*)markerManager
  54. {
  55. return self.contents.markerManager;
  56. }
  57. -(void) performInitialSetup
  58. {
  59. LogMethod();
  60. enableDragging = YES;
  61. enableZoom = YES;
  62. enableRotate = NO;
  63. decelerationFactor = kDefaultDecelerationFactor;
  64. deceleration = NO;
  65. screenScale = 0.0;
  66. // [self recalculateImageSet];
  67. if (enableZoom || enableRotate)
  68. [self setMultipleTouchEnabled:TRUE];
  69. self.backgroundColor = [UIColor grayColor];
  70. _constrainMovement=NO;
  71. // [[NSURLCache sharedURLCache] removeAllCachedResponses];
  72. }
  73. - (id)initWithFrame:(CGRect)frame
  74. {
  75. return [self initWithFrame:frame screenScale:0.0];
  76. }
  77. - (id)initWithFrame:(CGRect)frame screenScale:(float)theScreenScale
  78. {
  79. LogMethod();
  80. if (self = [super initWithFrame:frame]) {
  81. [self performInitialSetup];
  82. screenScale = theScreenScale;
  83. }
  84. return self;
  85. }
  86. /// \deprecated Deprecated any time after 0.5.
  87. - (id)initWithFrame:(CGRect)frame WithLocation:(CLLocationCoordinate2D)latlon
  88. {
  89. WarnDeprecated();
  90. LogMethod();
  91. if (self = [super initWithFrame:frame]) {
  92. [self performInitialSetup];
  93. }
  94. [self moveToLatLong:latlon];
  95. return self;
  96. }
  97. //===========================================================
  98. // contents
  99. //===========================================================
  100. - (RMMapContents *)contents
  101. {
  102. if (!_contentsIsSet) {
  103. RMMapContents *newContents = [[RMMapContents alloc] initWithView:self screenScale:screenScale];
  104. self.contents = newContents;
  105. [newContents release];
  106. _contentsIsSet = YES;
  107. }
  108. return contents;
  109. }
  110. - (void)setContents:(RMMapContents *)theContents
  111. {
  112. if (contents != theContents) {
  113. [contents release];
  114. contents = [theContents retain];
  115. _contentsIsSet = YES;
  116. [self performInitialSetup];
  117. }
  118. }
  119. -(void) dealloc
  120. {
  121. LogMethod();
  122. self.contents = nil;
  123. [super dealloc];
  124. }
  125. -(void) drawRect: (CGRect) rect
  126. {
  127. [self.contents drawRect:rect];
  128. }
  129. -(NSString*) description
  130. {
  131. CGRect bounds = [self bounds];
  132. return [NSString stringWithFormat:@"MapView at %.0f,%.0f-%.0f,%.0f", bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height];
  133. }
  134. /// Forward invocations to RMMapContents
  135. - (void)forwardInvocation:(NSInvocation *)invocation
  136. {
  137. SEL aSelector = [invocation selector];
  138. if ([self.contents respondsToSelector:aSelector])
  139. [invocation invokeWithTarget:self.contents];
  140. else
  141. [self doesNotRecognizeSelector:aSelector];
  142. }
  143. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
  144. {
  145. if ([super respondsToSelector:aSelector])
  146. return [super methodSignatureForSelector:aSelector];
  147. else
  148. return [self.contents methodSignatureForSelector:aSelector];
  149. }
  150. #pragma mark Delegate
  151. @dynamic delegate;
  152. - (void) setDelegate: (id<RMMapViewDelegate>) _delegate
  153. {
  154. if (delegate == _delegate) return;
  155. delegate = _delegate;
  156. _delegateHasBeforeMapMove = [(NSObject*) delegate respondsToSelector: @selector(beforeMapMove:)];
  157. _delegateHasAfterMapMove = [(NSObject*) delegate respondsToSelector: @selector(afterMapMove:)];
  158. _delegateHasBeforeMapZoomByFactor = [(NSObject*) delegate respondsToSelector: @selector(beforeMapZoom: byFactor: near:)];
  159. _delegateHasAfterMapZoomByFactor = [(NSObject*) delegate respondsToSelector: @selector(afterMapZoom: byFactor: near:)];
  160. _delegateHasMapViewRegionDidChange = [delegate respondsToSelector:@selector(mapViewRegionDidChange:)];
  161. _delegateHasBeforeMapRotate = [(NSObject*) delegate respondsToSelector: @selector(beforeMapRotate: fromAngle:)];
  162. _delegateHasAfterMapRotate = [(NSObject*) delegate respondsToSelector: @selector(afterMapRotate: toAngle:)];
  163. _delegateHasDoubleTapOnMap = [(NSObject*) delegate respondsToSelector: @selector(doubleTapOnMap:At:)];
  164. _delegateHasSingleTapOnMap = [(NSObject*) delegate respondsToSelector: @selector(singleTapOnMap:At:)];
  165. _delegateHasTapOnMarker = [(NSObject*) delegate respondsToSelector:@selector(tapOnMarker:onMap:)];
  166. _delegateHasTapOnLabelForMarker = [(NSObject*) delegate respondsToSelector:@selector(tapOnLabelForMarker:onMap:)];
  167. _delegateHasTapOnLabelForMarkerOnLayer = [(NSObject*) delegate respondsToSelector:@selector(tapOnLabelForMarker:onMap:onLayer:)];
  168. _delegateHasAfterMapTouch = [(NSObject*) delegate respondsToSelector: @selector(afterMapTouch:)];
  169. _delegateHasShouldDragMarker = [(NSObject*) delegate respondsToSelector: @selector(mapView: shouldDragMarker: withEvent:)];
  170. _delegateHasDidDragMarker = [(NSObject*) delegate respondsToSelector: @selector(mapView: didDragMarker: withEvent:)];
  171. _delegateHasDragMarkerPosition = [(NSObject*) delegate respondsToSelector: @selector(dragMarkerPosition: onMap: position:)];
  172. }
  173. - (id<RMMapViewDelegate>) delegate
  174. {
  175. return delegate;
  176. }
  177. #pragma mark Movement
  178. -(void) moveToProjectedPoint: (RMProjectedPoint) aPoint
  179. {
  180. if (_delegateHasBeforeMapMove) [delegate beforeMapMove: self];
  181. [self.contents moveToProjectedPoint:aPoint];
  182. if (_delegateHasAfterMapMove) [delegate afterMapMove: self];
  183. if (_delegateHasMapViewRegionDidChange) [delegate mapViewRegionDidChange:self];
  184. }
  185. -(void) moveToLatLong: (CLLocationCoordinate2D) point
  186. {
  187. if (_delegateHasBeforeMapMove) [delegate beforeMapMove: self];
  188. [self.contents moveToLatLong:point];
  189. if (_delegateHasAfterMapMove) [delegate afterMapMove: self];
  190. if (_delegateHasMapViewRegionDidChange) [delegate mapViewRegionDidChange:self];
  191. }
  192. -(void)setConstraintsSW:(CLLocationCoordinate2D)sw NE:(CLLocationCoordinate2D)ne
  193. {
  194. //store projections
  195. RMProjection *proj=self.contents.projection;
  196. RMProjectedPoint projectedNE = [proj latLongToPoint:ne];
  197. RMProjectedPoint projectedSW = [proj latLongToPoint:sw];
  198. [self setProjectedContraintsSW:projectedSW NE:projectedNE];
  199. }
  200. - (void)setProjectedContraintsSW:(RMProjectedPoint)sw NE:(RMProjectedPoint)ne {
  201. SWconstraint = sw;
  202. NEconstraint = ne;
  203. _constrainMovement=YES;
  204. }
  205. -(void)moveBy:(CGSize)delta
  206. {
  207. if ( _constrainMovement )
  208. {
  209. //bounds are
  210. RMMercatorToScreenProjection *mtsp=self.contents.mercatorToScreenProjection;
  211. //calculate new bounds after move
  212. RMProjectedRect pBounds=[mtsp projectedBounds];
  213. RMProjectedSize XYDelta = [mtsp projectScreenSizeToXY:delta];
  214. CGSize sizeRatio = CGSizeMake(((delta.width == 0) ? 0 : XYDelta.width / delta.width),
  215. ((delta.height == 0) ? 0 : XYDelta.height / delta.height));
  216. RMProjectedRect newBounds=pBounds;
  217. //move the rect by delta
  218. newBounds.origin.northing -= XYDelta.height;
  219. newBounds.origin.easting -= XYDelta.width;
  220. // see if new bounds are within constrained bounds, and constrain if necessary
  221. BOOL constrained = NO;
  222. if ( newBounds.origin.northing < SWconstraint.northing ) { newBounds.origin.northing = SWconstraint.northing; constrained = YES; }
  223. if ( newBounds.origin.northing+newBounds.size.height > NEconstraint.northing ) { newBounds.origin.northing = NEconstraint.northing - newBounds.size.height; constrained = YES; }
  224. if ( newBounds.origin.easting < SWconstraint.easting ) { newBounds.origin.easting = SWconstraint.easting; constrained = YES; }
  225. if ( newBounds.origin.easting+newBounds.size.width > NEconstraint.easting ) { newBounds.origin.easting = NEconstraint.easting - newBounds.size.width; constrained = YES; }
  226. if ( constrained )
  227. {
  228. // Adjust delta to match constraint
  229. XYDelta.height = pBounds.origin.northing - newBounds.origin.northing;
  230. XYDelta.width = pBounds.origin.easting - newBounds.origin.easting;
  231. delta = CGSizeMake(((sizeRatio.width == 0) ? 0 : XYDelta.width / sizeRatio.width),
  232. ((sizeRatio.height == 0) ? 0 : XYDelta.height / sizeRatio.height));
  233. }
  234. }
  235. if (_delegateHasBeforeMapMove) [delegate beforeMapMove: self];
  236. [self.contents moveBy:delta];
  237. if (_delegateHasAfterMapMove) [delegate afterMapMove: self];
  238. if (_delegateHasMapViewRegionDidChange) [delegate mapViewRegionDidChange:self];
  239. }
  240. - (void)zoomByFactor: (float) zoomFactor near:(CGPoint) center
  241. {
  242. [self zoomByFactor:zoomFactor near:center animated:NO];
  243. }
  244. - (void)zoomByFactor: (float) zoomFactor near:(CGPoint) center animated:(BOOL)animated
  245. {
  246. if ( _constrainMovement )
  247. {
  248. //check that bounds after zoom don't exceed map constraints
  249. //the logic is copued from the method zoomByFactor,
  250. float _zoomFactor = [self.contents adjustZoomForBoundingMask:zoomFactor];
  251. float zoomDelta = log2f(_zoomFactor);
  252. float targetZoom = zoomDelta + [self.contents zoom];
  253. BOOL canZoom=NO;
  254. if (targetZoom == [self.contents zoom]){
  255. //OK... . I could even do a return here.. but it will hamper with future logic..
  256. canZoom=YES;
  257. }
  258. // clamp zoom to remain below or equal to maxZoom after zoomAfter will be applied
  259. if(targetZoom > [self.contents maxZoom]){
  260. zoomFactor = exp2f([self.contents maxZoom] - [self.contents zoom]);
  261. }
  262. // clamp zoom to remain above or equal to minZoom after zoomAfter will be applied
  263. if(targetZoom < [self.contents minZoom]){
  264. zoomFactor = 1/exp2f([self.contents zoom] - [self.contents minZoom]);
  265. }
  266. //bools for syntactical sugar to understand the logic in the if statement below
  267. BOOL zoomAtMax = ([self.contents zoom] == [self.contents maxZoom]);
  268. BOOL zoomAtMin = ([self.contents zoom] == [self.contents minZoom]);
  269. BOOL zoomGreaterMin = ([self.contents zoom] > [self.contents minZoom]);
  270. BOOL zoomLessMax = ([self.contents zoom] < [ self.contents maxZoom]);
  271. //zooming in zoomFactor > 1
  272. //zooming out zoomFactor < 1
  273. if ((zoomGreaterMin && zoomLessMax) || (zoomAtMax && zoomFactor<1) || (zoomAtMin && zoomFactor>1))
  274. {
  275. //if I'm here it means I could zoom, now we have to see what will happen after zoom
  276. RMMercatorToScreenProjection *mtsp= self.contents.mercatorToScreenProjection ;
  277. //get copies of mercatorRoScreenProjection's data
  278. RMProjectedPoint origin=[mtsp origin];
  279. float metersPerPixel=mtsp.metersPerPixel;
  280. CGRect screenBounds=[mtsp screenBounds];
  281. //tjis is copied from [RMMercatorToScreenBounds zoomScreenByFactor]
  282. // First we move the origin to the pivot...
  283. origin.easting += center.x * metersPerPixel;
  284. origin.northing += (screenBounds.size.height - center.y) * metersPerPixel;
  285. // Then scale by 1/factor
  286. metersPerPixel /= _zoomFactor;
  287. // Then translate back
  288. origin.easting -= center.x * metersPerPixel;
  289. origin.northing -= (screenBounds.size.height - center.y) * metersPerPixel;
  290. origin = [mtsp.projection wrapPointHorizontally:origin];
  291. //calculate new bounds
  292. RMProjectedRect zRect;
  293. zRect.origin = origin;
  294. zRect.size.width = screenBounds.size.width * metersPerPixel;
  295. zRect.size.height = screenBounds.size.height * metersPerPixel;
  296. //can zoom only if within bounds
  297. canZoom= zoomDelta > 0 || !(zRect.origin.northing < SWconstraint.northing || zRect.origin.northing+zRect.size.height> NEconstraint.northing ||
  298. zRect.origin.easting < SWconstraint.easting || zRect.origin.easting+zRect.size.width > NEconstraint.easting);
  299. }
  300. if(!canZoom){
  301. RMLog(@"Zooming will move map out of bounds: no zoom");
  302. return;
  303. }
  304. }
  305. if (_delegateHasBeforeMapZoomByFactor) [delegate beforeMapZoom: self byFactor: zoomFactor near: center];
  306. [self.contents zoomByFactor:zoomFactor near:center animated:animated withCallback:(animated && (_delegateHasAfterMapZoomByFactor || _delegateHasMapViewRegionDidChange))?self:nil];
  307. if (!animated)
  308. {
  309. if (_delegateHasAfterMapZoomByFactor) [delegate afterMapZoom: self byFactor: zoomFactor near: center];
  310. if (_delegateHasMapViewRegionDidChange) [delegate mapViewRegionDidChange:self];
  311. }
  312. }
  313. #pragma mark RMMapContentsAnimationCallback methods
  314. - (void)animationFinishedWithZoomFactor:(float)zoomFactor near:(CGPoint)p
  315. {
  316. if (_delegateHasAfterMapZoomByFactor)
  317. [delegate afterMapZoom: self byFactor: zoomFactor near: p];
  318. }
  319. - (void)animationStepped {
  320. if (_delegateHasMapViewRegionDidChange) [delegate mapViewRegionDidChange:self];
  321. }
  322. #pragma mark Event handling
  323. - (RMGestureDetails) gestureDetails: (NSSet*) touches
  324. {
  325. RMGestureDetails gesture;
  326. gesture.center.x = gesture.center.y = 0;
  327. gesture.averageDistanceFromCenter = 0;
  328. gesture.angle = 0.0;
  329. int interestingTouches = 0;
  330. for (UITouch *touch in touches)
  331. {
  332. if ([touch phase] != UITouchPhaseBegan
  333. && [touch phase] != UITouchPhaseMoved
  334. && [touch phase] != UITouchPhaseStationary)
  335. continue;
  336. // RMLog(@"phase = %d", [touch phase]);
  337. interestingTouches++;
  338. CGPoint location = [touch locationInView: self];
  339. gesture.center.x += location.x;
  340. gesture.center.y += location.y;
  341. }
  342. if (interestingTouches == 0)
  343. {
  344. gesture.center = lastGesture.center;
  345. gesture.numTouches = 0;
  346. gesture.averageDistanceFromCenter = 0.0f;
  347. return gesture;
  348. }
  349. // RMLog(@"interestingTouches = %d", interestingTouches);
  350. gesture.center.x /= interestingTouches;
  351. gesture.center.y /= interestingTouches;
  352. for (UITouch *touch in touches)
  353. {
  354. if ([touch phase] != UITouchPhaseBegan
  355. && [touch phase] != UITouchPhaseMoved
  356. && [touch phase] != UITouchPhaseStationary)
  357. continue;
  358. CGPoint location = [touch locationInView: self];
  359. // RMLog(@"For touch at %.0f, %.0f:", location.x, location.y);
  360. float dx = location.x - gesture.center.x;
  361. float dy = location.y - gesture.center.y;
  362. // RMLog(@"delta = %.0f, %.0f distance = %f", dx, dy, sqrtf((dx*dx) + (dy*dy)));
  363. gesture.averageDistanceFromCenter += sqrtf((dx*dx) + (dy*dy));
  364. }
  365. gesture.averageDistanceFromCenter /= interestingTouches;
  366. gesture.numTouches = interestingTouches;
  367. if ([touches count] == 2)
  368. {
  369. CGPoint first = [[[touches allObjects] objectAtIndex:0] locationInView:[self superview]];
  370. CGPoint second = [[[touches allObjects] objectAtIndex:1] locationInView:[self superview]];
  371. CGFloat height = second.y - first.y;
  372. CGFloat width = first.x - second.x;
  373. gesture.angle = atan2(height,width);
  374. }
  375. //RMLog(@"center = %.0f,%.0f dist = %f, angle = %f", gesture.center.x, gesture.center.y, gesture.averageDistanceFromCenter, gesture.angle);
  376. return gesture;
  377. }
  378. - (void)resumeExpensiveOperations
  379. {
  380. [RMMapContents setPerformExpensiveOperations:YES];
  381. }
  382. - (void)delayedResumeExpensiveOperations
  383. {
  384. [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resumeExpensiveOperations) object:nil];
  385. [self performSelector:@selector(resumeExpensiveOperations) withObject:nil afterDelay:0.4];
  386. }
  387. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
  388. {
  389. UITouch *touch = [[touches allObjects] objectAtIndex:0];
  390. //Check if the touch hit a RMMarker subclass and if so, forward the touch event on
  391. //so it can be handled there
  392. id furthestLayerDown = [self.contents.overlay hitTest:[touch locationInView:self]];
  393. if ([[furthestLayerDown class]isSubclassOfClass: [RMMarker class]]) {
  394. if ([furthestLayerDown respondsToSelector:@selector(touchesBegan:withEvent:)]) {
  395. [furthestLayerDown performSelector:@selector(touchesBegan:withEvent:) withObject:touches withObject:event];
  396. return;
  397. }
  398. }
  399. if (lastGesture.numTouches == 0)
  400. {
  401. [RMMapContents setPerformExpensiveOperations:NO];
  402. }
  403. // RMLog(@"touchesBegan %d", [[event allTouches] count]);
  404. lastGesture = [self gestureDetails:[event allTouches]];
  405. if(deceleration)
  406. {
  407. if (_decelerationTimer != nil) {
  408. [self stopDeceleration];
  409. }
  410. }
  411. [self delayedResumeExpensiveOperations];
  412. }
  413. /// \bug touchesCancelled should clean up, not pass event to markers
  414. - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
  415. {
  416. UITouch *touch = [[touches allObjects] objectAtIndex:0];
  417. //Check if the touch hit a RMMarker subclass and if so, forward the touch event on
  418. //so it can be handled there
  419. id furthestLayerDown = [self.contents.overlay hitTest:[touch locationInView:self]];
  420. if ([[furthestLayerDown class]isSubclassOfClass: [RMMarker class]]) {
  421. if ([furthestLayerDown respondsToSelector:@selector(touchesCancelled:withEvent:)]) {
  422. [furthestLayerDown performSelector:@selector(touchesCancelled:withEvent:) withObject:touches withObject:event];
  423. return;
  424. }
  425. }
  426. [self delayedResumeExpensiveOperations];
  427. }
  428. - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
  429. {
  430. UITouch *touch = [[touches allObjects] objectAtIndex:0];
  431. //Check if the touch hit a RMMarker subclass and if so, forward the touch event on
  432. //so it can be handled there
  433. id furthestLayerDown = [self.contents.overlay hitTest:[touch locationInView:self]];
  434. if ([[furthestLayerDown class]isSubclassOfClass: [RMMarker class]]) {
  435. if ([furthestLayerDown respondsToSelector:@selector(touchesEnded:withEvent:)]) {
  436. [furthestLayerDown performSelector:@selector(touchesEnded:withEvent:) withObject:touches withObject:event];
  437. return;
  438. }
  439. }
  440. NSInteger lastTouches = lastGesture.numTouches;
  441. // Calculate the gesture.
  442. lastGesture = [self gestureDetails:[event allTouches]];
  443. BOOL decelerating = NO;
  444. if (touch.tapCount >= 2)
  445. {
  446. if (_delegateHasDoubleTapOnMap) {
  447. [delegate doubleTapOnMap: self At: lastGesture.center];
  448. } else {
  449. // Default behaviour matches built in maps.app
  450. float nextZoomFactor = [self.contents nextNativeZoomFactor];
  451. if (nextZoomFactor != 0)
  452. [self zoomByFactor:nextZoomFactor near:[touch locationInView:self] animated:YES];
  453. }
  454. } else if (lastTouches == 1 && touch.tapCount != 1) {
  455. // deceleration
  456. if(deceleration && enableDragging)
  457. {
  458. CGPoint prevLocation = [touch previousLocationInView:self];
  459. CGPoint currLocation = [touch locationInView:self];
  460. CGSize touchDelta = CGSizeMake(currLocation.x - prevLocation.x, currLocation.y - prevLocation.y);
  461. [self startDecelerationWithDelta:touchDelta];
  462. decelerating = YES;
  463. }
  464. }
  465. // If there are no more fingers on the screen, resume any slow operations.
  466. if (lastGesture.numTouches == 0 && !decelerating)
  467. {
  468. [self delayedResumeExpensiveOperations];
  469. }
  470. if (touch.tapCount == 1)
  471. {
  472. if(lastGesture.numTouches == 0)
  473. {
  474. CALayer* hit = [self.contents.overlay hitTest:[touch locationInView:self]];
  475. // RMLog(@"LAYER of type %@",[hit description]);
  476. if (hit != nil) {
  477. CALayer *superlayer = [hit superlayer];
  478. // See if tap was on a marker or marker label and send delegate protocol method
  479. if ([hit isKindOfClass: [RMMarker class]]) {
  480. if (_delegateHasTapOnMarker) {
  481. [delegate tapOnMarker:(RMMarker*)hit onMap:self];
  482. }
  483. } else if (superlayer != nil && [superlayer isKindOfClass: [RMMarker class]]) {
  484. if (_delegateHasTapOnLabelForMarker) {
  485. [delegate tapOnLabelForMarker:(RMMarker*)superlayer onMap:self];
  486. }
  487. if (_delegateHasTapOnLabelForMarkerOnLayer) {
  488. [delegate tapOnLabelForMarker:(RMMarker*)superlayer onMap:self onLayer:hit];
  489. }
  490. } else if ([superlayer superlayer] != nil && [[superlayer superlayer] isKindOfClass: [RMMarker class]]) {
  491. if (_delegateHasTapOnLabelForMarker) {
  492. [delegate tapOnLabelForMarker:(RMMarker*)[superlayer superlayer] onMap:self];
  493. }
  494. if (_delegateHasTapOnLabelForMarkerOnLayer) {
  495. [delegate tapOnLabelForMarker:(RMMarker*)[superlayer superlayer] onMap:self onLayer:hit];
  496. }
  497. } else if (_delegateHasSingleTapOnMap) {
  498. [delegate singleTapOnMap: self At: [touch locationInView:self]];
  499. }
  500. }
  501. }
  502. else if(!enableDragging && (lastGesture.numTouches == 1))
  503. {
  504. float prevZoomFactor = [self.contents prevNativeZoomFactor];
  505. if (prevZoomFactor != 0)
  506. [self zoomByFactor:prevZoomFactor near:[touch locationInView:self] animated:YES];
  507. }
  508. }
  509. if (_delegateHasAfterMapTouch) [delegate afterMapTouch: self];
  510. }
  511. - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
  512. {
  513. UITouch *touch = [[touches allObjects] objectAtIndex:0];
  514. //Check if the touch hit a RMMarker subclass and if so, forward the touch event on
  515. //so it can be handled there
  516. id furthestLayerDown = [self.contents.overlay hitTest:[touch locationInView:self]];
  517. if ([[furthestLayerDown class]isSubclassOfClass: [RMMarker class]]) {
  518. if ([furthestLayerDown respondsToSelector:@selector(touchesMoved:withEvent:)]) {
  519. [furthestLayerDown performSelector:@selector(touchesMoved:withEvent:) withObject:touches withObject:event];
  520. return;
  521. }
  522. }
  523. CALayer* hit = [self.contents.overlay hitTest:[touch locationInView:self]];
  524. // RMLog(@"LAYER of type %@",[hit description]);
  525. if (hit != nil) {
  526. if ([hit isKindOfClass: [RMMarker class]]) {
  527. if (!_delegateHasShouldDragMarker || (_delegateHasShouldDragMarker && [delegate mapView:self shouldDragMarker:(RMMarker*)hit withEvent:event])) {
  528. if (_delegateHasDidDragMarker) {
  529. [delegate mapView:self didDragMarker:(RMMarker*)hit withEvent:event];
  530. return;
  531. }
  532. }
  533. }
  534. }
  535. RMGestureDetails newGesture = [self gestureDetails:[event allTouches]];
  536. if(enableRotate && (newGesture.numTouches == lastGesture.numTouches))
  537. {
  538. if(newGesture.numTouches == 2)
  539. {
  540. CGFloat angleDiff = lastGesture.angle - newGesture.angle;
  541. CGFloat newAngle = self.rotation + angleDiff;
  542. [self setRotation:newAngle];
  543. }
  544. }
  545. if (enableDragging && newGesture.numTouches == lastGesture.numTouches)
  546. {
  547. CGSize delta;
  548. delta.width = newGesture.center.x - lastGesture.center.x;
  549. delta.height = newGesture.center.y - lastGesture.center.y;
  550. if (enableZoom && newGesture.numTouches > 1)
  551. {
  552. NSAssert (lastGesture.averageDistanceFromCenter > 0.0f && newGesture.averageDistanceFromCenter > 0.0f,
  553. @"Distance from center is zero despite >1 touches on the screen");
  554. double zoomFactor = newGesture.averageDistanceFromCenter / lastGesture.averageDistanceFromCenter;
  555. [self moveBy:delta];
  556. [self zoomByFactor: zoomFactor near: newGesture.center];
  557. }
  558. else
  559. {
  560. [self moveBy:delta];
  561. }
  562. }
  563. lastGesture = newGesture;
  564. [self delayedResumeExpensiveOperations];
  565. }
  566. // first responder needed to use UIMenuController
  567. - (BOOL)canBecomeFirstResponder
  568. {
  569. return YES;
  570. }
  571. #pragma mark Deceleration
  572. - (void)startDecelerationWithDelta:(CGSize)delta {
  573. if (ABS(delta.width) >= 1.0f && ABS(delta.height) >= 1.0f) {
  574. _decelerationDelta = delta;
  575. if ( !_decelerationTimer ) {
  576. _decelerationTimer = [NSTimer scheduledTimerWithTimeInterval:0.01f
  577. target:self
  578. selector:@selector(incrementDeceleration:)
  579. userInfo:nil
  580. repeats:YES];
  581. }
  582. }
  583. }
  584. - (void)incrementDeceleration:(NSTimer *)timer {
  585. if (ABS(_decelerationDelta.width) < kMinDecelerationDelta && ABS(_decelerationDelta.height) < kMinDecelerationDelta) {
  586. [self stopDeceleration];
  587. // Resume any slow operations after deceleration completes
  588. [self delayedResumeExpensiveOperations];
  589. return;
  590. }
  591. // avoid calling delegate methods? design call here
  592. [self.contents moveBy:_decelerationDelta];
  593. _decelerationDelta.width *= [self decelerationFactor];
  594. _decelerationDelta.height *= [self decelerationFactor];
  595. }
  596. - (void)stopDeceleration {
  597. if (_decelerationTimer != nil) {
  598. [_decelerationTimer invalidate];
  599. _decelerationTimer = nil;
  600. _decelerationDelta = CGSizeZero;
  601. // call delegate methods; design call (see above)
  602. [self moveBy:CGSizeZero];
  603. }
  604. }
  605. /// Must be called by higher didReceiveMemoryWarning
  606. - (void)didReceiveMemoryWarning
  607. {
  608. LogMethod();
  609. [contents didReceiveMemoryWarning];
  610. }
  611. - (void)setFrame:(CGRect)frame
  612. {
  613. CGRect r = self.frame;
  614. [super setFrame:frame];
  615. // only change if the frame changes AND there is contents
  616. if (!CGRectEqualToRect(r, frame) && contents) {
  617. [contents setFrame:frame];
  618. }
  619. }
  620. - (void)setRotation:(CGFloat)angle
  621. {
  622. if (_delegateHasBeforeMapRotate) [delegate beforeMapRotate: self fromAngle: rotation];
  623. [CATransaction begin];
  624. [CATransaction setValue:[NSNumber numberWithFloat:0.0f] forKey:kCATransactionAnimationDuration];
  625. [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
  626. rotation = angle;
  627. self.transform = CGAffineTransformMakeRotation(rotation);
  628. [contents setRotation:rotation];
  629. [CATransaction commit];
  630. if (_delegateHasAfterMapRotate) [delegate afterMapRotate: self toAngle: rotation];
  631. }
  632. @end