September 11 retrospective

The last thing the internet needs is yet another commentary about September 11, so I will be brief.

I used to listen to sports radio. A lot. Especially when I lived in Safety Harbor, Florida.

It seemed like just another Tuesday morning drive to the office down in St. Petersburg. Tony Bruno was on the air yakking about something or the other, who can remember what. I pull into the First Union parking lot at around 10 minutes until 9 or so, and Tony is making some kind of comment about a fire in a skyscraper in New York.

Cross the street, quick elevator ride up to the 6th floor, and there is dead silence amongst my team. When it was clear what was happening, my first thought was that my wife worked on the 29th floor of a building in downtown Tampa, which thankfully was evacuated very quickly.

The comic relief that morning was provided by Jim, our company president who was scheduled to fly out of town on business that morning. He called me at the office from his cell phone as he was waiting in traffic trying to get to the Tampa airport, asking me what was going on. (I am not sure what Jim listens to while driving, apparently nothing.) I told him to turn around and head back home.

Much like everyone else on that day, we followed along with the news at the office until the internet was overwhelmed with traffic, at which point we switched over to radio. I tried to keep everyone focused to try and get something done, but it was a lost cause and we closed the office a few hours early.

The air travel experience was much different when I traveled to my brother’s wedding 10 days later. My wife also had a plane ticket but would not go with me, and I could not blame her. Are we better or worse off in this country after the events that occurred on that day? I am not sure how to answer that, so I will leave it up to posterity.

But I will tell you to make sure to hug family and friends today to remind them how much they are appreciated.

We must not fear.

We must not forget.

How to make random entries into the iPhone address book

Are you a frustrated iOS developer working on an app that has to access the built-in address book on the device? Frustrated because you can’t find sample data to populate the address book in your Simulator, and you are too busy to create a bunch of test data?

Well, for a limited time* only, we at the Do Something Here Labs have a deal for you. For just a minimal** postage and handling charge, you too can use this sample code below to create random entries to your hearts content in your very own iOS app.

Here is the code in question. Simply drop this method into your Objective-C source code and call it in your app, and you will soon have contacts, contacts, and more contacts.

#import <AddressBook/AddressBook.h>
 
- (void)addressBookData
{
#define NUMBER_OF_RANDOM_ENTRIES    25
#define GENDER_OF_NAMES             @"m"    // use m for male or f for female
#define URL_FORMAT  @"http://old.wave39.com/roster/generate.php?f=csv&g=%@&num=%d&limit=99"
 
    NSString *urlString = [NSString stringWithFormat:URL_FORMAT, GENDER_OF_NAMES, 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)
            {
                ABAddressBookRef addressBook = ABAddressBookCreate(); 
                ABRecordRef person = ABPersonCreate();
                NSString *phone = [NSString stringWithFormat:@"%@-%03d-%04d", areaCode,
                                   (arc4random() % 799 + 200), (arc4random() % 9999)];   
                ABMutableMultiValueRef phoneNumberMultiValue = ABMultiValueCreateMutable(kABPersonPhoneProperty);
                CFStringRef phoneType = (arc4random() % 2 == 0 ? kABPersonPhoneMainLabel : kABPersonPhoneMobileLabel);
                ABMultiValueAddValueAndLabel(phoneNumberMultiValue, phone, phoneType, NULL);
                ABRecordSetValue(person, kABPersonFirstNameProperty, [fieldArray objectAtIndex:1], nil);
                ABRecordSetValue(person, kABPersonLastNameProperty, [fieldArray objectAtIndex:2], nil);
                ABRecordSetValue(person, kABPersonPhoneProperty, phoneNumberMultiValue, nil);
                ABAddressBookAddRecord(addressBook, person, nil);
                ABAddressBookSave(addressBook, nil);
                NSLog(@"Created record for %@ %@ (%@: %@)", [fieldArray objectAtIndex:1], 
                      [fieldArray objectAtIndex:2], phoneType, phone);
                CFRelease(phoneNumberMultiValue);
                CFRelease(person);
                CFRelease(addressBook);
            }
        }
    }
 
    NSLog(@"Done creating address book data.");
}

On a housekeeping note, make sure to include the AddressBook.framework library in your application, and make sure to import the AddressBook.h header file as shown. Although if you are already developing an app that accesses the address book, you probably have this covered.

Also, your device or computer needs to have internet access while the code is running, as the code accesses a simple PHP application that I did a while back to do random sports roster generation. (The PHP application will generate a maximum of 99 entries at a time, so if you want to try and have it generate 42 billion names with one call, go right on ahead and try.) OK, I probably should not be blocking the main thread with the synchronous request, but I did not have time do something other than the most quick and dirty solution possible.

