Archive for the ‘iOS’ Category.

Xcode project line counts

If you are looking to find out how many lines of code are in each of the files of your Xcode project, you can use the following command in Terminal after you change into the root folder of your project:

find . \( -iname \*.m -o -iname \*.mm -o -iname \*.h \) -exec wc -l '{}' \+

SpriteKit game background music stopped working

In my SpriteKit game, all of a sudden the background music stopped playing, all I heard was silence no matter what the volume levels were in either the app settings or via the hardware buttons.

I am not sure why this happened, but I was able to figure out how to get it working again. The key was adding an AVAudioSession call before the existing AVAudioPlayer code. Here is some sample code of what I did:

AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
 
NSURL *url = [NSURL fileURLWithPath:@"your_sound_file.caf"];
_player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
[_player setDelegate:self];
[_player prepareToPlay];
[_player play];

BTW, Happy Birthday to Brutus Beefcake. (I couldn’t really find any other good birthdays, deaths, or events in the Wikipedia page for today.)

Xcode test coverage falsely indicates methods covered by testing

If you have a normal Xcode project that you added unit testing to, you may find that the testing code coverage inside of Xcode shows that there is a lot more code being tested than actually is. Or if you have an app that does complex and/or lengthy things in its initialization, you may get tired of waiting for all that stuff to run just because you want to run the tests to see if they pass.

Luckily, I figured out a way to short circuit the app from its normal initialization if you are invoking it for the purposes of testing. Here is the code you would need to put in your app delegate’s didFinishLaunchingWithOptions method, preferably right at the top:

#if defined(DEBUG_VERSION)
    if ([[[NSProcessInfo processInfo] arguments] containsObject:@"-FNTesting"])
    {
        DebugLog(@"The -FNTesting argument is in use, so the app will just create a blank view controller");
        UIViewController *vc = [[UIViewController alloc] init];
        navigationController = [[MyNavigationController alloc] initWithRootViewController:vc];
        [window setRootViewController:navigationController];
        return YES;
    }
#endif

