Archive for the ‘iPhone’ Category.

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.

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

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.

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 iPhone Explorer 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:

iPhone Explorer

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

objc_msgSend EXC_BAD_ACCESS error after moving operations to a GCD block

The subject says it all. After trying to improve the responsiveness of my app by using a Grand Central Dispatch block to run some code asynchronously, the code in my main queue GCD block was failing with an objc_msgSend EXC_BAD_ACCESS error. Bugger.

The error itself typically describes sending a message to an object that has been released and is no longer valid, but I am not sure how that could have happened just by moving some operations inside of GCD blocks.

The way that I fixed this was to go into the diagnostics tab in the Edit Scheme window, and I turned on the Enabled Guard Malloc option. Then, when I ran the app, it showed me more specifically where the issue was. The problem turned out to be that I was using an autorelease array that was becoming invalid as a result of going into and coming out of the GCD blocks. I switched the array to a retained object, and everything started working fine again.

Just don’t forget to turn off the Enable Guard Malloc when you are done testing.

And if you have not yet checked out the coolness of Grand Central Dispatch, you are missing out on a valuable tool.

UILabel sizeToFit malfunction

For some reason, my use of UIView’s sizeToFit method was not working as expected. What I got when I tried to send my UILabel controls through this method was that they ended up being sized down too far, and as a result text was either eliminated from the label, or an ellipsis appeared at the end. I tried just about every combination of UILineBreakMode and numberOfLines I could think of, with no combination fixing the problem.

So what I ended up doing was to just manipulate the UILabel’s frame in the table view’s cellForRowAtIndexPath method. Here is a code snippet:

if (feedIndex == SECTION_CUSTOMER_AND_JOB_INFO) 
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CELL_CUSTOMER_AND_JOB_INFO];
 
    if (cell == nil) 
    {
        [[NSBundle mainBundle] loadNibNamed:@"SummaryCustomerAndJobInfoCell" owner:self options:NULL];
        cell = cellCustomerAndJobInfo;
    }
 
    // the next line uses the tag property of the label as set in the nib file
    UILabel *lbl = (UILabel *)[cell viewWithTag:1];
 
    NSString *text = @"Set your multiple line\ntext to display here...";
    CGSize constraint = CGSizeMake(lbl.frame.size.width, 20000.0f);
    CGSize size = [text sizeWithFont:lbl.font constrainedToSize:constraint
                       lineBreakMode:UILineBreakModeWordWrap];
 
    lbl.text = text;
    [lbl setFrame:CGRectMake(lbl.frame.origin.x, lbl.frame.origin.y, 
                                      lbl.frame.size.width, size.height)];
 
    return cell;
}

By the way, don’t forget to do the same kind of size calculations in your heightForRowAtIndexPath delegate method.

This past Wednesday was a day of great sadness for me, as I had to put my 15 year old schipperke to sleep. Good bye Captain Kirk, you were an ornery little cuss but we loved you anyway. :’(