Skip to content

Const CGRect in Objective C

File this under something I just learned from one of our vendors; you can declare const structs like a CGRect in your implementation like this:

const CGRect kPlaceholderFrame1 = {
    .origin.x = 126.0f,
    .origin.y = 22.0f,
    .size.width = 180.0f,
    .size.height = 100.0f
};

OSX utility to convert a plist to json

Here’s a new utility  for OSX Lion I threw together to convert a plist file to a JSON file.

It takes a single argument, which is an absolute or relative path to a plist file. The output of this application will be a JSON file written in the same directory of the plist.

Grab the source from github, or download the binary here
Its an extremely simple tool; it basically relies on NSJSONSerialzation to generate the JSON, and NSDictionary native support for plists to read in the original data. 2 lines of code to do the actual conversion, plus file writing and error handling.
Here’s the entire application’s code:

 

int main (int argc, const char * argv[])
{

    @autoreleasepool {

        if(argc < 2)
        {
            printf("Not enough arguments; missing plist file.\n");
            return 1;
        }

        NSFileManager *filemgr;
        NSString *currentpath;

        filemgr = [[NSFileManager alloc] init];

        currentpath = [filemgr currentDirectoryPath];

        // try to find the full path to the spcified file.
        NSString* path = [NSString stringWithCString:argv[1] encoding:NSUTF8StringEncoding];
        if(![filemgr fileExistsAtPath:path])
        {
            // path was not absolute or did not exist. Try looking in the current path. 
            path = [currentpath stringByAppendingPathComponent:path];

            if(![filemgr fileExistsAtPath:path])
            {
                printf("Could not find file %s\n", argv[1]);
                return 1;
            }
        }

        NSDictionary* dictionary = [NSDictionary dictionaryWithContentsOfFile:path];
        if (nil == dictionary) {
            printf("Could not parse plist file %s\n", argv[1]);
            return 1;
        }

        NSError* error = nil;

        NSData* data = [NSJSONSerialization dataWithJSONObject:dictionary options:NSJSONWritingPrettyPrinted error:&error];

        if(nil == data)
        {
            NSString* errorMessage = [NSString stringWithFormat:@"There was an error converting to JSON: %@", error];
            printf("%s", [errorMessage cStringUsingEncoding:NSUTF8StringEncoding]);
            return 1;
        }

        path = [path stringByAppendingPathExtension:@"json"];

        if (![data writeToFile:path atomically:YES]) {
            printf("Error writing to file: %s", [path cStringUsingEncoding:NSUTF8StringEncoding]);
            return 1;
        }

    }
    return 0;
}

Creating a PDF with iOS

CreatePDFExample

If you find yourself in the position where you need to generate a report that can be printed from your iOS application, you could either integrate directly with the AirPrint APIs, or you can alternatively generate a PDF file so the report can be emailed as well as printed. This is a relatively easy process, and is very similar to rendering graphics in a standard view context.