(The #if above uses a compiler variable that is only used in the debug scheme of the application. It’s a good plan all around to do this in your own code.)

Basically, this code only is going to run if your are doing a unit test run, and it just creates a blank view controller and returns.

BTW, Happy Pi Day. I meant to do this posting at 1:59 today to continue the pi theme, but got busy with work and forgot.

Advent of Code

I have been going through the 2015 version of the Advent of Code, which is a web site that has a bunch of interesting programming puzzles. In an attempt to try to learn something new, I decided I would solve the puzzles using Swift. I am about half way through so far, and the results have been eye opening.

Here is the link to the Advent of Code web site:

http://adventofcode.com

And here is a link to my Github repository with the solutions:

https://github.com/Wave39/AdventOfCode

BTW, Happy Birthday to Mike Keneally, the excellent guitarist and musician who has worked with so many great artists, including Joe Satriani and Steve Vai.

How to make random entries into the iPhone address book (updated)

A few years have passed since I posted a blog entry entitled How to make random entries into the iPhone address book, and while you might be able to figure out how to make this code work if you try to use it with iOS 9, I figured it was time to revisit.

Here is the iOS 9 compliant method for creating the random address book entries:

- (IBAction)add25ButtonPressed:(id)sender
{
#define NUMBER_OF_RANDOM_ENTRIES    25
#define URL_FORMAT  @"http://old.wave39.com/roster/generate.php?f=csv&g=%@&num=%d&limit=99"
 
    CFErrorRef error1 = nil;
    ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, &error1);
    ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error2) {
        // callback can occur in background, address book must be accessed on thread it was created on
        dispatch_async(dispatch_get_main_queue(), ^{
            if (error2)
            {
                NSLog(@"Address book error: %@", ((__bridge NSError *)error2).localizedDescription);
            }
            else if (!granted)
            {
                NSLog(@"Access to the address book has been denied.");
            }
            else
            {
                // access granted
                NSString *genderOfNames = ((arc4random() % 2) == 1 ? @"m" : @"f");
                NSString *urlString = [NSString stringWithFormat:URL_FORMAT, genderOfNames, NUMBER_OF_RANDOM_ENTRIES];
                NSURL *url = [NSURL URLWithString:urlString];
                NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
                NSURLResponse *response = nil;
                NSError *error = nil;
                NSData *data = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&response error:&error];
                NSString *stringData = [NSString stringWithUTF8String:[data bytes]];
                NSString *areaCode = [NSString stringWithFormat:@"%03d", (arc4random() % 799 + 200)];
 
                NSArray *lineArray = [stringData componentsSeparatedByString:@"\n"];
                for (NSString *line in lineArray)
                {
                    if ([line length] > 0)
                    {
                        NSArray *fieldArray = [line componentsSeparatedByString:@","];
                        if ([fieldArray count] == 3)
                        {
                            ABRecordRef person = ABPersonCreate();
                            NSString *phone = [NSString stringWithFormat:@"%@-%03d-%04d", areaCode,
                                               (arc4random() % 799 + 200), (arc4random() % 9999)];
                            ABMutableMultiValueRef phoneNumberMultiValue = ABMultiValueCreateMutable(kABMultiStringPropertyType);
                            CFStringRef phoneType = (arc4random() % 2 == 0 ? kABPersonPhoneMainLabel : kABPersonPhoneMobileLabel);
                            ABMultiValueAddValueAndLabel(phoneNumberMultiValue, (__bridge CFTypeRef)(phone), phoneType, NULL);
                            NSString *email = [NSString stringWithFormat:@"%@_%@@tapbandit.com", fieldArray[1], fieldArray[2]];
                            ABMutableMultiValueRef emailMultiValue = ABMultiValueCreateMutable(kABMultiStringPropertyType);
                            CFStringRef emailType = kABHomeLabel;
                            ABMultiValueAddValueAndLabel(emailMultiValue, (__bridge CFTypeRef)(email), emailType, NULL);
                            ABRecordSetValue(person, kABPersonFirstNameProperty, (__bridge CFTypeRef)(fieldArray[1]), nil);
                            ABRecordSetValue(person, kABPersonLastNameProperty, (__bridge CFTypeRef)(fieldArray[2]), nil);
                            ABRecordSetValue(person, kABPersonPhoneProperty, phoneNumberMultiValue, nil);
                            ABRecordSetValue(person, kABPersonEmailProperty, emailMultiValue, nil);
                            ABAddressBookAddRecord(addressBook, person, nil);
                            NSLog(@"Created record for %@ %@ (%@: %@; %@)", fieldArray[1], fieldArray[2], phoneType, phone, email);
                            CFRelease(phoneNumberMultiValue);
                            CFRelease(emailMultiValue);
                            CFRelease(person);
                        }
                    }
                }
 
                ABAddressBookSave(addressBook, nil);
                NSLog(@"Done creating address book data.");
 
                CFRelease(addressBook);
            }
        });
    });
}

BTW, Happy Birthday to Windows, first available 30 years ago today. Coincidentally, on the same exact day those 30 years ago, the Pittsburgh Pirates hired Jim Leyland to be their manager. And in both instances, the rest is history.

UITextField inside UITableViewCell moves one pixel on becoming first responder

If you have ever noticed that your UITextField inside of a UITableViewCell moves one pixel on becoming the first responder, and then moves one pixel back when resigning the first responder, then you are in luck, as I have found a work around.

Basically, I found that if your UITextField in Interface Builder is set to System 17.0 font, go ahead and change it to Helvetica Neue 17.0. For some reason, this works for me and does not move at all when becoming or resigning the first responder.

BTW, The King left us on this day back in 1977, so sing a verse of Jailhouse Rock or Heartbreak Hotel in his honor.

Expected expression error when creating new variable in switch case block

So I am coding along in my iOS app and I come across an Expected expression error when I am creating a new variable in switch case block. For example, this looks innocent enough…

switch (actionType)
{
    case 0:
        NSArray *componentArray = [theString componentsSeparatedByString:@","];
        // do more stuff here
        break;
    // more cases here
}

