Authored by Michael Tyson

Added Retina display support, improved responsiveness with deceleration, tile ap…

…pear animation, and improved tile loading notifications
... ... @@ -115,6 +115,8 @@ enum {
/// maximum zoom number allowed for the view. #minZoom and #maxZoom must be within the limits of #tileSource but can be stricter; they are clamped to tilesource limits if needed.
float maxZoom;
float screenScale;
id<RMTilesUpdateDelegate> tilesUpdateDelegate;
}
... ... @@ -123,11 +125,14 @@ enum {
@property (readonly) RMTileRect tileBounds;
@property (readonly) CGRect screenBounds;
@property (readwrite) float metersPerPixel;
@property (readonly) float scaledMetersPerPixel;
/// zoom level is clamped to range (minZoom, maxZoom)
@property (readwrite) float zoom;
@property (nonatomic, readwrite) float minZoom, maxZoom;
@property (nonatomic, assign) float screenScale;
@property (readonly) RMTileImageSet *imagesOnScreen;
@property (readonly) RMTileLoader *tileLoader;
... ...
... ... @@ -63,6 +63,7 @@
@synthesize boundingMask;
@synthesize minZoom;
@synthesize maxZoom;
@synthesize screenScale;
@synthesize markerManager;
#pragma mark --- begin constants ----
... ... @@ -128,6 +129,7 @@
renderer = nil;
imagesOnScreen = nil;
tileLoader = nil;
screenScale = ([[UIScreen mainScreen] respondsToSelector:@selector(scale)] ? [[UIScreen mainScreen] scale] : 1.0);
boundingMask = RMMapMinWidthBound;
... ... @@ -748,7 +750,8 @@
-(RMTileRect) tileBounds
{
return [mercatorToTileProjection project: mercatorToScreenProjection];
return [mercatorToTileProjection projectRect:[mercatorToScreenProjection projectedBounds]
atScale:[self scaledMetersPerPixel]];
}
-(CGRect) screenBounds
... ... @@ -777,6 +780,11 @@
[renderer setNeedsDisplay];
}
-(float) scaledMetersPerPixel
{
return [mercatorToScreenProjection metersPerPixel] / screenScale;
}
-(void)setMaxZoom:(float)newMaxZoom
{
maxZoom = newMaxZoom;
... ...
... ... @@ -320,20 +320,15 @@
return gesture;
}
- (void)userPausedDragging
- (void)resumeExpensiveOperations
{
[RMMapContents setPerformExpensiveOperations:YES];
}
- (void)unRegisterPausedDraggingDispatcher
- (void)delayedResumeExpensiveOperations
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(userPausedDragging) object:nil];
}
- (void)registerPausedDraggingDispatcher
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(userPausedDragging) object:nil];
[self performSelector:@selector(userPausedDragging) withObject:nil afterDelay:0.3];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(resumeExpensiveOperations) object:nil];
[self performSelector:@selector(resumeExpensiveOperations) withObject:nil afterDelay:0.4];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
... ... @@ -364,7 +359,7 @@
}
}
[self registerPausedDraggingDispatcher];
[self delayedResumeExpensiveOperations];
}
/// \bug touchesCancelled should clean up, not pass event to markers
... ... @@ -404,14 +399,7 @@
// Calculate the gesture.
lastGesture = [self gestureDetails:[event allTouches]];
// If there are no more fingers on the screen, resume any slow operations.
if (lastGesture.numTouches == 0)
{
[self unRegisterPausedDraggingDispatcher];
// When factoring, beware these two instructions need to happen in this order.
[RMMapContents setPerformExpensiveOperations:YES];
}
BOOL decelerating = NO;
if (touch.tapCount >= 2)
{
if (_delegateHasDoubleTapOnMap) {
... ... @@ -430,9 +418,17 @@
CGPoint currLocation = [touch locationInView:self];
CGSize touchDelta = CGSizeMake(currLocation.x - prevLocation.x, currLocation.y - prevLocation.y);
[self startDecelerationWithDelta:touchDelta];
decelerating = YES;
}
}
// If there are no more fingers on the screen, resume any slow operations.
if (lastGesture.numTouches == 0 && !decelerating)
{
[self delayedResumeExpensiveOperations];
}
if (touch.tapCount == 1)
{
... ... @@ -540,7 +536,7 @@
lastGesture = newGesture;
[self registerPausedDraggingDispatcher];
[self delayedResumeExpensiveOperations];
}
#pragma mark Deceleration
... ... @@ -548,17 +544,23 @@
- (void)startDecelerationWithDelta:(CGSize)delta {
if (ABS(delta.width) >= 1.0f && ABS(delta.height) >= 1.0f) {
_decelerationDelta = delta;
_decelerationTimer = [NSTimer scheduledTimerWithTimeInterval:0.01f
target:self
selector:@selector(incrementDeceleration:)
userInfo:nil
repeats:YES];
if ( !_decelerationTimer ) {
_decelerationTimer = [NSTimer scheduledTimerWithTimeInterval:0.01f
target:self
selector:@selector(incrementDeceleration:)
userInfo:nil
repeats:YES];
}
}
}
- (void)incrementDeceleration:(NSTimer *)timer {
if (ABS(_decelerationDelta.width) < kMinDecelerationDelta && ABS(_decelerationDelta.height) < kMinDecelerationDelta) {
[self stopDeceleration];
// Resume any slow operations after deceleration completes
[self delayedResumeExpensiveOperations];
return;
}
... ...
... ... @@ -39,9 +39,8 @@
@synthesize enableDragging;
@synthesize enableRotation;
#define kDefaultLineWidth 100
#define kDefaultLineWidth 2
/// \bug default values for lineWidth, lineColor, fillColor are hardcoded
- (id) initWithContents: (RMMapContents*)aContents
{
if (![super init])
... ... @@ -58,7 +57,6 @@
lineColor = [UIColor blackColor];
fillColor = [UIColor redColor];
//self.masksToBounds = NO;
self.masksToBounds = YES;
scaleLineWidth = NO;
... ... @@ -66,6 +64,11 @@
enableRotation = YES;
isFirstPoint = YES;
if ( [self respondsToSelector:@selector(setContentsScale:)] )
{
self.contentsScale = ([[UIScreen mainScreen] respondsToSelector:@selector(scale)] ? [[UIScreen mainScreen] scale] : 1.0);
}
return self;
}
... ...
... ... @@ -175,14 +175,14 @@
[customActions setObject:[NSNull null] forKey:@"position"];
[customActions setObject:[NSNull null] forKey:@"bounds"];
[customActions setObject:[NSNull null] forKey:kCAOnOrderOut];
/* CATransition *fadein = [[CATransition alloc] init];
fadein.duration = 2.0;
fadein.type = kCATransitionFade;
[customActions setObject:fadein forKey:kCAOnOrderIn];
[customActions setObject:[NSNull null] forKey:kCAOnOrderIn];
CATransition *fadein = [[CATransition alloc] init];
fadein.duration = 0.3;
fadein.type = kCATransitionReveal;
[customActions setObject:fadein forKey:@"contents"];
[fadein release];
*/
[customActions setObject:[NSNull null] forKey:kCAOnOrderIn];
layer.actions=customActions;
... ...
... ... @@ -79,7 +79,7 @@
// RMTileRect targetRect = [content tileBounds];
BOOL contained = CGRectContainsRect(loadedBounds, [content screenBounds]);
int targetZoom = (int)([[content mercatorToTileProjection] calculateNormalisedZoomFromScale:[content metersPerPixel]]);
int targetZoom = (int)([[content mercatorToTileProjection] calculateNormalisedZoomFromScale:[content scaledMetersPerPixel]]);
if((targetZoom > content.maxZoom) || (targetZoom < content.minZoom))
RMLog(@"target zoom %d is outside of RMMapContents limits %f to %f",
targetZoom, content.minZoom, content.maxZoom);
... ...
... ... @@ -30,10 +30,23 @@
static const NSUInteger kWebTileRetries = 30;
extern NSString *RMWebTileImageErrorDomain;
extern NSString *RMWebTileImageHTTPResponseCodeKey;
enum {
RMWebTileImageErrorUnexpectedHTTPResponse,
RMWebTileImageErrorZeroLengthResponse,
RMWebTileImageErrorNotFoundResponse
};
extern NSString *RMWebTileImageNotificationErrorKey;
/// RMTileImage subclass: a tile image loaded from a URL.
@interface RMWebTileImage : RMTileImage {
NSUInteger retries;
NSInteger retryCode;
NSUInteger retries;
NSError *lastError;
NSURL *url;
NSURLConnection *connection;
... ...
... ... @@ -31,6 +31,11 @@
#import "RMMapContents.h"
#import "RMTileLoader.h"
NSString *RMWebTileImageErrorDomain = @"RMWebTileImageErrorDomain";
NSString *RMWebTileImageHTTPResponseCodeKey = @"RMWebTileImageHTTPResponseCodeKey";
NSString *RMWebTileImageNotificationErrorKey = @"RMWebTileImageNotificationErrorKey";
@implementation RMWebTileImage
- (id) initWithTile: (RMTile)_tile FromURL:(NSString*)urlStr
... ... @@ -46,7 +51,7 @@
retries = kWebTileRetries;
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileRequested object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileRequested object:self];
[self requestTile];
... ... @@ -79,9 +84,10 @@
if(retries == 0) // No more retries
{
[super displayProxy:[RMTileProxy errorTile]];
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileRetrieved object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileRetrieved object:self];
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileError object:[NSNumber numberWithInteger:retryCode]];
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileError object:self userInfo:[NSDictionary dictionaryWithObject:lastError forKey:RMWebTileImageNotificationErrorKey]];
[lastError autorelease]; lastError = nil;
return;
}
... ... @@ -104,7 +110,7 @@
if (!connection)
{
[super displayProxy:[RMTileProxy errorTile]];
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileRetrieved object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileRetrieved object:self];
}
}
... ... @@ -113,12 +119,14 @@
if (!connection)
return;
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileRetrieved object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileRetrieved object:self];
[connection cancel];
[connection release];
connection = nil;
if ( lastError ) [lastError release]; lastError = nil;
[super cancelLoading];
}
... ... @@ -152,7 +160,14 @@
/// \bug magic number
else if(statusCode == 404) // Not Found
{
[super displayProxy:[RMTileProxy missingTile]];
[super displayProxy:[RMTileProxy missingTile]];
NSError *error = [NSError errorWithDomain:RMWebTileImageErrorDomain
code:RMWebTileImageErrorNotFoundResponse
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
NSLocalizedString(@"The requested tile was not found on the server", @""), NSLocalizedDescriptionKey, nil]];
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileError object:self userInfo:[NSDictionary dictionaryWithObject:error forKey:RMWebTileImageNotificationErrorKey]];
[self cancelLoading];
}
else // Other Error
... ... @@ -168,14 +183,21 @@
case 503: retry = TRUE; break;
}
NSError *error = [NSError errorWithDomain:RMWebTileImageErrorDomain
code:RMWebTileImageErrorUnexpectedHTTPResponse
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:statusCode], RMWebTileImageHTTPResponseCodeKey,
[NSString stringWithFormat:NSLocalizedString(@"The server returned error code %d", @""), statusCode], NSLocalizedDescriptionKey, nil]];
if(retry)
{
retryCode = statusCode;
if ( lastError ) [lastError release];
lastError = [error retain];
[self requestTile];
}
else
{
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileError object:[NSNumber numberWithInteger:statusCode]];
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileError object:self userInfo:[NSDictionary dictionaryWithObject:error forKey:RMWebTileImageNotificationErrorKey]];
[self cancelLoading];
}
}
... ... @@ -209,12 +231,14 @@
if(retry)
{
retryCode = [error code];
if ( lastError ) [lastError release];
lastError = [error retain];
[self requestTile];
}
else
{
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileError object:[NSNumber numberWithInteger:[error code]]];
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileError object:self userInfo:[NSDictionary dictionaryWithObject:error forKey:RMWebTileImageNotificationErrorKey]];
[self cancelLoading];
}
}
... ... @@ -223,8 +247,12 @@
{
if ([data length] == 0) {
//RMLog(@"connectionDidFinishLoading %@ data size %d", _connection, [data length]);
/// \bug magic number
retryCode = 512;
if ( lastError ) [lastError release];
lastError = [[NSError errorWithDomain:RMWebTileImageErrorDomain
code:RMWebTileImageErrorZeroLengthResponse
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
NSLocalizedString(@"The server returned a zero-length response", @""), NSLocalizedDescriptionKey, nil]] retain];
[self requestTile];
}
else
... ... @@ -237,8 +265,9 @@
url = nil;
[connection release];
connection = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileRetrieved object:nil];
if ( lastError ) [lastError release]; lastError = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:RMTileRetrieved object:self];
}
}
... ...
... ... @@ -819,7 +819,14 @@
isa = PBXProject;
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "MapView" */;
compatibilityVersion = "Xcode 3.1";
developmentRegion = English;
hasScannedForEncodings = 1;
knownRegions = (
English,
Japanese,
French,
German,
);
mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */;
projectDirPath = "";
projectReferences = (
... ... @@ -1157,6 +1164,7 @@
OTHER_LDFLAGS = "-ObjC";
PREBINDING = NO;
PRODUCT_NAME = MapView;
RUN_CLANG_STATIC_ANALYZER = NO;
};
name = Debug;
};
... ...
... ... @@ -224,6 +224,7 @@
isa = PBXProject;
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "SampleMap" */;
compatibilityVersion = "Xcode 3.1";
developmentRegion = English;
hasScannedForEncodings = 1;
knownRegions = (
English,
... ... @@ -315,8 +316,10 @@
GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
HEADER_SEARCH_PATHS = "../../MapView/**";
INFOPLIST_FILE = Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 3.0;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = SampleMap;
SDKROOT = iphoneos4.1;
};
name = Debug;
};
... ... @@ -330,8 +333,10 @@
GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
HEADER_SEARCH_PATHS = "../../MapView/**";
INFOPLIST_FILE = Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 3.0;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = SampleMap;
SDKROOT = iphoneos4.1;
};
name = Release;
};
... ...