The key methods in this example are:

  • UIGraphicsBeginPDFContextToFile: instruct the system to generate a new graphics context backed by a file.
  • UIGraphicsGetCurrentContext: Retrieve the context that was just created
  • CGContext...: Any of the CGContext methods that can render to a graphics context
  • UIGraphicsBeginPDFPageWithInfo:Begin a new page in the PDF
  • UIGraphicsEndPDFContext: Finish the PDF and write it to disk (to the file specified in the call to UIGraphicsBeginPDFContextToFile

You’ll notice in the attached example that I use a CGRectZero to initialize the the PDF in the call to UIGraphicsBeginPDFContextToFile. This will create a PDF of the default 8 1/2″ x 11″ paper size, at 612 x 792 pixels. This is documented in the platform documentation at: http://developer.apple.com/library/ios/#documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GeneratingPDF/GeneratingPDF.html

Other items to note: we are keeping track of our Y position on the page so we can render text line by line until we run out of space. We determine how much space we are going to use by using the NSString’s measurement functions, which are probably the easiest way to get simple text metrics from the system. For more advanced text rendering you could always use Core Text.

After we generate our PDF file, we present the user with the option of emailing the file or displaying it. To display it, we use the QuickLook view controller, which provides an easy way to preview any content the system knows how to render (this includes web, pdf, images, etc). From the quick look view controller, the user can then print the file. When emailing, the file is added as an attachment using the MFMailComposeViewController.

Here’s the code that makes it all possible (and a link to the sample project CreatePDFExample):


// create some sample data. In a real application, this would come from the database or an API.
NSString* path = [[NSBundle mainBundle] pathForResource:@"sampleData" ofType:@"plist"];
NSDictionary* data = [NSDictionary dictionaryWithContentsOfFile:path];
NSArray* students = [data objectForKey:@"Students"];

// get a temprorary filename for this PDF
path = NSTemporaryDirectory();
self.pdfFilePath = [path stringByAppendingPathComponent:[NSString stringWithFormat:@"%d.pdf", [[NSDate date] timeIntervalSince1970] ]];

// Create the PDF context using the default page size of 612 x 792.
// This default is spelled out in the iOS documentation for UIGraphicsBeginPDFContextToFile
UIGraphicsBeginPDFContextToFile(self.pdfFilePath, CGRectZero, nil);

// get the context reference so we can render to it.
CGContextRef context = UIGraphicsGetCurrentContext();

int currentPage = 0;

// maximum height and width of the content on the page, byt taking margins into account.
CGFloat maxWidth = kDefaultPageWidth - kMargin * 2;
CGFloat maxHeight = kDefaultPageHeight - kMargin * 2;

// we're going to cap the name of the class to using half of the horizontal page, which is why we're dividing by 2
CGFloat classNameMaxWidth = maxWidth / 2;

// the max width of the grade is also half, minus the margin
CGFloat gradeMaxWidth = (maxWidth / 2) - kColumnMargin;

// only create the fonts once since it is a somewhat expensive operation
UIFont* studentNameFont = [UIFont boldSystemFontOfSize:17];
UIFont* classFont = [UIFont systemFontOfSize:15];

CGFloat currentPageY = 0;

// iterate through out students, adding to the pdf each time.
for (NSDictionary* student in students)
{
// every student gets their own page
// Mark the beginning of a new page.
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, kDefaultPageWidth, kDefaultPageHeight), nil);
currentPageY = kMargin;

// draw the student's name at the top of the page.
NSString* name = [NSString stringWithFormat:@"%@ %@",
[student objectForKey:@"FirstName"],
[student objectForKey:@"LastName"]];

CGSize size = [name sizeWithFont:studentNameFont forWidth:maxWidth lineBreakMode:UILineBreakModeWordWrap];
[name drawAtPoint:CGPointMake(kMargin, currentPageY) forWidth:maxWidth withFont:studentNameFont lineBreakMode:UILineBreakModeWordWrap];
currentPageY += size.height;

// draw a one pixel line under the student's name
CGContextSetStrokeColorWithColor(context, [[UIColor blueColor] CGColor]);
CGContextMoveToPoint(context, kMargin, currentPageY);
CGContextAddLineToPoint(context, kDefaultPageWidth - kMargin, currentPageY);
CGContextStrokePath(context);

// iterate through the list of classes and add these to the PDF.
NSArray* classes = [student objectForKey:@"Classes"];
for(NSDictionary* class in classes)
{
NSString* className = [class objectForKey:@"Name"];
NSString* grade = [class objectForKey:@"Grade"];

// before we render any text to the PDF, we need to measure it, so we'll know where to render the
// next line.
size = [className sizeWithFont:classFont constrainedToSize:CGSizeMake(classNameMaxWidth, MAXFLOAT) lineBreakMode:UILineBreakModeWordWrap];

// if the current text would render beyond the bounds of the page,
// start a new page and render it there instead
if (size.height + currentPageY > maxHeight) {
// create a new page and reset the current page's Y value
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, kDefaultPageWidth, kDefaultPageHeight), nil);
currentPageY = kMargin;
}

// render the text
[className drawInRect:CGRectMake(kMargin, currentPageY, classNameMaxWidth, maxHeight) withFont:classFont lineBreakMode:UILineBreakModeWordWrap alignment:UITextAlignmentLeft];

// print the grade to the right of the class name
[grade drawInRect:CGRectMake(kMargin + classNameMaxWidth + kColumnMargin, currentPageY, gradeMaxWidth, maxHeight) withFont:classFont lineBreakMode:UILineBreakModeWordWrap alignment:UITextAlignmentLeft];

currentPageY += size.height;

}

// increment the page number.
currentPage++;

}

// end and save the PDF.
UIGraphicsEndPDFContext();

