360iDev MapKit Presentation - Denver 2009
Upcoming SlideShare
Loading in...5
×

Like this? Share it with your network

Share

360iDev MapKit Presentation - Denver 2009

  • 12,896 views
Uploaded on

The Presentation I gave at 360iDev in Denver 2009

The Presentation I gave at 360iDev in Denver 2009

More in: Education
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
12,896
On Slideshare
5,812
From Embeds
7,084
Number of Embeds
12

Actions

Shares
Downloads
113
Comments
0
Likes
3

Embeds 7,084

http://bill.dudney.net 7,000
http://translate.googleusercontent.com 32
http://www.slideshare.net 29
http://harindersinghbasra.blogspot.com 7
http://www.iphonepundits.com 4
http://theoldreader.com 4
http://webcache.googleusercontent.com 3
http://www.hanrss.com 1
http://cc.bingj.com 1
http://www.feedage.com 1
http://www.google.com 1
http://dashboard.bloglines.com 1

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Maps on the iPhone Using MapKit for Fun and Profit Wednesday, September 30, 2009 1
  • 2. Add A MKMapView Wednesday, September 30, 2009 2
  • 3. Tweak Parameters Show the blue breathing dot Then Build and Go... Wednesday, September 30, 2009 3
  • 4. And Bada-Boom A Map App Wednesday, September 30, 2009 4
  • 5. But we want more, like the breathing Blue Dot Wednesday, September 30, 2009 5
  • 6. Where we are Headed ‘Right’ Action - Details ‘Left’ Action - Shake Map Animated ‘Breathing’ Wednesday, September 30, 2009 6
  • 7. Stuff To Do • View Based Project • Parse XML from USGS • Create Earthquakes • Store Earthquakes • Add Annotations • Provide Annotation Views • Respond to Events Wednesday, September 30, 2009 7
  • 8. View Based Project Connect the MapView to our VC Wednesday, September 30, 2009 8
  • 9. Parse XML NSOperationQueue operation NSOperation thread parseForData: addOperation: Parse XML EarthquakeParser initWithContentsOfURL: parse invokeOnMainThread: didStartElement: foundCharcters: didEndElement: NSXMLParser event loop addEarthquake: parserFinished EarthquakeParserDelegate (MapQuakesViewController) Don’t block the main event thread! Wednesday, September 30, 2009 9
  • 10. NSOperationQueue operation NSOperation thread Parse XML parseForData: addOperation: EarthquakeParser Kick Off The Parse parseForData: is on an alternate thread, won’t block event thread while downloading the XML Wednesday, September 30, 2009 10
  • 11. EarthquakeParser initWithContentsOfURL: Parse XML parse NSXMLParser Parse XML on the Each XML Event comes to the parser’s delegate on the alternate thread alternate thread Wednesday, September 30, 2009 11
  • 12. NSXMLParser Kick-off - (BOOL)parseForData { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; NSURL *url = [NSURL URLWithString:feedURLString]; Parse XML 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; } Wednesday, September 30, 2009 12
  • 13. Create Earthquakes EarthquakeParser didStartElement: foundCharcters: didEndElement: NSXMLParser Parse XML on the Each XML Event comes to the parser’s delegate on the alternate thread alternate thread Wednesday, September 30, 2009 13
  • 14. Entry Element starts and Earthquake - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName Create Earthquakes 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; } } Wednesday, September 30, 2009 14
  • 15. EarthquakeParser Back to the Main Thread Store Earthquakes invokeOnMainThread: event loop addEarthquake: EarthquakeParserDelegate Let the main thread know an earthquake was (MapQuakesViewController) found by pushing the addEarthquake: and parserFinished methods onto the main thread Wednesday, September 30, 2009 15
  • 16. 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]; } } Wednesday, September 30, 2009 16
  • 17. MapQuakesVC holds the Earthquakes Store Earthquakes - (void)addEarthquake:(Earthquake *)earthquake { [self.earthquakes addObject:earthquake]; } Wednesday, September 30, 2009 17
  • 18. EarthquakeParser Back to the Main Thread Add Annotations invokeOnMainThread: event loop parserFinished EarthquakeParserDelegate Once all the XML is parsed we tell the delegate (MapQuakesViewController) which displays the earthquakes in the form of an annotation. Wednesday, September 30, 2009 18
  • 19. Back to the Main Thread Again Add Annotations - (void)parserDidEndDocument:(NSXMLParser *)parser { [(id)[self delegate] performSelectorOnMainThread:@selector(parserFinished) withObject:nil waitUntilDone:NO]; [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; [self autorelease]; } Wednesday, September 30, 2009 19
  • 20. Display Earthquakes as Annotations Add Annotations - (void)parserFinished { [self displayEarthquakes]; } Wednesday, September 30, 2009 20
  • 21. Remove Old Annotations - (void)displayEarthquakes { Add Annotations [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]; } } Wednesday, September 30, 2009 21
  • 22. Find Visible Quakes Sorted by Magnitude - (void)displayEarthquakes { Add Annotations [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]; } } Wednesday, September 30, 2009 22
  • 23. Limit Earthquakes to 100 - (void)displayEarthquakes { Add Annotations [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]; } } Wednesday, September 30, 2009 23
  • 24. Find and Display Earthquakes - (void)displayEarthquakes { Add Annotations [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]; } } Wednesday, September 30, 2009 24
  • 25. Remove old Earthquake Annotations Add 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]; } Wednesday, September 30, 2009 25
  • 26. Filter Visible Earthquakes - (NSArray *)sortAndFilterEarthquakes { Add Annotations // 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; } Wednesday, September 30, 2009 26
  • 27. Sort Earthquakes - (NSArray *)sortAndFilterEarthquakes { Add Annotations // 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; } Wednesday, September 30, 2009 27
  • 28. Provide Annotation Views EarthquakeParserDelegate (MapQuakesViewController) addAnnotation: viewForAnnotation: MKMapViewDelegate (MapQuakesViewController) delegate Provides Annotation View Wednesday, September 30, 2009 28
  • 29. Return nil for user Provide Annotation Views location - (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; } Wednesday, September 30, 2009 29
  • 30. Provide Annotation Views Reuse Annotation Views - (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; } Wednesday, September 30, 2009 30
  • 31. Create if no re-useable Provide Annotation Views views exist - (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; } Wednesday, September 30, 2009 31
  • 32. Provide Annotation Views Configure View - (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; } Wednesday, September 30, 2009 32
  • 33. Custom Provide Annotation Views MKAnnotationView Subclass EarthquakeAnnotationView Wednesday, September 30, 2009 33
  • 34. Configure the Provide Annotation Views Annotation View - (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]; } Wednesday, September 30, 2009 34
  • 35. Provide Annotation Views Shape Layer - (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]; } Wednesday, September 30, 2009 35
  • 36. Provide Annotation Views Breathing Animation - (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]; } Wednesday, September 30, 2009 36
  • 37. Selection Starts Provide Annotation Views Animation - (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]; } } Wednesday, September 30, 2009 37
  • 38. De-Selection Stops Provide Annotation Views Animation - (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]; } } Wednesday, September 30, 2009 38
  • 39. Selected Animation Provide Annotation Views Wednesday, September 30, 2009 39
  • 40. ‘Right’ Detail Respond To Events ‘Left’ Shake Map Wednesday, September 30, 2009 40
  • 41. Open The Web Page Respond 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]; } Wednesday, September 30, 2009 41
  • 42. 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 Wednesday, September 30, 2009 42
  • 43. 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 Wednesday, September 30, 2009 43
  • 44. Thanks! Pragmatic iPhone Studio Wednesday, September 30, 2009 44