Posts tagged ‘iPhone SDK’

Migrating ASIHTTPRequest to AFNetworking

After using the ASIHTTPRequest library for a long time (so sad that Ben Copsey decided to abandon it), I finally decided to give in and switch to AFNetworking by Mattt Thompson and Scott Raymond. It has taken some trial and error to get it working correctly, so here is what I have found so far.

Conversion #1: Simple call to a URL

For this conversion, I am just making a call to a web service URL. I don’t really care about the return value, I just care if the call went through or whether there was an error in communications with the web service.

The ASI code:

    NSString *urlString = [NSString stringWithFormat:@"http://myurl.com?id=%@", theID];
    NSURL *url = [NSURL URLWithString:urlString];
    __block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
    [request setDelegate:self];
    [request setCompletionBlock:^{
        NSLog(@"success");
    }];
 
    [request setFailedBlock:^{
        NSError *error = [request error];
        NSLog(@"error: %@", error.localizedDescription);
    }];
 
    [request startAsynchronous];

The AFNetworking code:

    NSString *urlString = [NSString stringWithFormat:@"http://myurl.com?id=%@", theID];
    NSURL *url = [NSURL URLWithString:urlString];
    AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:[NSURLRequest requestWithURL:url]];
    [op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
     {
         NSLog(@"success");
     }
                              failure:^(AFHTTPRequestOperation *operation, NSError *error)
     {
        NSLog(@"error: %@", error.localizedDescription);
     }];
 
    [op start];

Yeah, admittedly this one was not very hard to convert. Stay tuned, other more complicated conversions are coming.

BTW, Happy Birthday to Jim J. Bullock, who excellently portrayed Prince Valium in Spaceballs.

A better way to launch the Maps app for navigation on iOS 6

Back in the days when Google actually ruled Maps on the iOS platform (5.x and older), you had to push a URL through UIApplication’s sharedApplication object, and the system would see the  Google maps URL and intercept it, sending the data to Maps instead of Safari.

Well this kind of works in iOS 6 as well, substituting maps.apple.com for maps.google.com. But alas, there is a better way.

Here is the code that I am now using to launch Maps, and it works in both iOS 6 and iOS 5.

- (void)launchMaps:(NSString *)serviceAddress
{
    NSString *currentLocation = @"Current+Location";
 
    if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"6.0"))
    {
        // use MapKit to handle launching Maps app instead of pushing a URL
        CLGeocoder *geocoder = [[CLGeocoder alloc] init];
        [geocoder geocodeAddressString:serviceAddress completionHandler:^(NSArray *placemarks, NSError *error) {
            if (error)
            {
                NSLog(@"Geocode failed with error: %@", error.localizedDescription);
                return;
            }
 
            if (placemarks && placemarks.count > 0)
            {
                CLPlacemark *pl = placemarks[0];
                CLLocation *loc = pl.location;
                NSLog(@"Geocoded latitude: %f; longitude: %f", loc.coordinate.latitude, loc.coordinate.longitude);
                MKPlacemark *placemark = [[MKPlacemark alloc] initWithCoordinate:loc.coordinate addressDictionary:nil];
                MKMapItem *mapItem = [[MKMapItem alloc] initWithPlacemark:placemark];
                mapItem.name = @"Whatever name you want goes here";
                mapItem.phoneNumber = @"And if you have a phone number, it would go here";
                NSArray *mapItemArray = @[ mapItem ];
                NSDictionary *launchOpt = @{ MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving };
                [MKMapItem openMapsWithItems:mapItemArray launchOptions:launchOpt];
                [mapItem release];
                [placemark release];
            }
        }];
 
        [geocoder release];
        return;
    }
 
    NSString *urlString = [NSString stringWithFormat:@"http://maps.google.com/maps?saddr=%@&daddr=%@", currentLocation, serviceAddress];
    NSString *escaped = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:escaped]];
}

And here are the defines for the OS checking macros that I use. (Yes, I know you should check for specific features instead of checking OS version numbers.)

#define SYSTEM_VERSION_EQUAL_TO(v)                  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v)              ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v)                 ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v)     ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)

The address string passed in would just be the components of your address separated by spaces (such as “1060 W Addison St Chicago IL”), and the Maps app should open up and give you the opportunity to start navigation to the destination.

BTW, Happy Birthday to Frank Zamboni, who is now forever immortalized with a Google Doodle.

Measure twice, refactor once

Just a quick year end post for my loyal readers…

When you are looking to make performance improvements on your iOS app, no matter how well you think you know the code, fire up Instruments and run your code on a real device. Before the holiday break, I was not going to do this because I was certain where the code was slowing down, but on a lark I decided to profile it and needless to say it was a real eye opener. I saved myself a bundle of time by not chasing down phantom slow downs, made the code run super super fast, and got the girl in the end. (OK, maybe not that last one.)

Happy and prosperous New Year to one and all.

Hello NSScanner

