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.

Running a block after a delay

So I was looking for a way to easily run a block of Objective-C code after a set delay time. I found a few ways to do it, but one blog post I found encapsulated it pretty well. Here is the URL of that blog post:

Delayed Blocks in Objective-C (link redacted)

Using the NSObject category from the blog post, I can now run my block after a delay by using something like this:

[self performBlock:^{ [self myMethod]; } afterDelay:2.0f];

This will come in handy for my next blog post, which I will try to do over the weekend. (Hint: think of unrolling view hierarchies…)

BTW, Happy Birthday to Jack Lambert, one of the greatest Kent State alumni ever.

Batter vs. Pitcher app released

Well, I have finally released my Batter vs. Pitcher app that I have been working on for months now. The app is all about baseball statistics, so if you are a fan of baseball, please check it out:

Batter vs. Pitcher

BTW, Happy Birthday to John Kundla, former NBA coaching great. (I am not an NBA fan, but I could not find anyone I wanted to mention on the Wikipedia site for July 3, and his entry was the oldest on the births list that had not passed away.)

A very nice open-source progress display

I found an awesome progress display that I have implemented in one of my iPhone apps. It is dead simple to use, does exactly what it should do and nothing more, and it is also open-source, which is good for developers on a budget like me.

This project by Matej Bukovinski is on github, here is the link:

MBProgressHUD

I found my way to this project through the awesome [iOS developer:tips]; web site, here is the link to their posting:

iOS Open Source : Heads Up Display with MBProgressHUD

BTW, Happy Birthday to Stanley Clarke. I have a ticket for when Return To Forever plays here in Columbus in August, I can’t wait.

Balance

Check out this code I was using for calculating text widths in my iPhone app’s PDF generator class (the first comment with the asterisks was added by me for this posting):

- (CGFloat)calculateTextWidth:(NSString *)text 
{ 
    // ********** do not use this method, see below for the fix **********
    CGSize fullSize = [UIScreen mainScreen].applicationFrame.size; 
    UIGraphicsBeginImageContext(fullSize);
    CGContextRef context = UIGraphicsGetCurrentContext();
 
    // calculate the text size
    CGContextSelectFont(context, [fontName UTF8String], fontSize, kCGEncodingMacRoman);
    CGContextSetTextMatrix(context, CGAffineTransformMakeScale(1.0, -1.0));
    CGContextSetTextDrawingMode(context, kCGTextInvisible);
 
    // measure the text
    CGPoint initialTextPosition = CGContextGetTextPosition(context);
    CGContextShowTextAtPoint(context, 0, 0, [text cStringUsingEncoding:NSASCIIStringEncoding], text.length);
    CGPoint finalTextPosition = CGContextGetTextPosition(context);
 
    return finalTextPosition.x - initialTextPosition.x;
}

Pretty awesome, huh? Well, it worked awesome, my right justified text labels were all lining up great. The problem was that I could do one PDF in the app, maybe two, and the app would throw all kinds of memory warnings and then crash.

Well, as it turns out, it is a good idea to use a UIGraphicsEndImageContext() call when you use a UIGraphicsBeginImageContext() call. After I did this, all was right with the world.

Here is the finished product:

- (CGFloat)calculateTextWidth:(NSString *)text 
{ 
    CGSize fullSize = [UIScreen mainScreen].applicationFrame.size; 
    UIGraphicsBeginImageContext(fullSize);
    CGContextRef context = UIGraphicsGetCurrentContext();
 
    // calculate the text size
    CGContextSelectFont(context, [fontName UTF8String], fontSize, kCGEncodingMacRoman);
    CGContextSetTextMatrix(context, CGAffineTransformMakeScale(1.0, -1.0));
    CGContextSetTextDrawingMode(context, kCGTextInvisible);
 
    // measure the text
    CGPoint initialTextPosition = CGContextGetTextPosition(context);
    CGContextShowTextAtPoint(context, 0, 0, [text cStringUsingEncoding:NSASCIIStringEncoding], text.length);
    CGPoint finalTextPosition = CGContextGetTextPosition(context);
 
    UIGraphicsEndImageContext();  // !!!!!
 
    return finalTextPosition.x - initialTextPosition.x;
}

BTW, Happy Birthday to Zuleikha Robinson. I miss the boys (and Yves, obviously) from The Lone Gunmen, I wish the show could have gone more than the 1/2 season it lasted.

How to Move Your App to the Cloud (CONDG meeting, June 23, 2011)

Brian Prince gave a presentation on Windows Azure at the Central Ohio .NET Developer Group meeting on June 23, 2011. The presentation was mostly talking the process of moving existing applications to Azure, and the pitfalls that can arise when doing so. It was a very interesting presentation, and very appropriate since the company I am now working at is looking at creating some cloud based services.

BTW, Happy 99th Birthday to Alan Turing. OK, he is not currently among the living, but Happy Birthday to him nonetheless.

iPhone/iOS development utilities

I have really been liking a couple of freeware iPhone, iPod touch, iPad, and iOS Simulator tools that I found. They both run great on my iMac, and are super simple to use, while providing some great missing functionality that the Xcode tools do not have.

The first is iExplorer from Macroplant, which allows you to spelunk through the file system of a device connected to your system. It works with stock devices as well as jailbroken devices. Here is the link to download this application:

iExplorer

The other tool is iOS-Simulator Cropper from Curious Times. This tool take screen shots from the iOS Simulator and, shockingly enough, automatically crops them down so that you can use them in your marketing materials or for uploading to the App Store. Here is the link to download this application:

iOS-Simulator Cropper (link redacted, URL not available)

EDIT: I changed the link to iExplorer (the application formerly known as iPhone Explorer), so the link above should not be broken any more.

Garage Band loop browser acting up

This is not so much a development issue as just a general usability issue, but it had frustrated me for a while and I am glad I got the issue behind me.

The problem was that my Garage Band v6 (2011 version) had some problems with the loops. When I would fire up the program, some of the loops in the loop browser showed up sort of grayed out with an arrow next to them. When I click on one of the grayed arrowed loops, I would get a message about how I needed to use Software Update to download the loops onto my computer. However, when I would go to Software Update, it would insist that there were no updates available. Also, when I went back into Garage Band to do the same thing again, it would respond with this message:

You have already initiated the installation of additional content for GarageBand via Software Update. For additional information please open the ‘Software Update’ application.

The battle of wits has begun.

Finally after trying to uninstall and reinstall a couple of times, and after looking through umpteen dozen posts on how this fixed it or that fixed it, I came upon this post on Apple’s web site:

Update the loop library (this link is dead as of 2/23/2012)

Finally I have slain the ROUS that was my loop browser issue.

EDIT:

The link above appears to be dead as of February 23, 2012, a quick Google search for “update the loop library” with specifying the site of apple.com yielded this link on Apple’s web site:

Update the loop library (http://support.apple.com/kb/PH1936) (this link is dead as of 1/8/2017)

I do not recall the exact contents of the now dead link, but from what I remember, I think that link and this one recommended very similar steps of actions.