// Ask the user if they'd like to see the file or email it.
UIActionSheet* actionSheet = [[[UIActionSheet alloc] initWithTitle:@"Would you like to preview or email this PDF?"
delegate:self
cancelButtonTitle:@"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:@"Preview", @"Email", nil] autorelease];
[actionSheet showInView:self.view];

 

Objective-C Version String Comparison

Often in code you have to compare two version strings, such as 1.0.0.1 and 1.2. You can not just convert these to fractions since there are multiple periods which do not correlate to a decimal number. The best way I found was to use the build in string comparator with some options.

Listed below is a category I extend NSString with to do version string comparisons. Code can be downloaded here: NSString+Version


@implementation NSString(Version)

-(BOOL) isOlderVersionThan:(NSString*)otherVersion
{
return ([self compare:otherVersion options:NSNumericSearch] == NSOrderedAscending);
}

-(BOOL) isNewerVersionThan:(NSString*)otherVersion
{
return ([self compare:otherVersion options:NSNumericSearch] == NSOrderedDescending);
}

@end

Drawing polyines or routes on a MKMapView in iOS4 – Part 3

Get right to it: here’s the sample code

With the release of iOS 4, Apple has drastically decreased the complexity involved in rendering shapes, including routes, on a map. In fact, they’ve added an entire set of classes to MapKit specifically designed to help you add shapes to a map. These new classes and protocols are:
MKCircle
MKCircleView
MKMultiPoint
MKOverlayPathView
MKPolygon
MKPolygonView
MKPolyline
MKPolylineView
MKShape
MKOverlay

These classes make it incredibly easy to do what was once complex (And slightly buggy). Before when you would scroll or zoom in on a map with a route rendered in an MKAnnotationView, the route would have to temporarily be hidden because it was not scaled or scrolled along with with map. the MapKit framework now handles all this automatically.

So how do you add a route to a map using the updated MKMapView in iOS 4?

  1. Create a MKPolyline
  2. Add the polyline (as a MKOverlay) to the map
  3. Implement mapView:viewForOverlay: in your MKMapViewDelegate
  4. Initialize, set values on, and return a MKPolylineView from your updated map view delegate
  5. Here’s some sample code that shows how you might create your MKPolyline:

    -(void) loadRoute
    {
    NSString* filePath = [[NSBundle mainBundle] pathForResource:@”route” ofType:@”csv”];
    NSString* fileContents = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    NSArray* pointStrings = [fileContents componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];

    // while we create the route points, we will also be calculating the bounding box of our route
    // so we can easily zoom in on it.
    MKMapPoint northEastPoint;
    MKMapPoint southWestPoint;

    // create a c array of points.
    MKMapPoint* pointArr = malloc(sizeof(CLLocationCoordinate2D) * pointStrings.count);

    for(int idx = 0; idx < pointStrings.count; idx++)
    {
    // break the string down even further to latitude and longitude fields.
    NSString* currentPointString = [pointStrings objectAtIndex:idx];
    NSArray* latLonArr = [currentPointString componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@","]];

    CLLocationDegrees latitude = [[latLonArr objectAtIndex:0] doubleValue];
    CLLocationDegrees longitude = [[latLonArr objectAtIndex:1] doubleValue];

    // create our coordinate and add it to the correct spot in the array
    CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(latitude, longitude);

    MKMapPoint point = MKMapPointForCoordinate(coordinate);

    //
    // adjust the bounding box
    //

    // if it is the first point, just use them, since we have nothing to compare to yet.
    if (idx == 0) {
    northEastPoint = point;
    southWestPoint = point;
    }
    else
    {
    if (point.x > northEastPoint.x)
    northEastPoint.x = point.x;
    if(point.y > northEastPoint.y)
    northEastPoint.y = point.y;
    if (point.x < southWestPoint.x)
    southWestPoint.x = point.x;
    if (point.y < southWestPoint.y)
    southWestPoint.y = point.y;
    }

    pointArr[idx] = point;

    }

    // create the polyline based on the array of points.
    self.routeLine = [MKPolyline polylineWithPoints:pointArr count:pointStrings.count];

    _routeRect = MKMapRectMake(southWestPoint.x, southWestPoint.y, northEastPoint.x - southWestPoint.x, northEastPoint.y - southWestPoint.y);

    // clear the memory allocated earlier for the points
    free(pointArr);

    }

    Now that you’ve got your route’s polyline created, you need to let the map view display it by adding it as an overlay:

    [self.mapView addOverlay:self.routeLine];

    Adding the overlay alone will not render anything on the map. Your MKMapViewDelegate implementation must return an overlay for this route you’ve just added. Luckily, the new iOS 4 MapKit framework provides a default implementation of an MKOverlayView that is capable of rendering a line on a map. This class is called MKPolylineView, and this is how you might use it:

    – (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id )overlay
    {
    MKOverlayView* overlayView = nil;

    if(overlay == self.routeLine)
    {
    //if we have not yet created an overlay view for this overlay, create it now.
    if(nil == self.routeLineView)
    {
    self.routeLineView = [[[MKPolylineView alloc] initWithPolyline:self.routeLine] autorelease];
    self.routeLineView.fillColor = [UIColor redColor];
    self.routeLineView.strokeColor = [UIColor redColor];
    self.routeLineView.lineWidth = 3;
    }

    overlayView = self.routeLineView;

    }

    return overlayView;

    }

    I hope this helps you use the new MapKit classes. Apple really has simplified rendering lines, routes and other shapes on the map, and these new classes and protocols really deprecate my previous articles on the topic, so long as you are targeting iOS 4 and above.

    Here a sample project that uses the above code:
    os4Maps

Which Platform

Had an interesting time tonight at the XConomy Mobile Madness conference. I was invited to be a guest panelist in a forum titled “Mobile Smackdown”; the point of the forum was to debate the merits of various mobile platforms.

I was chosen to argue for the iPhone.

As far as mobile development platforms are concerned, I’m fairly agnostic. I’ve worked in Windows Mobile, Android and the iPhone, and can honestly say that each one of them has their merits. For me, its not the features of a development platform that decides whether or not I should be developing for it. Its the business case for that platform that makes a more compelling argument than the technology itself.

My team (iPhone) lost the debate. I really don’t like to argue. Its also really difficult to argue for a point that you don’t completely agree with. I can not claim that the iPhone is easier to develop for or a better platform for developers when I have enjoyed all that Android offers. The other teams made very good points about the closed nature of the iPhone as well as the strict approval process on the iTunes App Store. Apple has recently reversed its decision on many apps, and that has the effect of turning app developers away, as Android becomes more and more popular.

There were two audience participatory votes at tonight’s “smackdown”:

  • Which team presented their case the best
  • Regardless of the debate, which platform should developers target

I was not surprised by the answer to the first question; I’m pretty terrible at debate. The second answer surprised me though; the most hands went up for Android. I think there are a few reasons for this:

  • Its easy to get lost in the sea of 120,000+ apps, and independent developers are having and increasingly difficult time rising to the top of the iTunes App Store.
  • Android’s future looks bright, as more devices from multiple manufactures are deployed on multiple networks. The potential market for these devices are larger than a device from a single carrier on a single network (understanding of course the iPhone is on many networks throughout the world, but restricted to one carried in its most popular market, the U.S.).
  • Android is an easy platform for which to develop. Its Java.
  • Android apps can be distributed independently of the app store

While Android shows huge potential, there are more people downloading and actually paying for apps on the iPhone. This may change, but until it does, we need to consider the size of the market we want to enter with our apps vs. the difficultly of getting our apps discovered. It may be better to target an emerging market with fewer paying customers if they can actually find your app. But if it is an extremely unique and popular idea, your potential for revenue is still greater on the iPhone.

Come Write Software

Come write software with me and the rest of Raizlabs. We’re looking to build our team of developers. Are you interested in writing mobile applications? Send me a note.

Objective-C SHA1 Function for the iPhone

Here’s a nice little function that will generate an SHA-1 hash digest that will match what the php sha1() function will generate if you give it the same input:

#import <CommonCrypto/CommonDigest.h>

@implementation SHA1

+(NSString*) digest:(NSString*)input
{
const char *cstr = [input cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:input.length];

uint8_t digest[CC_SHA1_DIGEST_LENGTH];

CC_SHA1(data.bytes, data.length, digest);

NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];

for(int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++)
[output appendFormat:@"%02x", digest[i]];

return output;

}
@end