In an attempt to broaden my iOS development horizons, I thought I would take a look at some of those iOS classes and components that you may not know too much about or have used in the past, but probably should due to their elegance or functionality.

Today’s example is NSScanner (reference and programming guide). This handy class has one main purpose, to scan through a source string into either strings or other data types, such as integers and floats.

Converting a NSString to an integer is fairly trivial, as you can just do something like [myString intValue]. However, the NSScanner class maintains a pointer as the source string is scanned, so if you need to loop through a string and scan multiple values in from one string, NSScanner is your class. One other interesting thing that NSScanner can do is to take in a string of hexadecimal digits and convert them as well.

In the simplest case, here is how you would convert a string to an integer:

NSString *theString = @"42";
unsigned int theValue;
[[NSScanner scannerWithString:theString] scanHexInt:&theValue];

All of the scan methods return a BOOL that lets you know if the scan was successful or not, so be sure to not do what I am doing above and check the return value to make sure there was not an error.

Here is how you would process a hexadecimal string and examine each byte in the string:

int theLength = [hexString length];
for (int i = 0; i < theLength; i += 2)
{
    NSRange theRange = NSMakeRange(i, 2);
    NSString *hexDigits = [hexString substringWithRange:theRange];
    unsigned int theValue;
    [[NSScanner scannerWithString:hexDigits] scanHexInt:&theValue];
    NSLog(@"Byte number %d is %d\n", (i / 2), theValue);
}

And a final example, let’s say you had the text of an HTML page and you wanted to pull out all of the links in the page (denoted by an A tag with an HREF= entry). Here is some code that would do that:

NSScanner *scanner = [NSScanner scannerWithString:theHTMLString];
NSString *foundString = nil;
[scanner scanUpToString:@"a href=" intoString:nil];
while (![scanner isAtEnd])
{
    [scanner scanUpToString:@"\"" intoString:nil];
    scanner.scanLocation++;
    [scanner scanUpToString:@"\"" intoString:&foundString];
    [scanner scanUpToString:@"a href=" intoString:nil];
    NSLog(@"Link found: %@", foundString);
}

If you would like to download the NSScannerTest project that I put together to demonstrate these features in a running app, here is the download link, make sure to be using the latest version of Xcode and iOS SDK:

NSScannerTest source code

(Bonus mini tutorial: If you download and look at the source code, you will get a little sampling of NSRegularExpression and NSTextCheckingResult, two Foundation classes that probably deserve a tutorial posting of their own in the future.)

I have a couple of ideas on what I am going to cover next, but if you have any suggestions, please use the comments. Thanks and I’ll see you next time.

iOS 6 Calendar and Address Book issues

I’m sure that it was noted somewhere in the docs or change log, but apparently they changed the way that you access the Calendar and Address Book data for iOS 6. Previously, you could just create an event store or address book object and fire away, but now there is an extra step involved.

Now, you have to request access, and then in the callback that fires when access is granted by the user, perform the actions that you need to do with the object.

So here is what the code to access the Calendar looks like:

EKEventStore *eventStore = [[EKEventStore alloc] init];
if ([eventStore respondsToSelector:@selector(requestAccessToEntityType:completion:)])
{
    [eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (error)
            {
                // display error message here
            }
            else if (!granted)
            {
                // display access denied error message here
            }
            else
            {
                // access granted
                // do the important stuff here
            }
        });
    }];
}
else
{
    // do the important stuff here
}
 
[eventStore release];

And here is what the code looks like to access the Address Book data:

if ([self isABAddressBookCreateWithOptionsAvailable])
{
    CFErrorRef error = nil;
    addressBook = ABAddressBookCreateWithOptions(NULL, &amp;error);
    ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (error)
            {
                // display error message here
            }
            else if (!granted)
            {
                // display access denied error message here
            }
            else
            {
                // access granted
                // do the important stuff here
            }
        });
    });
}
else
{
    addressBook = ABAddressBookCreate();
    // do the important stuff here
}

And in the above code, here is the method that tests for the existence of the ABAddressBookCreateWithOptions function:

- (BOOL)isABAddressBookCreateWithOptionsAvailable
{
    return &ABAddressBookCreateWithOptions != NULL;
}

And also, I am not sure if this is an iOS 6 thing or not, and ultimately I am not sure if it will help in the long run, but in my own testing of generating PDFs on an iOS 6 device, I found that using the Core Foundation function CFAttributedStringSetAttribute was causing intermittent crashes. So I switched to using the function CFAttributedStringSetAttributes instead, and it seemed to work much better. Here is a code sample of what that looks like:

CFMutableDictionaryRef fontDict = CFDictionaryCreateMutable(NULL, 1, NULL, NULL);
CFDictionaryAddValue(fontDict, kCTFontAttributeName, fontRef);
CFAttributedStringSetAttributes(currentText2, CFRangeMake(0, CFAttributedStringGetLength(currentText2)), fontDict, false);
CFRelease(fontDict);

