Friday, May 29, 2009

Creating a UIBarButtonItem programmatically

This counts as one of those things that I'll want to do again in the future but will have to re-research it by poring through old code or Googling (yet again). Sometimes for brevity, it isn't ideal to have a View Controller *and* a XIB for every little thing you want to present. The XIB is an extra file and the symbolic links to the controller can be a pain to maintain. To create your own action button (say, in the upper-right corner), use the following code:

- (void)viewDidLoad {
UINavigationBar *navBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
UINavigationItem *navItem = [[UINavigationItem alloc] initWithTitle:@"Load Colors"];
[navBar pushNavigationItem:navItem animated:NO];
UIBarButtonItem *editButton = [[UIBarButtonItem alloc] initWithTitle:@"Edit"
style:UIBarButtonItemStyleBordered
target:self
action:@selector(toggleEdit:)];
navItem.rightBarButtonItem = editButton;

[self.view addSubview:navBar];

[editButton release];
[navItem release];
[navBar release];
[super viewDidLoad];
}

Thursday, May 28, 2009

Using @class and #import

The iPhone SDK contains two directives that use can use to declare other classes in your project. They are:

@class YourClassName;

and

#import "YourClassName.h"

The differences between the two are not clearly explained (in anything I've read) but the impact is very substantial. While many writers dismiss these as interchangeable, they are not! This might be a situational example, but this is how I use those directives.

First, use @class only in the header file. This notifies the compiler that you are instantiating variables for use. For example:

@class YourClassName;
@interface TheCurrentClass {
YourClassName *instanceVar;
}
@property (nonatomic, retain) YourClassName *instanceVar;
@end

While you get a nice way to define your class instance, the @class directive does not provide any of the underlying methods and variables available to that class. To access anything meaningful in your instance, you must use #import in your implementation. For example:

#import "TheCurrentClass.h"
#import "YourClassName.h"
@implementation TheCurrentClass
@synthesize instanceVar;
-(void)viewDidLoad {
YourClassName *tempInstance = [[YourClassName alloc] init];
self.instanceVar = tempInstance;
[tempInstance release];

[self.instanceVar someMethod:value];
}

Again, this is situational, and the actual code will vary depending on your need, but the point is that the #import gives you access to "someMethod:". If you don't use #import in this case, you will get the error I referred to in the previous post.

Unable to find instance method in another class

I'm refactoring my code because I need a better understanding of retain/release of static variables, but that will be another post...

I decided to implement a trick I learned *after* I started ColorMatcher, which was to instantiate between classes using "@class" (as opposed to "#import"). A new compiler warning appeared:

warning: no '-setDialType' method found

I'm not sure about the reasons just yet, but when you see that warning, use #import instead.

Wednesday, May 27, 2009

Determining the dimensions of a text string before display

With 2D graphical rendering, such as Apple's Quartz SDK, you occasionally want to display text. However, the text position is something you do manually and could change, depending on circumstances (for example, if the text is centered on the screen). The short answer, is this is how you do it.

The big epiphany for me was that I was trying too hard to stick purely with Core Graphics and shying away from Objective-C. However, Apple has taken pains to ensure that, even though the syntax and nomenclature is somewhat of a mish-mash, they do interoperate nicely. Here is an example of my final code:

-(void)drawText:(CGContextRef)context text:(NSString*)text color:(UIColor*)color {
UIFont *font = [UIFont systemFontOfSize:24.0];
CGSize size = [text sizeWithFont:font];
CGPoint center = CGPointMake(160.0, 100.0);
CGPoint origin = CGPointMake(center.x - (size.width / 2.0), center.y - (size.height / 2.0));
CGContextSetFillColorWithColor(context, color.CGColor);
[text drawAtPoint:origin withFont:font];
}

It took way too long to get this code. I hope it doesn't happen again... *sigh*

Tuesday, May 26, 2009

Converting a NSString to a char array

I spent the better part of today wrestling with this problem. In fact, I'm the poster of that question. The short of it is that I have have a lot to learn about the retain/release cycle of Cocoa variables. What's weird is that I could swear I was re-initializing the variable each cycle but who am I to argue against a working solution? I'll figure out the root cause someday...

Wednesday, May 13, 2009

Re-initializing a View

When switching between multiple views using a UITabBarController, by default the view is only initialized the very first time (viewDidLoad). If a state changes that impacts a view the second time you visit it, the appropriate method to overload is:

(void)viewWillAppear:(bool)animated;

Tuesday, May 12, 2009

Accessing the Root Controller from a sub view

One of the harder concepts to grasp (as someone who is new to Cocoa and the iPhone SDK) is how to navigate through the class and object hierarchy. Sometimes you feel like a rat in a maze with dead-ends every which way. In one example I wanted to do something seemingly simple: programmatically change from one tab (in a UITabView) to another, as if the user had done so themselves.

Surprisingly, this was a difficult problem to Google because the questions and answers never aligned to match my specific needs. In an unusual burst of insight, combined with some clues from Apple documentation and user posts, I came up with the following technique.

First, in the AppDelegate @interface (the main class), there is the line:

UITabBarController *rootController;

Next, in one of my subview controller interfaces, I have:

ColorMatcherAppDelegate *app;

which provides a pointer from a minor class to the main class. Next, I add the following to the @implementation of that same class:

app = [[UIApplication sharedApplication] delegate];
app.rootController.selectedIndex = 0;

When all is said and done, it only involves a few lines of code, but they sure were hard to find!