But wait, there’s more.

Happy birthday to Terry Bradshaw. I’m sure that if Terry was developing an iOS app, and he needed to populate some random address book entries, this would be the code he would use. And if it is good enough for Terry, isn’t it good enough for everyone?

(* = For as long as this web site is accessible on the internet)

(** = $0.00)

Batter vs. Pitcher Lite now available

Maybe someone at Apple was reading my blog yesterday, because a couple of hours after I posted my lamentations over the BvP Lite approval wait, the app went into the approval process and was approved then an hour or so after that. Awesome job Apple, thanks.

Here is the link to the Batter vs. Pitcher Lite free app:

Batter vs. Pitcher Lite

And if you need to be able to analyze more than one batter and/or pitcher at a time, you would need to get the non-free Batter vs. Pitcher app:

Batter vs. Pitcher

No birthday, anniversary, or holiday wishes at this time, but I would like to say that I am totally impressed with my new 13″ MacBook Air. It seems to be more than adequate for building iOS apps, and due to the SSD drive, it actually seems to compile, build, and fire up the simulator much faster than my old MacBook Pro.

Still patiently waiting

Well, I took the version 1.0 BvP app and turned this 1 batter vs. 1 pitcher app into a free app, and submitted this new app to iTunes Connect. They seem to be dragging their feet on this new app, as it has been over a week. App revisions seem to go much faster, it only took a few days when I submitted the 2.0 BvP app, which features the ability to have multiple pitchers vs. multiple batters. This is a much more useful mode for fantasy baseball or for statistical research.

BTW, Happy Birthday to John Boker. (Not the John Boker who played baseball for the Ogden Raptors, but “the” John Boker who was formerly a track star and also happens to be Ohio’s pre-eminent bacon tea connoisseur.)

Two superviews are better than one

So I have a table view that is built by using a custom cell that I built in Interface Builder and load up in my view controller. This UITableViewCell has a bunch of UIButton controls right off the root of the cell view in it that I need to snag the action on, and of course there can be a lot of cells in the table. In order to do this as compactly as possible, I wanted to just create one IBAction method, and funnel all button presses to that method. Then, inside the method, I would just read the tag of the sender to decide what button was pressed, and then back up one level to get the cell, ask the table for the index path of that cell, and then I would know which row to act on.

Well of course it did not work as expected the first time. For some reason, when I asked the table for the index path of the cell, it would always return a nil index path. So here is the final code that works.

UITableViewCell *cell = (UITableViewCell *)sender.superview.superview;
NSIndexPath *path = [contactsTable indexPathForCell:cell];

As I studied the problem, I discovered that the first superview off the sender does not get you far enough back, you have to go back another superview.

BTW, thanks to Leo Fender, born this day in 1909, for founding a company with such awesome products.

I Want My MP3 (Reprise)

Well, I gave in and uninstalled the official Twitter app from the Droid Charge, and what do you know, my MP3s play. I can now enjoy my ringtone of “The Analog Kid”.

I Want My MP3

Please take a look at the two electronic devices in the following picture:

Electronic devices

One of these devices plays the MP3 files that I ripped in iTunes from CDs that I own, and one of these devices does not. (The Joe Satriani pick is included just to give you an idea of the size of the device on the left.)

I think you can see where I am going here with this one. The cheapo flimsy extra-white Kube MP3 player on the left plays the MP3 files just swimmingly. Conversely, this is not exactly what I am expecting to see when I go to the music player on my shiny new Samsung Droid Charge from Verizon:

Android screen shot

Anybody have any ideas? If I reboot the device, the MP3 files play for a while, and then it goes back to the wonderful “Sorry, the player does not support this type of audio file” message. Also, I have no intention of uninstalling the Twitter app. I use the Twitter app and I am not sure if it would even go away if I tried to uninstall it. (I have tried repeatedly to uninstall “Let’s Golf” from the device, but alas each time I uninstall it, it shows back up again.)

If you would like to follow along with this on the Verizon support board, please feel free…

MP3 files will not play

Unfortunately, you would need to sign up for a forum account to contribute. If you have something to add, please feel free to click the Comment link just below this post.

BTW, Happy Pi Approximation Day. July 22, 22 divided by 7 is 3.142857 (decimal pattern then repeats), which is as close to pi as you can get with a fraction made up of a numerator and denominator that are reasonbly small.

It’s like, how much more awesome could “Hair Nation” be?

And the answer is, none more awesome…

It's Big Bottom time!

I think this seals the deal now, I am going to have to be subscribing to satellite radio when the trial subscription runs out on my new car. Sirius/XM stockholders should be happy…