BTW, happy birthday to Chevy Chase, one of the best comic actors ever.

The app delegate’s UIWindow is finicky

I learned the hard way that there is a right way to do things in your didFinishLaunchingWithOptions with regards to self.window, and then there is the way that Apple used to have apps work that does not work any longer.

In case you haven’t heard, screen rotation in iOS 6 is much different from the previous versions of iOS. As a result, I was banging my head against the wall trying to figure out the magic incantation to get my rotation working again, and as it turned out, it had to do with this line of code in my app delegate:

[self.window addSubview:navigationController.view];

I was building my UINavigationController in the code and assigning it to the window as shown above. On the advice of an enterprising Stack Overflow user, I changed the line of code to this:

[self.window setRootViewController:navigationController];

And now the rotation events are starting to once again fire in my app. Yay.

For some additional information on how iOS6 handles rotation differently, please visit this blog post, which is where I found part of the information that I needed to get rotation working again in my app:

iOS 6 SupportedOrientations with UINavigationController – Shabbir’s Code Snippets

BTW, Happy Anniversary to Devil’s Tower, which was declared to be the first National Monument in the U.S. on this date in 1906. I now have a burning desire to watch Close Encounters for about the 100th time.

Intermittently localized strings (and MobileVLC build issues)

So I was having an issue with my NSLocalizedString entries. At seemingly random intervals, the app that I built and deployed to either the simulator or the iPad would display the key that I pass into NSLocalizedString instead of the value from my Localizable.strings file. Which is very annoying.

As it turns out, the app was getting confused because I had 2 Localizable.strings files in my project. So if this is happening to you, make sure that you are looking at the file list in your workspace, and start typing Localizable in the search field at the bottom of the list. If you see more than two Localizable.strings files, consolidate them into one and then delete the extra ones.

Also, I have been having a devil of a time trying to get MobileVLC built and running. If you can help, please take a look at my posting on Stack Overflow on the latest issue:

iOS SDK 5.1 linker error for non_lazy_ptr in Xcode 4.4.1

I have also posted to the VideoLAN forum about this issue, again with no luck:

iOS SDK 5.1 linker error for non_lazy_ptr in Xcode 4.4.1

BTW, happy birthday (posthumously) to Danny Gatton, a supremely talented guitar player who left us too soon.

CI for iOS: Better Than Bacon? (CIDUG meeting, July 24, 2012)

Kevin Munc gave a presentation last night on doing continuous integration using Jenkins for developing iOS apps. It seemed somewhat complicated to set up, but I did like some of the things that he showed. Hopefully I will get a few free seconds here at my job to try and implement some of the things that Kevin showed.

Also, for those few of you who use Xnews for reading your Usenet newsgroups, it was acting up for me by not remembering that I only wanted to see unread articles when going into a newsgroup, I would have to hit the U key after the newsgroup loaded to hide all the read headers. This is far too much work for me. The way I found to get around this was to exit the program and spelunk into the folder on your computer where Xnews lives, and look for a folder called “data”. Inside this folder, you may see a folder or file for the group that you are having trouble with. When I saw this, I just deleted the file, started the application back up, and all was right with the world.

BTW, Happy Birthday to Doug Drabek, the former pitcher for the Pirates (and other teams) that I met briefly at last years Pirates Fantasy Camp.

Locale friendly numeric entries

If you are ever planning to have someone from a country other than the U.S. use your iOS application, then I would go through right now and check for all the floatValue and doubleValue methods of NSString, and get rid of them if the NSString is coming from data that the user can enter. Needless to say, those users in Europe who use a comma for the decimal separator and a period for the thousands separator will notice some hilarities when using your app.

I put together a quick method in my NSString categories code (notice that it uses self as the source string) that I am using to avoid this very issue. Here is what it looks like:

- (double)localeSafeDecimalValue
{
    NSNumberFormatter *decimalStyle = [[NSNumberFormatter alloc] init];
    [decimalStyle setNumberStyle:NSNumberFormatterDecimalStyle];
    NSNumber *n = [decimalStyle numberFromString:self];
    [decimalStyle release];
    return [n doubleValue];
}

This will work as long as the user is entering numbers in the format that their International Settings are set to on their iOS device.

BTW, happy birthday to Michael Anthony, bass player and backup singer of Van Halen and Chickenfoot fame.

Blazingly fast inserts with SQLite

As a follow up to Stir Trek, thanks to my friend and former co-worker John B., who reminded me that if you have a lot of inserts you want to do, execute this command on your database before you get started:

BEGIN TRANSACTION;

Then, after all your inserts have been sent , execute this:

END TRANSACTION;

If you do this, I promise that your users will be much more happier. (In my iOS app instance, the insert times went from about 90 seconds to 0.7 seconds.)

BTW, happy birthday to Amy Heckerling, who directed two of my all time most favorite movies, National Lampoon’s European Vacation and Johnny Dangerously.