Drawing polyines or routes on a MKMapView (as an MKAnnotationView) – Part 2

It turns out there is a better way to display routes on a map than the last example I gave. The problem with my last example is that the route exists in a layer above the map view, resulting in the line of the route being drawn on top of any annotation views. So pins, photos and anything else expressed as an MKAnnotation would be drawn over by the routes in the layer above the map view.

The solution to this problem then is to draw the lines as usual, but in a custom MKAnnoationView. This view will be set to not clip its own subviews, and will then have a subview that lies outside the frame of the annotation view, and this subview will draw the lines. The route annotation view needs a subview to do the rendering which will always be positioned at the full frame size and origin of the map. This way the MKAnnotationView can be smaller than the route, but it  always draws in the internal subview, which is the size of the map view.

So… long story short: the ability to put a route on a MKMapView via a custom MKAnnotation. You can download the sample code here.

//
// CSRouteView.m
// testMapp
//
// Created by Craig on 8/18/09.
// Copyright Craig Spitzkoff 2009. All rights reserved.
//
#import "CSRouteView.h"
#import "CSRouteAnnotation.h"
// this is an internally used view to CSRouteView. The CSRouteView needs a subview that does not get clipped to always
// be positioned at the full frame size and origin of the map. This way the view can be smaller than the route, but it
// always draws in the internal subview, which is the size of the map view.
@interface CSRouteViewInternal : UIView
{
// route view which added this as a subview.
CSRouteView* _routeView;
}
@property (nonatomic, retain) CSRouteView* routeView;
@end
@implementation CSRouteViewInternal
@synthesize routeView = _routeView;
-(void) drawRect:(CGRect) rect
{
CSRouteAnnotation* routeAnnotation = (CSRouteAnnotation*)self.routeView.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 != routeAnnotation.points && routeAnnotation.points.count > 0)
{
CGContextRef context = UIGraphicsGetCurrentContext();

if(nil == routeAnnotation.lineColor)
routeAnnotation.lineColor = [UIColor blueColor]; // setting the property instead of the member variable will automatically reatin it.

CGContextSetStrokeColorWithColor(context, routeAnnotation.lineColor.CGColor);
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 < routeAnnotation.points.count; idx++)
{
CLLocation* location = [routeAnnotation.points objectAtIndex:idx];
CGPoint point = [self.routeView.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);
}
}