No matter what I do, the Xcode compiler complains about the NSArray line right below the case 0 statement. What kind of horse hockey is this? The code is so simple that it is laughable.

My investigations led me to discover that there is a C compilation rule that specifies that you cannot have a variable declaration right after a label. And apparently, case statements are compiled into labels. (By exactly what sorcery escapes me, I am sure if you are interested you can dig into the C language definitions and figure it out.)

So I am forced to do this instead:

NSArray *componentArray;
switch (actionType)
{
    case 0:
        componentArray = [theString componentsSeparatedByString:@","];
        // do more stuff here
        break;
    // more cases here
}

BTW, Happy Birthday to Cheryl Burke, who is by far my favorite pro dancer on Dancing With The Stars. I hope Cheryl returns to the show at some point in the future.

Custom UITableViewCell with class and nib file

It is only a matter of time before you want to create a custom UITableViewCell with class and nib file on the iOS platform. There are a couple of things to keep in mind when doing this so that you do not bang your head against the wall, trying to figure out why it is not working.

In order to do this, you will probably create a .m and .h file for your code, and a .xib file for the UI. After creating a new nib file, you will have to remove the blank UIView and put a blank UITableViewCell on the design surface. Make sure to get into the habit right away of setting both the File’s Owner and the top level table view cell to your new class name.

Then, if you any IBOutlet controls, when you are assigning the pointers in Interface Builder, make sure that you are dragging to the top level cell and not the File’s Owner, otherwise you will get a crash when the cell is being built.

BTW, Happy Birthday to Gary Lockwood, most famously of 2001: A Space Odyssey and the 2nd Star Trek pilot.

Problems presenting view controller from clickedButtonWithIndex on iPad with iOS 8

As I work through my apps, I keep finding nifty iOS 8 issues. Such as one where I am having problems presenting a view controller from the clickedButtonWithIndex delegate method on an iPad with iOS 8.

From my research, it appears that under the covers, Apple is taking my UIActionSheet and morphing it into a UIActionController on the iPad in iOS 8. Unfortunately, weird things can happen if you try to present another view controller from within the clickedButtonWithIndex method, as the alert controller is still visible when the new controller is being presented. As a result, you get a warning message in the console that looks something like this:

Warning: Attempt to present <NewViewController: 0x12345678> on <OldViewController: 0x98765432> which is already presenting <UIAlertController: 0x24682468>

The solution to this seems to be to react to the didDismissWithButtonIndex method on UIAlertViewDelegate. When this is done, the alert controller appears to be gone on the iOS 8 iPad by the time that the new view controller is presented, and all is happy.

BTW, a posthumous Happy Birthday to Paul Butterfield, a fantastic artist who left us way too early. Luckily he is being immortalized in the Rock and Roll Hall of Fame, so more people should learn about him and experience his music.

CLGeocoder with built-in NSCache

In case you were wondering, Apple “recommends” that you only make one call per minute to their geocoding system. I can kind of understand the reasoning behind this, as they do not want to let people abuse the system. If you have a bunch of geocoding requests that you need to make within a short amount of time, you will not be cut off immediately if you push through a bunch of requests within seconds, but eventually you will get clipped. Now, if the requests you make are part of a table view that is scrolling, wouldn’t it be neat if you could find a CLGeocoder with built-in NSCache?

Well now you can. Please check out my GitHub repository for BPGeocoder:

https://github.com/Wave39/BPGeocoder

This class inherits from CLGeocoder and you use it as a replacement for CLGeocoder, except that it maintains its own NSCache of addresses that it has geocoded, and if you pass in an address it has already seen, it does not bother contacting the Apple servers, it just returns back the cached results it found earlier.

BTW, Happy Birthday to Terry Farrell, who played Valerie in Back To School and Lt. Dax on Star Trek: Deep Space 9, which was the best Star Trek series, IMHO. (Additionally, I think we need to get an internet campaign going to get IMDb to change the cast photo on their DS9 page to be one with Terry. Leave a comment if you agree. Sorry, Nicole!)