Getting Oriented with MapKit: Everything you need to get started with the new mapping framework.
360iDev MapKit Presentation - Denver 2009
description
Transcript of 360iDev MapKit Presentation - Denver 2009
Maps on the iPhone
Using MapKit for Fun and Profit
1Wednesday, September 30, 2009
Add A MKMapView
2Wednesday, September 30, 2009
Tweak Parameters
Then Build and Go...
Show the blue breathing dot
3Wednesday, September 30, 2009
And Bada-Boom A Map App
4Wednesday, September 30, 2009
But we want more, like the breathing
Blue Dot
5Wednesday, September 30, 2009
Where we are Headed
Animated ‘Breathing’
‘Left’ Action - Shake Map
‘Right’ Action - Details
6Wednesday, September 30, 2009
Stuff To Do
• View Based Project• Parse XML from USGS• Create Earthquakes• Store Earthquakes• Add Annotations• Provide Annotation Views• Respond to Events
7Wednesday, September 30, 2009
View
Based Project
Connect the MapView to our VC
8Wednesday, September 30, 2009
NSOperationQueue
eventloop
operationthread
EarthquakeParser
NSOperation
NSXMLParser
didStartElement:foundCharcters:
didEndElement:
initWithContentsOfURL:
parse
parseForData:
EarthquakeParserDelegate(MapQuakesViewController)
addEarthquake:
invokeOnMainThread:
parserFinished
addOperation:
Parse XML
Don’t block the main event thread!
Parse XM
L
9Wednesday, September 30, 2009
NSOperationQueue
operationthread NSOperation
EarthquakeParseraddOperation:
parseForData:
Kick Off The Parse parseForData: is on an alternate thread, won’t block event thread while downloading the XML
Parse XM
L
10Wednesday, September 30, 2009
EarthquakeParser
NSXMLParser
initWithContentsOfURL:
parse
Parse XML on the alternate thread
Each XML Event comes to the parser’s delegate on the alternate thread
Parse XM
L
11Wednesday, September 30, 2009
NSXMLParser Kick-off- (BOOL)parseForData { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; NSURL *url = [NSURL URLWithString:feedURLString]; BOOL success = NO; NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url]; [parser setDelegate:self]; [parser setShouldProcessNamespaces:NO]; [parser setShouldReportNamespacePrefixes:NO]; [parser setShouldResolveExternalEntities:NO]; success = [parser parse]; NSError *parseError = [parser parserError]; if (parseError) {! NSLog(@"parse error = %@", parseError); } [parser release]; [pool drain]; return success;}
Parse XM
L
12Wednesday, September 30, 2009
EarthquakeParser
NSXMLParser
didStartElement:foundCharcters:
didEndElement:
Parse XML on the alternate thread
Each XML Event comes to the parser’s delegate on the alternate thread
Create Earthquakes
13Wednesday, September 30, 2009
Entry Element starts and Earthquake
Create Earthquakes
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { if(nil != qName) { elementName = qName; // swap for the qName if we have a name space } if ([elementName isEqualToString:@"entry"]) { self.currentEarthquake = [[[Earthquake alloc] init] autorelease]; } else if([elementName isEqualToString:@"link"]) { // ignore the related content and just grab the alternate if ([[attributeDict valueForKey:@"rel"] isEqualToString:@"alternate"]) { NSString *link = [attributeDict valueForKey:@"href"]; self.currentEarthquake.detailsURL = [NSString stringWithFormat:@"http://earthquake.usgs.gov/%@", link]; } } else if([elementName isEqualToString:@"title"] || [elementName isEqualToString:@"updated"] || [elementName isEqualToString:@"id"] || [elementName isEqualToString:@"georss:point"] || [elementName isEqualToString:@"georss:elev"]) { self.propertyValue = [NSMutableString string]; } else { self.propertyValue = nil; }}
14Wednesday, September 30, 2009
eventloop
EarthquakeParser
EarthquakeParserDelegate(MapQuakesViewController)
addEarthquake:
invokeOnMainThread:
Back to the Main Thread
Let the main thread know an earthquake was found by pushing the addEarthquake: and parserFinished methods onto the main thread
Store Earthquakes
15Wednesday, September 30, 2009
Ending Element Pushes Earthquake
Store Earthquakes
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { ... } else if([elementName isEqualToString:@"entry"]) { Earthquake *quake = self.currentEarthquake; self.currentEarthquake = nil; [(id)[self delegate] performSelectorOnMainThread:@selector(addEarthquake:) withObject:quake waitUntilDone:NO]; }}
16Wednesday, September 30, 2009
MapQuakesVC holds the Earthquakes
- (void)addEarthquake:(Earthquake *)earthquake { [self.earthquakes addObject:earthquake];}
Store Earthquakes
17Wednesday, September 30, 2009
eventloop
EarthquakeParser
EarthquakeParserDelegate(MapQuakesViewController)
invokeOnMainThread:
parserFinished
Back to the Main Thread
Once all the XML is parsed we tell the delegate which displays the earthquakes in the form of an annotation.
Add A
nnotations
18Wednesday, September 30, 2009
Back to the Main Thread Again
Add A
nnotations
- (void)parserDidEndDocument:(NSXMLParser *)parser { [(id)[self delegate] performSelectorOnMainThread:@selector(parserFinished) withObject:nil waitUntilDone:NO]; [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; [self autorelease];}
19Wednesday, September 30, 2009
Display Earthquakes as Annotations
- (void)parserFinished { [self displayEarthquakes];}
Add A
nnotations
20Wednesday, September 30, 2009
Remove Old Annotations
- (void)displayEarthquakes { [self removeAnnotations]; NSArray *visibleQuakes = [self sortAndFilterEarthquakes]; // limit the numer to 100 if(visibleQuakes.count > 100) { // remove the earthquakes from the top of the // list until we are down to 100 NSUInteger removeCount = visibleQuakes.count - 100; NSRange keepers = {removeCount, 100}; // location and length visibleQuakes = [visibleQuakes subarrayWithRange:keepers]; } for(Earthquake *earthquake in visibleQuakes) { EarthquakeAnnotation *eqAnn = [EarthquakeAnnotation annotationWithEarthquake:earthquake]; [self.mapView addAnnotation:eqAnn]; }}
Add A
nnotations
21Wednesday, September 30, 2009
- (void)displayEarthquakes { [self removeAnnotations]; NSArray *visibleQuakes = [self sortAndFilterEarthquakes]; // limit the numer to 100 if(visibleQuakes.count > 100) { // remove the earthquakes from the top of the // list until we are down to 100 NSUInteger removeCount = visibleQuakes.count - 100; NSRange keepers = {removeCount, 100}; // location and length visibleQuakes = [visibleQuakes subarrayWithRange:keepers]; } for(Earthquake *earthquake in visibleQuakes) { EarthquakeAnnotation *eqAnn = [EarthquakeAnnotation annotationWithEarthquake:earthquake]; [self.mapView addAnnotation:eqAnn]; }}
Find Visible Quakes Sorted by Magnitude
Add A
nnotations
22Wednesday, September 30, 2009
Limit Earthquakes to 100
Add A
nnotations
- (void)displayEarthquakes { [self removeAnnotations]; NSArray *visibleQuakes = [self sortAndFilterEarthquakes]; // limit the numer to 100 if(visibleQuakes.count > 100) { // remove the earthquakes from the top of the // list until we are down to 100 NSUInteger removeCount = visibleQuakes.count - 100; NSRange keepers = {removeCount, 100}; // location and length visibleQuakes = [visibleQuakes subarrayWithRange:keepers]; } for(Earthquake *earthquake in visibleQuakes) { EarthquakeAnnotation *eqAnn = [EarthquakeAnnotation annotationWithEarthquake:earthquake]; [self.mapView addAnnotation:eqAnn]; }}
23Wednesday, September 30, 2009
- (void)displayEarthquakes { [self removeAnnotations]; NSArray *visibleQuakes = [self sortAndFilterEarthquakes]; // limit the numer to 100 if(visibleQuakes.count > 100) { // remove the earthquakes from the top of the // list until we are down to 100 NSUInteger removeCount = visibleQuakes.count - 100; NSRange keepers = {removeCount, 100}; // location and length visibleQuakes = [visibleQuakes subarrayWithRange:keepers]; } for(Earthquake *earthquake in visibleQuakes) { EarthquakeAnnotation *eqAnn = [EarthquakeAnnotation annotationWithEarthquake:earthquake]; [self.mapView addAnnotation:eqAnn]; }}
Find and Display Earthquakes
Add A
nnotations
24Wednesday, September 30, 2009
Remove old Earthquake Annotations
- (void) removeAnnotations { // remove the old annotations but // don't modify the array while iterating NSArray *annotationsCopy = [self.mapView.annotations copy]; for(id annotation in annotationsCopy) { if([[annotation class] isSubclassOfClass:[EarthquakeAnnotation class]]) { [self.mapView removeAnnotation:annotation]; } } [annotationsCopy release]; }
Add A
nnotations
25Wednesday, September 30, 2009
Filter Visible Earthquakes
- (NSArray *)sortAndFilterEarthquakes { // find the visible earthquakes MKCoordinateRegion region = [self.mapView region]; LocationBoundingBox bbox = LocationBoundingBoxMake(region.center, region.span); NSPredicate *latPred = [NSPredicate predicateWithFormat: @"latitude BETWEEN {%@, %@}", [NSNumber numberWithFloat:bbox.min.latitude], [NSNumber numberWithFloat:bbox.max.latitude]]; NSPredicate *lonPred = [NSPredicate predicateWithFormat: @"longitude BETWEEN {%@, %@}", [NSNumber numberWithFloat:bbox.min.longitude], [NSNumber numberWithFloat:bbox.max.longitude]]; NSArray *predicates = [NSArray arrayWithObjects:latPred, lonPred, nil]; NSPredicate *locationPred = [NSCompoundPredicate andPredicateWithSubpredicates:predicates]; NSArray *quakes = [self.earthquakes filteredArrayUsingPredicate:locationPred]; NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:@"magnitude" ascending:YES]; NSArray *descriptors = [NSArray arrayWithObject:descriptor]; NSArray *sortedEarthquakes = [quakes sortedArrayUsingDescriptors:descriptors]; [descriptor release]; return sortedEarthquakes;}
Add A
nnotations
26Wednesday, September 30, 2009
Sort Earthquakes- (NSArray *)sortAndFilterEarthquakes { // find the visible earthquakes MKCoordinateRegion region = [self.mapView region]; LocationBoundingBox bbox = LocationBoundingBoxMake(region.center, region.span); NSPredicate *latPred = [NSPredicate predicateWithFormat: @"latitude BETWEEN {%@, %@}", [NSNumber numberWithFloat:bbox.min.latitude], [NSNumber numberWithFloat:bbox.max.latitude]]; NSPredicate *lonPred = [NSPredicate predicateWithFormat: @"longitude BETWEEN {%@, %@}", [NSNumber numberWithFloat:bbox.min.longitude], [NSNumber numberWithFloat:bbox.max.longitude]]; NSArray *predicates = [NSArray arrayWithObjects:latPred, lonPred, nil]; NSPredicate *locationPred = [NSCompoundPredicate andPredicateWithSubpredicates:predicates]; NSArray *quakes = [self.earthquakes filteredArrayUsingPredicate:locationPred]; NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:@"magnitude" ascending:YES]; NSArray *descriptors = [NSArray arrayWithObject:descriptor]; NSArray *sortedEarthquakes = [quakes sortedArrayUsingDescriptors:descriptors]; [descriptor release]; return sortedEarthquakes;}
Add A
nnotations
27Wednesday, September 30, 2009
MKMapViewDelegate(MapQuakesViewController)
EarthquakeParserDelegate(MapQuakesViewController)
addAnnotation:
viewForAnnotation:
delegate Provides Annotation View
Provide Annotation V
iews
28Wednesday, September 30, 2009
Return nil for user location
Provide Annotation V
iews
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation { MKAnnotationView *view = nil; if(annotation != mapView.userLocation) {!! EarthquakeAnnotation *eqAnn = (EarthquakeAnnotation*)annotation;!! view = [self.mapView dequeueReusableAnnotationViewWithIdentifier:@"earthquakeLoc"];!! if(nil == view) { view = [[[EarthquakeAnnotationView alloc] initWithAnnotation:eqAnn reuseIdentifier:@"earthquakeLoc"] autorelease];!! } view.annotation = annotation; [view setCanShowCallout:YES]; UIButton *button = [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; [view setRightCalloutAccessoryView:button]; } return view;}
29Wednesday, September 30, 2009
Reuse Annotation ViewsProvide A
nnotation View
s
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation { MKAnnotationView *view = nil; if(annotation != mapView.userLocation) {!! EarthquakeAnnotation *eqAnn = (EarthquakeAnnotation*)annotation;!! view = [self.mapView dequeueReusableAnnotationViewWithIdentifier:@"earthquakeLoc"];!! if(nil == view) { view = [[[EarthquakeAnnotationView alloc] initWithAnnotation:eqAnn reuseIdentifier:@"earthquakeLoc"] autorelease];!! } view.annotation = annotation; [view setCanShowCallout:YES]; UIButton *button = [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; [view setRightCalloutAccessoryView:button]; } return view;}
30Wednesday, September 30, 2009
Create if no re-useable views exist
Provide Annotation V
iews
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation { MKAnnotationView *view = nil; if(annotation != mapView.userLocation) {!! EarthquakeAnnotation *eqAnn = (EarthquakeAnnotation*)annotation;!! view = [self.mapView dequeueReusableAnnotationViewWithIdentifier:@"earthquakeLoc"];!! if(nil == view) { view = [[[EarthquakeAnnotationView alloc] initWithAnnotation:eqAnn reuseIdentifier:@"earthquakeLoc"] autorelease];!! } view.annotation = annotation; [view setCanShowCallout:YES]; UIButton *button = [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; [view setRightCalloutAccessoryView:button]; } return view;}
31Wednesday, September 30, 2009
Configure ViewProvide A
nnotation View
s
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation { MKAnnotationView *view = nil; if(annotation != mapView.userLocation) {!! EarthquakeAnnotation *eqAnn = (EarthquakeAnnotation*)annotation;!! view = [self.mapView dequeueReusableAnnotationViewWithIdentifier:@"earthquakeLoc"];!! if(nil == view) { view = [[[EarthquakeAnnotationView alloc] initWithAnnotation:eqAnn reuseIdentifier:@"earthquakeLoc"] autorelease];!! } view.annotation = annotation; [view setCanShowCallout:YES]; UIButton *button = [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; [view setRightCalloutAccessoryView:button]; } return view;}
32Wednesday, September 30, 2009
Provide Annotation V
iews
Custom MKAnnotationViewSubclass
EarthquakeAnnotationView
33Wednesday, September 30, 2009
Configure the Annotation View
Provide Annotation V
iews
- (void)setAnnotation:annotation { [super setAnnotation:annotation]; [self.layer.sublayers makeObjectsPerformSelector: @selector(removeFromSuperlayer)]; self.frame = CGRectMake(0.0f, 0.0f, 48.0f, 48.0f); self.earthquake = [(EarthquakeAnnotation *)annotation earthquake]; [self addBreathingLayer]; [self addDarkCircleLayer]; }
34Wednesday, September 30, 2009
Shape LayerProvide A
nnotation View
s
- (void) addBreathingLayer { self.circleLayer = [CAShapeLayer layer]; CGColorRef color = [self newFillColor]; self.circleLayer.fillColor = color; CGColorRelease(color); color = [self newStrokeColor]; self.circleLayer.strokeColor = color; CGColorRelease(color); self.circleLayer.lineWidth = 1.0f; CGMutablePathRef path = CGPathCreateMutable(); CGRect square = CGRectMake(0.0f, 0.0f, 48.0f, 48.0f); CGPathAddEllipseInRect(path, NULL, square); self.circleLayer.path = path; CGPathRelease(path); self.circleLayer.frame = square; CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; pathAnimation.duration = 1.5f; pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; pathAnimation.repeatCount = 1E100f; pathAnimation.autoreverses = YES; self.circleLayer.actions = [NSDictionary dictionaryWithObject:pathAnimation forKey:@"path"]; [self.layer addSublayer:self.circleLayer];}
35Wednesday, September 30, 2009
Breathing AnimationProvide A
nnotation View
s
- (void) addBreathingLayer { self.circleLayer = [CAShapeLayer layer]; CGColorRef color = [self newFillColor]; self.circleLayer.fillColor = color; CGColorRelease(color); color = [self newStrokeColor]; self.circleLayer.strokeColor = color; CGColorRelease(color); self.circleLayer.lineWidth = 1.0f; CGMutablePathRef path = CGPathCreateMutable(); CGRect square = CGRectMake(0.0f, 0.0f, 48.0f, 48.0f); CGPathAddEllipseInRect(path, NULL, square); self.circleLayer.path = path; CGPathRelease(path); self.circleLayer.frame = square; CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; pathAnimation.duration = 1.5f; pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; pathAnimation.repeatCount = 1E100f; pathAnimation.autoreverses = YES; self.circleLayer.actions = [NSDictionary dictionaryWithObject:pathAnimation forKey:@"path"]; [self.layer addSublayer:self.circleLayer];}
36Wednesday, September 30, 2009
Selection Starts Animation
Provide Annotation V
iews
- (void)setSelected:(BOOL)selected animated:(BOOL)animated { [super setSelected:selected animated:animated]; if(YES == selected) { // animate the bottom shape's path CGRect square = CGRectMake(16.0f, 16.0f, 16.0f, 16.0f); CGMutablePathRef path = CGPathCreateMutable(); CGPathAddEllipseInRect(path, NULL, square); self.circleLayer.path = path; CGPathRelease(path); EarthquakeParser *parser = [EarthquakeParser earthquakeParser]; parser.delegate = self; [parser getShakeMapForEarthquake:self.earthquake]; } else { [CATransaction begin]; [CATransaction setDisableActions:YES]; CGMutablePathRef path = CGPathCreateMutable(); CGRect square = CGRectMake(0.0f, 0.0f, 48.0f, 48.0f); CGPathAddEllipseInRect(path, NULL, square); self.circleLayer.path = path; CGPathRelease(path); [CATransaction commit]; }}
37Wednesday, September 30, 2009
De-Selection Stops Animation
Provide Annotation V
iews
- (void)setSelected:(BOOL)selected animated:(BOOL)animated { [super setSelected:selected animated:animated]; if(YES == selected) { // animate the bottom shape's path CGRect square = CGRectMake(16.0f, 16.0f, 16.0f, 16.0f); CGMutablePathRef path = CGPathCreateMutable(); CGPathAddEllipseInRect(path, NULL, square); self.circleLayer.path = path; CGPathRelease(path); EarthquakeParser *parser = [EarthquakeParser earthquakeParser]; parser.delegate = self; [parser getShakeMapForEarthquake:self.earthquake]; } else { [CATransaction begin]; [CATransaction setDisableActions:YES]; CGMutablePathRef path = CGPathCreateMutable(); CGRect square = CGRectMake(0.0f, 0.0f, 48.0f, 48.0f); CGPathAddEllipseInRect(path, NULL, square); self.circleLayer.path = path; CGPathRelease(path); [CATransaction commit]; }}
38Wednesday, September 30, 2009
Selected AnimationProvide Annotation V
iews
39Wednesday, September 30, 2009
Respond To Events
‘Right’ Detail
‘Left’ Shake Map
40Wednesday, September 30, 2009
Open The Web PageR
espond To Events
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control { NSURL *url = nil; EarthquakeAnnotation *eqAnn = (EarthquakeAnnotation *)[view annotation]; if(view.rightCalloutAccessoryView == control) { NSString *urlString = [eqAnn.earthquake.detailsURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; url = [NSURL URLWithString:urlString]; } else { NSString *urlString = [eqAnn.earthquake.shakeMapURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; url = [NSURL URLWithString:urlString]; } [[UIApplication sharedApplication] openURL:url];}
41Wednesday, September 30, 2009
Where To Now?
• Your data will likely have more interesting selection criteria, exploit it
• The ‘detail’ from right and left buttons can do lots more interesting stuff• USGS Provides KML files for shake
maps• Use Core Animation in the right or left
buttons
42Wednesday, September 30, 2009
Summary
• Filtering your Annotations is important if you have lots of data
• It’s easy to build your own custom annotation views and add Core Animation to them
43Wednesday, September 30, 2009
Thanks!
Pragmatic iPhone Studio
44Wednesday, September 30, 2009