By Rupert
Posts tagged iphone
iPhone Bug Note #1: Route-Me and Three20 in the same Project. _aasin dup symbol when building on iOS4
Aug 14th
It seems route-me and three20 doesn’t mix well on iOS4. When building my project (Philippines), I get an error on _aasin duplicate symbol, which was also discussed on route-me issue 138 at code.google.com
Three20 needs linker flag shown below:
-all_load
The only way to go about this was linking proj4 directly from the project and removing it from route-me, thus no need to remove the “all_load” linker flag. Note: I tried removing the “all_load” linker flag from my project and it builds fine. However, the app crashes.
1. Drag the proj4.xcodeproj from RouteMe to your main project.
2. Afterwards delete the proj4.xcodeproj from RouteMe.

3. Your project should now reference both MapView and Proj4.

iPhone Dev Note #21: Route-Me Offline Mapping from Database
Aug 12th
Part I: Download the osm (openstreetmap) tiles
1. Download the tiles from osm using downloadosmtiles.pl
- Download Geo-OSM-Tiles-0.02.tar.gz from CPAN
- See README file. Compile and build
perl Makefile.PL make make test make install
- Copy downloadosmtiles.pl to /usr/bin
- Usage:
downloadosmtiles.pl --lat=min_lat:max_lat --lon=min_long:max_long --zoom=min_zoom:max_zoom
How do you set the min_lat, max_lat and min_long, max_long?
- Go to www.openstreetmap.org
- Click on the “Edit” tab
- You will see the extent or bounds of the map under the section “Area to Export”
- Alternatively, you can click on “Manually select a different area” to specifically choose an area.

downloadosmtiles.pl --lat=6.9443:7.2261 --lon=125.5082:125.7104 --zoom=6:12
The tiles will be downloaded to the current directory.

Part II:Put the tiles in the sqlite database
1. Read Frank’s email to route-me group regarding map2sqlite
2. Download map2sqlite-1.0.tar.bz2.
3. Build the map2sqlite XCodeProj. Afterwards, find map2sqlite and drop it in /usr/bin.
cp map2sqlite /usr/bin
4. Run map2sqlite to import the tiles in sqlite.
map2sqlite -db ph-1.0.0.db -mapdir ph-osm-map/ 2010-08-12 17:24:40.749 map2sqlite[14113:903] map2sqlite 1.0 2010-08-12 17:24:40.756 map2sqlite[14113:903] Creating ph-1.0.0.db 2010-08-12 17:24:40.761 map2sqlite[14113:903] Importing map tiles at ph-osm-map/ 2010-08-12 17:25:03.169 map2sqlite[14113:903] 2010-08-12 17:25:03.170 map2sqlite[14113:903] Map statistics 2010-08-12 17:25:03.170 map2sqlite[14113:903] -------------- 2010-08-12 17:25:03.171 map2sqlite[14113:903] map db: ph-1.0.0.db 2010-08-12 17:25:03.171 map2sqlite[14113:903] file size: 13758464 bytes 2010-08-12 17:25:03.172 map2sqlite[14113:903] tile directory: ph-osm-map/ 2010-08-12 17:25:03.172 map2sqlite[14113:903] number of tiles: 9091 2010-08-12 17:25:03.173 map2sqlite[14113:903] zoom levels: 6 - 11 2010-08-12 17:25:03.218 map2sqlite[14113:903] zoom level 6: 12 tiles, ( 28, 52)x( 31, 54), {x=112.500000,y=21.943047}x{x=129.375000,y=0.000000} 2010-08-12 17:25:03.219 map2sqlite[14113:903] zoom level 7: 35 tiles, ( 56, 105)x( 62, 109), {x=115.312500,y=21.943047}x{x=129.375000,y=2.811371} 2010-08-12 17:25:03.222 map2sqlite[14113:903] zoom level 8: 117 tiles, ( 112, 210)x( 124, 218), {x=115.312500,y=21.943047}x{x=127.968750,y=4.214943} 2010-08-12 17:25:03.223 map2sqlite[14113:903] zoom level 9: 450 tiles, ( 225, 420)x( 249, 437), {x=115.312500,y=21.289375}x{x=127.968750,y=4.214943} 2010-08-12 17:25:03.225 map2sqlite[14113:903] zoom level 10: 1715 tiles, ( 450, 841)x( 498, 875), {x=115.664062,y=21.289375}x{x=127.968750,y=4.565474} 2010-08-12 17:25:03.231 map2sqlite[14113:903] zoom level 11: 6762 tiles, ( 900, 1683)x( 997, 1751), {x=115.839844,y=21.289375}x{x=127.968750,y=4.565474}
5. ph-osm-map is 43.9 MB but was compressed to ph-1.0.0.db (13.8 MB)
Part III: Downlaod the route-me code from trunk and run some examples.
- Follow this previous tutorial
Part IV: Patch the trunk to incorporate the RMDBMapSource from Frank Schroder
1. What we need to add to the trunk. Download RMDBMapSource.zip
+ RMDBMapSource.h + RMDBMapSource.m + RMDBTileImage.h + RMDBTileImage.m
Copy the files above to the “Map” directory.