CGContextStrokePath(context);

// debug. Draw the line around our view.
/*
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, 0, self.frame.size.height);
CGContextAddLineToPoint(context, self.frame.size.width, self.frame.size.height);
CGContextAddLineToPoint(context, self.frame.size.width, 0);
CGContextAddLineToPoint(context, 0, 0);
CGContextStrokePath(context);
*/
}

}
-(id) init
{
self = [super init];
self.backgroundColor = [UIColor clearColor];
self.clipsToBounds = NO;

return self;
}
-(void) dealloc
{
self.routeView = nil;

[super dealloc];
}
@end
@implementation CSRouteView
@synthesize mapView = _mapView;
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
// do not clip the bounds. We need the CSRouteViewInternal to be able to render the route, regardless of where the
// actual annotation view is displayed.
self.clipsToBounds = NO;

// create the internal route view that does the rendering of the route.
_internalRouteView = [[CSRouteViewInternal alloc] init];
_internalRouteView.routeView = self;

[self addSubview:_internalRouteView];
}
return self;
}
-(void) setMapView:(MKMapView*) mapView
{
[_mapView release];
_mapView = [mapView retain];

[self regionChanged];
}
-(void) regionChanged
{
NSLog(@"Region Changed");

// move the internal route view.
CGPoint origin = CGPointMake(0, 0);
origin = [_mapView convertPoint:origin toView:self];

_internalRouteView.frame = CGRectMake(origin.x, origin.y, _mapView.frame.size.width, _mapView.frame.size.height);
[_internalRouteView setNeedsDisplay];

}
- (void)dealloc
{
[_mapView release];
[_internalRouteView release];

[super dealloc];
}
@end

Tagged , ,

Lumbar Epidural Steroid Injection

Today I had a series injections of anesthetics and steroids in my spine. These are supposed to temporarily relieve my pain over the next two or three weeks. After that, we get to see if I am still in pain, and how bad it is, and that will be the lead determining factor of whether I go in for a discectomy to take care of the herniated disc. The discectomy would be a minimally invasive micro endoscopic procedure, which depending on the results may or may not be an outpatient procedure.

The shots I had today were painless, and the radiologist who administered them was amazing. The hard part though was lying on my stomach long enough to receive the injections. This three minutes of lying on my stomach was probably one of the most painful experiences of my life. My back just can not support that position right now. But relief came quickly afterward, so the three minutes of extreme pain was worth it.

What I’m hoping for, of course, is that after three weeks and the wearing off of the steroid injection, that the ejected disc material pushing up against my sciatic nerve will have been reabsorbed or receded away from my nerves. That’s really the source of all this pain; not the rupturing of the disc, but rather the pressing on and displacement of my lumbar nerves. If I’m not in pain after that, I can start some more serious physical therapy and hopefully get some strength back. But the radiologists reaction to my MRI was not encouraging, and he gave the impression that any relief he could offer would only be temporary. Only time will tell.

New England Baptist Hospital was impressive though. No smoking anywhere on their campus, super clean and modern, all the support staff were in suits… it just felt like a very well run operation.

In light of all this JobVent.com prospecting goes well… I’ve had several good conversations over the last few days. The power of online social networking in situations like this is becoming extremely clear. LinkedIn and Twitter have been indispensable.