Shameless plug

As long as the Pittsburgh Pirates are playing above .500 baseball, I will be discounting the Batter vs. Pitcher app from $2.99 to $0.99, so get it while they’re hot…

Batter vs. Pitcher

BTW, Happy Bastille Day!

Logging the view hierarchy

I found some nice code on Stack Overflow for outputting the view hierarchy to the console, and I thought it was worthy of a few mention after some minor modifications of course.

The code takes in a view and iterates through the whole view hierarchy from that view, outputting the vital statistics of the views to the console. I modified the code a bit to pass in a parameter initially to control if the logging routine walks the superviews, and I also made the output show the screen coordinates in integers instead of showing floats with 4 decimal places.

Now, with a line like this in my application (don’t forget of course to import the InspectView header file):

[InspectView dumpViewToLog:self.view findParent:YES];

I can get this in the console:

2011-07-09 16:33:10.475 MyApplication[1038:207] 
Inspect view hierarchy -----------------------------------
Original view is (0x6840800)
UIWindow (0x6314b00): frame origin: (0, 0) size: (320, 480) [tag=0] UIView : UIResponder : NSObject : 
.   UIImageView (0x6314a70): frame origin: (0, 20) size: (320, 460) [tag=0] UIView : UIResponder : NSObject : 
.   UILayoutContainerView (0x63190a0): frame origin: (0, 0) size: (320, 480) [tag=0] UIView : UIResponder : NSObject : 
.   .   UINavigationTransitionView (0x631a7e0): frame origin: (0, 0) size: (320, 480) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   UIViewControllerWrapperView (0x6149dd0): frame origin: (0, 64) size: (320, 416) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   UITableView (0x6840800): frame origin: (0, 0) size: (320, 416) [tag=0] UIScrollView : UIView : UIResponder : NSObject : 
.   .   .   .   .   UITableViewCell (0x615cad0): frame origin: (0, 290) size: (320, 129) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIGroupTableViewCellBackground (0x614b9b0): frame origin: (9, 0) size: (302, 129) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UITableViewCellContentView (0x615d590): frame origin: (10, 1) size: (300, 126) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   .   UILabel (0x614b2f0): frame origin: (10, 5) size: (279, 21) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   .   UILabel (0x6149800): frame origin: (10, 34) size: (227, 84) [tag=4] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIImageView (0x615c950): frame origin: (10, 1) size: (300, 10) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   UITableViewCell (0x6385000): frame origin: (0, 204) size: (320, 66) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIGroupTableViewCellBackground (0x615b0f0): frame origin: (9, 0) size: (302, 66) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UITableViewCellContentView (0x6385710): frame origin: (10, 1) size: (300, 63) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   .   UILabel (0x6379b10): frame origin: (10, 6) size: (277, 21) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   .   UILabel (0x6379eb0): frame origin: (10, 35) size: (227, 21) [tag=3] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIImageView (0x6147b70): frame origin: (10, 1) size: (300, 10) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   UITableViewCell (0x6386b60): frame origin: (0, 76) size: (320, 108) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIGroupTableViewCellBackground (0x63872c0): frame origin: (9, 0) size: (302, 108) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UITableViewCellContentView (0x6386d50): frame origin: (10, 1) size: (267, 105) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   .   UILabel (0x6387de0): frame origin: (10, 35) size: (227, 63) [tag=2] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   .   UILabel (0x6388350): frame origin: (10, 6) size: (235, 21) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIButton (0x6385c10): frame origin: (267, 1) size: (43, 105) [tag=0] UIControl : UIView : UIResponder : NSObject : 
.   .   .   .   .   .   .   UIImageView (0x6386d80): frame origin: (7, 37) size: (29, 31) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIImageView (0x6387eb0): frame origin: (10, 1) size: (300, 10) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   UITableViewCell (0x63860a0): frame origin: (0, 10) size: (320, 46) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIGroupTableViewCellBackground (0x6386de0): frame origin: (9, 0) size: (302, 46) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UITableViewCellContentView (0x63882e0): frame origin: (10, 1) size: (267, 43) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   .   UILabel (0x6385530): frame origin: (10, 0) size: (247, 43) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIButton (0x6385860): frame origin: (267, 1) size: (43, 43) [tag=0] UIControl : UIView : UIResponder : NSObject : 
.   .   .   .   .   .   .   UIImageView (0x6386000): frame origin: (7, 6) size: (29, 31) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIImageView (0x6386450): frame origin: (10, 1) size: (300, 10) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   UIImageView (0x615c020): frame origin: (0, 409) size: (320, 7) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   UIImageView (0x614a810): frame origin: (312, 1) size: (7, 144) [tag=0] UIView : UIResponder : NSObject : 
.   .   UINavigationBar (0x63192c0): frame origin: (0, 20) size: (320, 44) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   UILabel (0x6149360): frame origin: (62, 0) size: (200, 44) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   UILabel (0x61493d0): frame origin: (0, 19) size: (200, 20) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   UILabel (0x615c810): frame origin: (0, -1) size: (200, 24) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   UINavigationItemButtonView (0x6349b80): frame origin: (5, 7) size: (49, 30) [tag=0] UINavigationItemView : UIView : UIResponder : NSObject : 
End of view hierarchy -----------------------------------