2. Edit RMTileImage.h and RMTileImage.m base on the patch below.
Index: MapView/Map/RMTileImage.h =================================================================== --- MapView/Map/RMTileImage.h (revision 605) +++ MapView/Map/RMTileImage.h (working copy) @@ -37,8 +37,10 @@ #import "RMNotifications.h" #import "RMTile.h" #import "RMTileProxy.h" +#import "FMDatabase.h" @class RMTileImage; +@class NSData; @interface RMTileImage : NSObject { // I know this is a bit nasty. @@ -64,6 +66,7 @@ + (RMTileImage*)imageForTile: (RMTile) tile withURL: (NSString*)url; + (RMTileImage*)imageForTile: (RMTile) tile fromFile: (NSString*)filename; + (RMTileImage*)imageForTile: (RMTile) tile withData: (NSData*)data; ++ (RMTileImage*)imageForTile: (RMTile) tile fromDB: (FMDatabase*)db; - (void)moveBy: (CGSize) delta; - (void)zoomByFactor: (float) zoomFactor near:(CGPoint) center; Index: MapView/Map/RMTileImage.m =================================================================== --- MapView/Map/RMTileImage.m (revision 605) +++ MapView/Map/RMTileImage.m (working copy) @@ -29,6 +29,7 @@ #import "RMWebTileImage.h" #import "RMTileLoader.h" #import "RMFileTileImage.h" +#import "RMDBTileImage.h" #import "RMTileCache.h" #import "RMPixel.h" #import <QuartzCore/QuartzCore.h> @@ -108,6 +109,11 @@ return [image autorelease]; } ++ (RMTileImage*)imageForTile:(RMTile) _tile fromDB: (FMDatabase*)db +{ + return [[[RMDBTileImage alloc] initWithTile: _tile fromDB:db] autorelease]; +} + -(void) cancelLoading { [[NSNotificationCenter defaultCenter] postNotificationName:RMMapImageLoadingCancelledNotification
We just need to add this line on RMTileImage.h:
+#import "FMDatabase.h" ... ++ (RMTileImage*)imageForTile: (RMTile) tile fromDB: (FMDatabase*)db;
do the same for RMTileImage.m:
+#import "RMDBTileImage.h" ... ++ (RMTileImage*)imageForTile:(RMTile) _tile fromDB: (FMDatabase*)db +{ + return [[[RMDBTileImage alloc] initWithTile: _tile fromDB:db] autorelease]; +}
4. Still with me? Comment NSAssert on 609 on RMMapContents.m

Part V: RouteMeSampleMapDBOffline code
1. Download RouteMeSampleMapDBOffline.zip
2. Drop the project in the samples directory.

3. Build. You should be able to build this since the header path is relative to the route-me trunk.

4. Run from the simulator

