diff --git a/Classes/NVMapViewController.h b/Classes/NVMapViewController.h index d5040d1..1d36a89 100644 --- a/Classes/NVMapViewController.h +++ b/Classes/NVMapViewController.h @@ -13,4 +13,6 @@ MKMapView *_mapView; } +- (void)updatePolylineAnnotationView; + @end diff --git a/Classes/NVMapViewController.m b/Classes/NVMapViewController.m index e04d763..e065c69 100644 --- a/Classes/NVMapViewController.m +++ b/Classes/NVMapViewController.m @@ -8,7 +8,7 @@ #import "NVMapViewController.h" #import "NVPolylineAnnotationView.h" - +#import @implementation NVMapViewController @@ -98,7 +98,7 @@ - (void)loadView { nil]; NVPolylineAnnotation *annotation = [[[NVPolylineAnnotation alloc] initWithPoints:points mapView:_mapView] autorelease]; - [_mapView addAnnotation:annotation]; + [_mapView addAnnotation:annotation]; // use some magic numbers to create a map region MKCoordinateRegion region; @@ -118,8 +118,54 @@ - (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id { NSMutableArray* _points; MKMapView* _mapView; - } --(id) initWithPoints:(NSArray*) points mapView:(MKMapView *)mapView; - @property (nonatomic, retain) NSArray* points; +-(id) initWithPoints:(NSArray*) points mapView:(MKMapView *)mapView; + @end diff --git a/Classes/NVPolylineAnnotation.m b/Classes/NVPolylineAnnotation.m index e6099cf..d6ae289 100644 --- a/Classes/NVPolylineAnnotation.m +++ b/Classes/NVPolylineAnnotation.m @@ -11,12 +11,12 @@ @implementation NVPolylineAnnotation -@synthesize points = _points; +@synthesize points = _points; -(id) initWithPoints:(NSArray*) points mapView:(MKMapView *)mapView { self = [super init]; - _points = [[NSArray alloc] initWithArray:points]; + _points = [points mutableCopy]; _mapView = [mapView retain]; return self; diff --git a/Classes/NVPolylineAnnotationView.h b/Classes/NVPolylineAnnotationView.h index 49233ef..c364da0 100644 --- a/Classes/NVPolylineAnnotationView.h +++ b/Classes/NVPolylineAnnotationView.h @@ -20,5 +20,6 @@ - (id)initWithAnnotation:(NVPolylineAnnotation *)annotation mapView:(MKMapView *)mapView; +- (void) regionChanged; @end diff --git a/Classes/NVPolylineAnnotationView.m b/Classes/NVPolylineAnnotationView.m index 83e5d4b..0808d93 100644 --- a/Classes/NVPolylineAnnotationView.m +++ b/Classes/NVPolylineAnnotationView.m @@ -5,8 +5,10 @@ // Created by William Lachance on 10-03-31. // Inspired by code and ideas from Craig Spitzkoff and Nicolas Neubauer 2009. // +// Further development by Joerg Polakowski (www.mobile-melting.de) #import "NVPolylineAnnotationView.h" +#import const CGFloat POLYLINE_WIDTH = 4.0; @@ -35,14 +37,19 @@ - (id) initWithPolylineView:(NVPolylineAnnotationView *)polylineView self.clipsToBounds = NO; } + // for debugging only to check when and how the polyline annotation view is updated + /* + CALayer *infoLayer = [self layer]; + [infoLayer setBorderWidth:2]; + [infoLayer setBorderColor:[[UIColor greenColor] CGColor]]; + */ return self; } -(void) drawRect:(CGRect)rect { - + NVPolylineAnnotation* annotation = (NVPolylineAnnotation*)_polylineView.annotation; - if (annotation.points && annotation.points.count > 0) - { + if (!self.hidden && annotation.points && annotation.points.count > 0) { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor); @@ -55,16 +62,92 @@ -(void) drawRect:(CGRect)rect { CLLocation* location = [annotation.points objectAtIndex:i]; CGPoint point = [_mapView convertCoordinate:location.coordinate toPointToView:self]; - if (i == 0) - CGContextMoveToPoint(context, point.x, point.y); - else - CGContextAddLineToPoint(context, point.x, point.y); - } - + BOOL contains = CGRectContainsPoint(rect, point); + if (contains) { + CGPoint prevPoint = CGPointZero; + @try { + CLLocation *prevLocation = [annotation.points objectAtIndex:(i - 1)]; + prevPoint = [_mapView convertCoordinate:prevLocation.coordinate + toPointToView:self]; + if (!CGRectContainsPoint(rect, prevPoint)) { // outside + CGContextMoveToPoint(context, prevPoint.x, prevPoint.y); + } + } + @catch(NSException *ex) { + prevPoint = CGPointZero; + } + + if (!CGPointEqualToPoint(prevPoint, CGPointZero)) { // prevPoint outside + CGContextAddLineToPoint(context, point.x, point.y); + } + else { // prevPoint inside + CGContextMoveToPoint(context, point.x, point.y); + } + + CGPoint nextPoint = CGPointZero; + @try { + CLLocation *nextLocation = [annotation.points objectAtIndex:(i + 1)]; + nextPoint = [_mapView convertCoordinate:nextLocation.coordinate + toPointToView:self]; + if (!CGRectContainsPoint(rect, nextPoint)) { // outside + CGContextAddLineToPoint(context, nextPoint.x, nextPoint.y); + } + else { + nextPoint = CGPointZero; + } + } + @catch(NSException *ex) { + nextPoint = CGPointZero; + } + } + else { + // if current point is outside the drawing rect, check if the line drawn + // between current point and next point intersects with the drawing rect + CGPoint nextPoint = CGPointZero; + @try { + CLLocation *nextLocation = [annotation.points objectAtIndex:(i + 1)]; + nextPoint = [_mapView convertCoordinate:nextLocation.coordinate + toPointToView:self]; + if (!CGRectContainsPoint(rect, nextPoint)) { // outside, check intersection + CGMutablePathRef myPath = CGPathCreateMutable(); + CGPathMoveToPoint(myPath, NULL, point.x, point.y ); + CGPathAddLineToPoint(myPath, NULL, nextPoint.x, nextPoint.y); + CGPathCloseSubpath(myPath); + + CGRect lineRect = CGPathGetBoundingBox(myPath); + + if (CGRectIntersectsRect(rect, lineRect)) { + CGContextMoveToPoint(context, point.x, point.y); + CGContextAddLineToPoint(context, nextPoint.x, nextPoint.y); + } + CGPathRelease(myPath); + } + } + @catch(NSException *ex) { + nextPoint = CGPointZero; + } + } + } CGContextStrokePath(context); } } +- (BOOL) lineIntersectsRect:(CGRect)rect from:(CGPoint)a to:(CGPoint)b { + float lineSlope = (b.y - a.y) / (b.x - a.x); + float yIntercept = a.y - lineSlope * a.x; + float leftY = lineSlope * CGRectGetMinX(rect) + yIntercept; + float rightY = lineSlope * CGRectGetMaxX(rect) + yIntercept; + + if (leftY >= CGRectGetMinY(rect) && leftY <= CGRectGetMaxY(rect)) { + return YES; + } + if (rightY >= CGRectGetMinY(rect) && rightY <= CGRectGetMaxY(rect)) { + return YES; + } + return NO; +} + + -(void) dealloc { [super dealloc]; [_mapView release]; @@ -86,7 +169,7 @@ - (id)initWithAnnotation:(NVPolylineAnnotation *)annotation self.backgroundColor = [UIColor clearColor]; self.clipsToBounds = NO; self.frame = CGRectMake(0.0, 0.0, _mapView.frame.size.width, _mapView.frame.size.height); - + _internalView = [[[NVPolylineInternalAnnotationView alloc] initWithPolylineView:self mapView:_mapView] autorelease]; [self addSubview:_internalView]; } @@ -96,31 +179,23 @@ - (id)initWithAnnotation:(NVPolylineAnnotation *)annotation -(void) regionChanged { // move the internal route view. - NVPolylineAnnotation* annotation = (NVPolylineAnnotation*)self.annotation; - CGPoint minpt, maxpt; - for (int i = 0; i < annotation.points.count; i++) - { - CLLocation* location = [annotation.points objectAtIndex:i]; - CGPoint point = [_mapView convertCoordinate:location.coordinate toPointToView:_mapView]; - if (point.x < minpt.x || i == 0) - minpt.x = point.x; - if (point.y < minpt.y || i == 0) - minpt.y = point.y; - if (point.x > maxpt.x || i == 0) - maxpt.x = point.x; - if (point.y > maxpt.y || i == 0) - maxpt.y = point.y; + /* In iOS version 4.0 and above we need to calculate the new frame. Before iOS 4 setting + the view's frame to the mapview's frame was sufficient*/ + NSString *reqSysVer = @"4.0"; + NSString *currSysVer = [[UIDevice currentDevice] systemVersion]; + if ([currSysVer compare:reqSysVer options:NSNumericSearch] != NSOrderedAscending) { + CGPoint origin = CGPointMake(0, 0); + origin = [_mapView convertPoint:origin toView:self]; + _internalView.frame = CGRectMake(origin.x, origin.y, _mapView.frame.size.width, _mapView.frame.size.height); + } + else { // iOS < 4.0 + _internalView.frame = _mapView.frame; } - CGFloat w = maxpt.x - minpt.x + (2*POLYLINE_WIDTH); - CGFloat h = maxpt.y - minpt.y + (2*POLYLINE_WIDTH); - - _internalView.frame = CGRectMake(minpt.x - POLYLINE_WIDTH, minpt.y - POLYLINE_WIDTH, - w, h); [_internalView setNeedsDisplay]; } -- (CGPoint) centerOffset { +- (CGPoint) centerOffset { // HACK: use the method to get the centerOffset (called by the main mapview) // to reposition our annotation subview in response to zoom and motion // events diff --git a/README b/README index aad7f9f..aa444ba 100644 --- a/README +++ b/README @@ -3,6 +3,11 @@ MKMapView that continues to display correctly even as a user pinches and zooms the map. It incorporates and expands upon ideas from Craig Spitzkoff and Nicolas Neubauer. +This solution is focused on iPhone OS versions pro to 4.x +Note: In iPhone OS 4.0 the MKPolyline has been added. + +However, if you want to create an application, which should run on iOS versions prior to 4.x and make use of a polyline, you should use this project's polyline implementation. + For more information, see: http://navarra.ca/?p=786 @@ -11,3 +16,9 @@ For information on the inspiration behind this original work, see: http://spitzkoff.com/craig/?p=65 http://pixelfehler.nicolas-neubauer.de/2009/09/04/mapkit-google-maps-iphone-and-drawing-routes-or-polylines/ + +The original example from http://github.com/wlach contained some bugs, which did not seem to get fixed. Bugs fixed: +- Polyline View overlaps other Annotation Views (http://github.com/wlach/nvpolyline/issues#issue/2) +- App crashes when zoomed in (http://github.com/wlach/nvpolyline/issues#issue/1) + + diff --git a/nvpolyline.xcodeproj/project.pbxproj b/nvpolyline.xcodeproj/project.pbxproj index 5887452..6c7c589 100755 --- a/nvpolyline.xcodeproj/project.pbxproj +++ b/nvpolyline.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; }; 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; }; + 1FF5265011FEC1540018A36F /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1FF5264F11FEC1540018A36F /* QuartzCore.framework */; }; 288765FD0DF74451002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765FC0DF74451002DB57D /* CoreGraphics.framework */; }; 28AD733F0D9D9553002E5188 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28AD733E0D9D9553002E5188 /* MainWindow.xib */; }; /* End PBXBuildFile section */ @@ -34,6 +35,7 @@ 1D3623250D0F684500981E51 /* nvpolylineAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = nvpolylineAppDelegate.m; sourceTree = ""; }; 1D6058910D05DD3D006BFB54 /* nvpolyline.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = nvpolyline.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 1FF5264F11FEC1540018A36F /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = iphoneos3.1.3/System/Library/Frameworks/QuartzCore.framework; sourceTree = ""; }; 288765FC0DF74451002DB57D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 28AD733E0D9D9553002E5188 /* MainWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainWindow.xib; sourceTree = ""; }; 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; @@ -51,6 +53,7 @@ 288765FD0DF74451002DB57D /* CoreGraphics.framework in Frameworks */, 069D56261162FB1B009B3FF2 /* MapKit.framework in Frameworks */, 069D57F81164E2A3009B3FF2 /* CoreLocation.framework in Frameworks */, + 1FF5265011FEC1540018A36F /* QuartzCore.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -113,6 +116,7 @@ 29B97323FDCFA39411CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( + 1FF5264F11FEC1540018A36F /* QuartzCore.framework */, 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */, 1D30AB110D05D00D00671497 /* Foundation.framework */, 288765FC0DF74451002DB57D /* CoreGraphics.framework */, @@ -190,6 +194,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Don't Code Sign"; COPY_PHASE_STRIP = NO; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -197,6 +202,8 @@ GCC_PREFIX_HEADER = nvpolyline_Prefix.pch; INFOPLIST_FILE = "nvpolyline-Info.plist"; PRODUCT_NAME = nvpolyline; + "PROVISIONING_PROFILE[sdk=iphoneos*]" = ""; + SDKROOT = iphoneos4.0; }; name = Debug; };