Notice above that my table view that I passed in is the first entry that is indented 4 levels in the above listing. This is because I used YES as the boolean parameter, which walks all the way to the top of the view hierarchy before dumping out all of the views.

Here is the code that generates it. First, the InspectView header file:

//
//  InspectView.h
//
 
#define objectString(anObject) [[anObject description] UTF8String]
 
#import <Foundation/Foundation.h>
#import <CoreFoundation/CoreFoundation.h>
 
@interface InspectView : NSObject 
{
}
 
+ (void)dumpViewToLog:(id)viewObj findParent:(BOOL)findTheParent;
+ (NSString *)dumpViewToString:(id)viewObj findParent:(BOOL)findTheParent;
+ (NSString *)dumpViewToString:(id)viewObj level:(int)level;
 
@end

And the implementation file:

//
//  InspectView.m
//
 
#import "InspectView.h"
 
#define THE_LOG NSLog
 
@implementation InspectView
 
+ (void)dumpViewToLog:(id)viewObj findParent:(BOOL)findTheParent
{
    THE_LOG(@"%@", [self dumpViewToString:viewObj findParent:findTheParent]);
}
 
+ (NSString *)dumpViewToString:(id)viewObj findParent:(BOOL)findTheParent 
{    
    NSString *s = @"\nInspect view hierarchy -----------------------------------" ;
 
    // go up to outtermost view.
    if (findTheParent)
    {
        s = [s stringByAppendingFormat:@"\nOriginal view is (0x%x)", viewObj];
 
        while ([viewObj superview]) 
        {
            viewObj = [viewObj superview];
        }
    }
 
    s = [s stringByAppendingString:[self dumpViewToString:viewObj level:0]];
    s = [s stringByAppendingString:@"\nEnd of view hierarchy -----------------------------------"];
    return s;
}
 
+ (NSString *) dumpViewToString:(id)viewObj level:(int)level 
{
    NSString *s = @"\n";
    // indent to show the current level
    for (int i = 0; i < level; i++) 
    {
        s = [s stringByAppendingString:@".   "];
    }
 
    s = [s stringByAppendingFormat:@"%@ (0x%x): frame origin: (%d, %d) size: (%d, %d) [tag=%d] ", 
         [[viewObj class] description], viewObj,
         (int) (((UIView*)viewObj).frame.origin.x), 
         (int) (((UIView*)viewObj).frame.origin.y),
         (int) (((UIView*)viewObj).frame.size.width),
         (int) (((UIView*)viewObj).frame.size.height),
         ((UIView*)viewObj).tag
         ];  // shows the hex address of input view.
    //  s = [s stringByAppendingFormat:@"%@ : ", [[viewObj class] description] ];
 
    id obj = [viewObj superclass];
 
    while (NULL != obj) 
    {
        s = [s stringByAppendingFormat:@"%@ : ", [[obj class] description]];
        obj = [obj superclass];
    }
 
    // recurse for all subviews
    for (UIView *sub in [viewObj subviews]) 
    {
        s = [s stringByAppendingString: [self dumpViewToString:sub level:(level + 1)]];
    }
 
    return s;
}
 
@end

If you combine today’s post with yesterday’s post (delayed block execution), you can do something like this at the end of the viewDidLoad method of your UITableViewController:

[self performBlock:^{ [InspectView dumpViewToLog:self.view findParent:NO]; } afterDelay:1.0f];
[self performBlock:^{ [InspectView dumpViewToLog:self.view findParent:NO]; } afterDelay:11.0f];

The result of this is that you will get a dump of your table view hierarchy after 1 second (this gives the table a chance to get drawn), the user will be able to work with the table view for another 10 seconds, and then another dump. This could help you diagnose any kind of issues you may be having with your table views or table view cells.

As usual, keep in mind that since this is code to be used for debugging purposes, this is not production quality code.

BTW, Happy Birthday to one of the all time great Leafs, Red Kelly.