What I’ve learned during the past two weeks (July 26 – Aug 8) in iOS Development?
Aug 9th
1. WWDC 2010 Sample Code. I’ve totally missed the download link from iTunes so I’m listing it here just in case. Note: You need to be registered in the iPhone Developer Program. Check out the Video Access List to know which videos are available for each developer program.
2. AdvancedTableViewCells – sample code provided by WWDC 2010 which explains several ways how to subclass a UITableViewCell.
- iPhone Development Foundation Videos: “Session 432 – Mastering iPhone Table Views”
- WWDC 2010 Session Video: “Session 128 – Mastering Table Views”
- IndividualSubviewsBasedApplicaionCell uses a sib
- CompositeSubviewBasedApplicationCell uses a custom view to drall all UI in drawRect
- HybridSubviewBasedApplicationCell = CompositeSubviewBasedApplicationCell + custom UI elements during init.
3. Play YouTube videos without exiting the application.
- Uses a webview to embed a youtube link.
- Doesn’t work on the simulator. Need to test on actual device
4. Black based Animations
- WWDC 2010 Session Videos HD: “Session 128 – Building Animation Driven Interfaces”
- It’s better to use block based animations than old style UIViewAnimations. Look at code below, after animations on the first block completes, displayContentOnPoiBoard executes..
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationCurveEaseOut animations:^{
viewPoiBoard.frame = newFrame;
viewPoiBoard.alpha = 0.8;
buttonMapNearby.alpha = 0;
buttonMapDropPin.alpha = 0;
segmentedControlMapType.alpha = 0;
}completion:^(BOOL finished){
[self displayContentOnPoiBoard:currentPoi];
}];5. Beta Testing using AdHoc Distribution
- WWDC 2010 Session Video: “Session 310 – App Publishing with iTunes Connect”
- iTunes Connect Program User Guide
Procedure:
1. From the Provisioning Portal, Add the devices
2. Create an AdHoc Distribution Provisioning Profile
3. Use Adhoc.mobileprovision in XCode under codesigning
4. Add an Entitlements.plist
5. Build
6. Try “Build and Archive”
- this is new in XCode, under “Archived Applications”, which is actually cool as you can immediately save the ipa file under “Share Application…”

7. Before I have to give out the Adhoc.mobileprovision as well, but now the ipa file is already sufficient.
6. iTunes Connect “New Version”
- Important Information about Submitting Your Apps to iTunes Connect
- Provide a version number and add metadata information e.g (“What’s New in this Version?). Once finished with all the metadata changes, click on “Ready to Upload Binary” and the App Status changes to “Waiting For Upload”.
- Upload the binary thru XCode, see next step.
7. Appstore Distribution Binary Submission
- Follow the instructions on Beta Testing (5) but instead use an Appstore Distribution Provisioning Profile instead.
- Trying “Build and Archive”, we can now “Validate Application” which validates the binary after providing your iTunes Connect username, password.
- “Submit Application to iTunes Connect” uploads the binary and changes the status to “Upload Received” then “Waiting For Review”.
8. Control when your app goes live
You Can Now Control When Your App Update Goes Live
- Pretty nifty stuff as you can now control when a version goes live, I notice there is a new “Release Control” which states “Hold for Developer Release”.
iPhone Note #14: Drawing a Point, Line, Polygon on top of MKMapview
Aug 9th
UPDATE: Aug 9, 2010
DrawMap2.zip
Note: This does not contain the new MapKit functions for overlaying lines and polygons. This zip was created to compile against 4.0.0 but still have the same codebase.
============================
This is an update to iPhone DevNote #13. This post has solved my zooming/panning problem with a CustomView on top of my MKMapView courtesy of http://spitzkoff.com/craig/?p=108 (Craig’s blog).
The trick here is instead of doing the drawing on the drawRect method of the CustomView, we will use Craig’s methodology to use the drawRect method of a custom MKAnnotationView. Note, that he also used an internal view and made clipsToBounds = NO, this way we can draw the whole geometry on top of MKMapView not just a portion of it. The end result is the shape (polygon in this example) is below the added pins.

@interface LinePolygonAnnotationInternalView : UIView { // line view which added this as a subview. LinePolygonAnnotationView* _mainView; } @property (nonatomic, retain) LinePolygonAnnotationView* mainView; @end @implementation LinePolygonAnnotationInternalView @synthesize mainView = _mainView; -(id) init { self = [super init]; self.backgroundColor = [UIColor clearColor]; self.clipsToBounds = NO; return self; } -(void) drawRect:(CGRect) rect { GeometryAnnotation* myAnnotation = (GeometryAnnotation*)self.mainView.annotation; // only draw our lines if we're not int he moddie of a transition and we // acutally have some points to draw. if(!self.hidden && nil != myAnnotation.points && myAnnotation.points.count > 0) { CGContextRef context = UIGraphicsGetCurrentContext(); // Drawing lines with a white stroke color CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0); // Draw them with a 2.0 stroke width so they are a bit more visible. CGContextSetLineWidth(context, 2.0); if(myAnnotation.geometryType == kGeometryTypePolygon){ CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0); } // Draw them with a 2.0 stroke width so they are a bit more visible. CGContextSetLineWidth(context, 2.0); for(int idx = 0; idx < myAnnotation.points.count; idx++) { CLLocation* location = [myAnnotation.points objectAtIndex:idx]; CGPoint point = [self.mainView.mapView convertCoordinate:location.coordinate toPointToView:self]; NSLog(@"Point: %lf, %lf", point.x, point.y); if(idx == 0) { // move to the first point CGContextMoveToPoint(context, point.x, point.y); } else { CGContextAddLineToPoint(context, point.x, point.y); } } if(myAnnotation.geometryType == kGeometryTypeLine){ CGContextStrokePath(context); } else if(myAnnotation.geometryType == kGeometryTypePolygon){ CGContextClosePath(context); CGContextDrawPath(context, kCGPathFillStroke); } } } -(void) dealloc { self.mainView = nil; [super dealloc]; } @end @implementation LinePolygonAnnotationView @synthesize mapView = _mapView; - (id)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.backgroundColor = [UIColor clearColor]; // do not clip the bounds. We need the LinePolygonAnnotationInternalView to be able to render the whole line/polygon, regardless of where the // actual annotation view is displayed. self.clipsToBounds = NO; // create the internal line view that does the rendering of the line. _internalView = [[LinePolygonAnnotationInternalView alloc] init]; _internalView.mainView = self; [self addSubview:_internalView]; } return self; } -(void) setMapView:(MKMapView*) mapView { [_mapView release]; _mapView = [mapView retain]; [self regionChanged]; } -(void) regionChanged { NSLog(@"Region Changed"); // move the internal line view. 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); [_internalView setNeedsDisplay]; } - (void)dealloc { [_mapView release]; [_internalView release]; [super dealloc]; } @end
I extended the class above to be able to draw both lines and polygons by checking a property (geometryType) of the GeometryAnnotation. If the geometryType is a line, then just stroke the path. However, if the geometryType is a polygon, then close the path and fill it.
if(myAnnotation.geometryType == kGeometryTypeLine){ CGContextStrokePath(context); } else if(myAnnotation.geometryType == kGeometryTypePolygon){ CGContextClosePath(context); CGContextDrawPath(context, kCGPathFillStroke); }
And here is the GeometryAnnotation class. Most of the code is from Craig, i just added the geometryType property:
// Created by Craig on 8/18/09. // Copyright Craig Spitzkoff 2009. All rights reserved. // #import "GeometryAnnotation.h" @implementation GeometryAnnotation @synthesize coordinate = _center; @synthesize points = _points; @synthesize annotationID; @synthesize geometryType; -(id) initWithPoints:(NSArray*) points withGeometry:(GeometryType)geomType { self = [super init]; geometryType = geomType; _points = [[NSMutableArray alloc] initWithArray:points]; // create a unique ID for this line so it can be added to dictionaries by this key. self.annotationID = [NSString stringWithFormat:@"%p", self]; // determine a logical center point for this line based on the middle of the lat/lon extents. double maxLat = -91; double minLat = 91; double maxLon = -181; double minLon = 181; for(CLLocation* currentLocation in _points) { CLLocationCoordinate2D coordinate = currentLocation.coordinate; if(coordinate.latitude > maxLat) maxLat = coordinate.latitude; if(coordinate.latitude < minLat) minLat = coordinate.latitude; if(coordinate.longitude > maxLon) maxLon = coordinate.longitude; if(coordinate.longitude < minLon) minLon = coordinate.longitude; } _span.latitudeDelta = (maxLat + 90) - (minLat + 90); _span.longitudeDelta = (maxLon + 180) - (minLon + 180); // the center point is the average of the max and mins _center.latitude = minLat + _span.latitudeDelta / 2; _center.longitude = minLon + _span.longitudeDelta / 2; NSLog(@"Found center of new Annotation at %lf, %ld", _center.latitude, _center.longitude); return self; } -(MKCoordinateRegion) region { MKCoordinateRegion region; region.center = _center; region.span = _span; return region; } -(void) dealloc { [_points release]; [super dealloc]; } @end
Now that we have a way to draw a line/polygon as a custom MKAnnotationView, we need a custom TouchView (GeometryTouchView) which could accept the touch events.
For example, if the user wants to draw a line geometry, the GeometryTouchView would accept touch events from the user and add a point as a PointAnnotation in the Map. Succeeding points would be added to an array. For every point added, the MKAnnotationView drawRects method connects the points to produce a line. The MKAnnotationView is now added to the map.
Once the geometry is added as an annotation, the custom TouchView is hidden. This way we have access (panning/zooming) to the mapview. If we make a pan or a zoom, the region changes, thus we need to redraw the shape of the annotation again.
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated { if(currentAnnotationView != nil){ NSLog(@"regionWillChangeAnimated"); currentAnnotationView.hidden = YES; } } - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated { if(currentAnnotationView != nil){ NSLog(@"regionDidChangeAnimated"); currentAnnotationView.hidden = NO; [currentAnnotationView regionChanged]; } }
-(void) regionChanged { NSLog(@"Region Changed"); // move the internal line view. 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); [_internalView setNeedsDisplay]; }
The resulting image is now:

(Download the DrawMap.zip code.) – old. This is for iOS < 4
iPhone Cross Application Launch/Marketing Strategy
Jul 15th
As many developers keep on asking me how I reached the rankings I have now for MyTravelPhilippines, I would like to share the marketing/launch strategy that we did. The strategy is not new. I’ve read from numerous iphone developer blogs who have a few apps in the store about cross app promotion. But how do you really do it?
On Dec 2009, I have released the “Philippines 2010 Election Survey”. The red dot is on May 11 which is the actual elections for the Philippines. However after this event, you can see the downloads went down.. well because the elections are over…

When the election results came out from the government, I immediately made the app, “Philippines 2010 Election Results”. I have launched this on June 26. I was hoping that most users would be eager to see the results and most did update to the new app. Within the updated app, I made a link to download “MyTravelPhilippines” in the appstore.

The election app has a total of 4k users where 25% updated. Consequently, the “MyTravelPhilippines” eyeball did do the trick and on June 26 we had 849 downloads.

The 849 downloads on June 26 was significant as it was able to push “MyTravelPhilippines” to rank #1(Free Philippines Travel Category) and rank #9 (Free Philippines Overall).

In just two weeks, I was able to hit 5k mark for MyTravelPhilippines which took me 6 months to do in the Philippines Elections app.
What I’ve learned?
- Use an event, in this case the election results, to get most out of the updates.
- Cross app promotion works. It’s like migrating your users from one app to another.
- Application Visibility is very important. Try hard to make it to the Top 10 in that category. (Yes, I know its easier said that done.. but really aim for this..)
What I’ve missed?
Application updates to keep the app alive. This would help in “the download and never open again or open occasionally syndrome”. At the time of this writing, I am currently trying to do this as I could see the graph is slowly trending down. What I should have anticipated is to keep at least one version higher (1.1) and uploading the older version(1.0) on the store. The timing of this updates could be periodical–after 3 weeks, a month or an event. But with the limited resources that we had, only a two man team, and no marketing $$$ expense at all in ads, it was a great experience.
Comments