Nov 202012
 

My Ferret game is using a UITabBarController in part of the UI. Most of the sample code and tutorials I’ve seen have you start with the TabBar template, or have the Tab Bar in the main window. Ferret has a SwitchBoard type icon grid (implemented by Nimbus) and when you press one of the icons you push a view controller that has a tab bar at the bottom.

First time I did this I wrote some pretty awful code. I tried to do the “right” thing, doing the UI in Interface Builder, but I couldn’t hook up each of the Tab Bar’s views in IB, I had to do it programatically. See here?

tabbar

Notice in the Attributes Inspector that I do not have a field for the UIViewController’s nib!

So I wrote code like this:

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
        MeReadOnlyViewController *meViewController = [[MeReadOnlyViewController alloc] initWithNibName:@"MeReadOnlyViewController" bundle:nil];
        [meViewController setImagePickerVC:self];
		MeFotosViewController *fotosViewController = [[MeFotosViewController alloc] initWithNibName:@"MeFotosViewController" bundle:nil];
		MeBadgesViewController *badgesViewController = [[MeBadgesViewController alloc] initWithNibName:@"MeBadgesViewController" bundle:nil];
		MeFriendsViewController *friendsViewController = [[MeFriendsViewController alloc] initWithNibName:@"MeFriendsViewController" bundle:nil];

		NSArray *array = [[NSArray alloc] initWithObjects:meViewController, fotosViewController, badgesViewController, friendsViewController, nil];
		self.viewControllers = array;

        [self.view addSubview:meViewController.view];
		self.selectedViewController = meViewController;
    }
    return self;
}

Now, that’s not horrible. If you’re not building a UITabBarController from IB you’re going to have to do this. But check out that line,

self.viewControllers = array

What’s up with that?

@interface MeTabViewController : UIViewController <UINavigationControllerDelegate>

@property (nonatomic, retain) NSArray *viewControllers;
@property (nonatomic, retain) UIViewController *selectedViewController;

@property (strong, nonatomic) IBOutlet UITabBar *tabBar;
@property (strong, nonatomic) IBOutlet UITabBarItem *meTab;
@property (strong, nonatomic) IBOutlet UITabBarItem *badgesTab;
@property (strong, nonatomic) IBOutlet UITabBarItem *fotosTab;
@property (strong, nonatomic) IBOutlet UITabBarItem *friendsTab;

@end

Now that’s making me really squirm. Can you say “bad code smell?” I need to create my own NSArray of viewControllers, but why do I need a property for each of the UITabBarItems? Here’s why:

- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
    UIViewController *newViewController;

    self.navigationItem.rightBarButtonItem = nil;

    if (item == meTab_) {
        newViewController = [viewControllers_ objectAtIndex:0];

        self.navigationItem.rightBarButtonItem = [self editButtonItem];
    }else if (item == fotosTab_) {
        newViewController = [viewControllers_ objectAtIndex:1];
    } else if (item == badgesTab_) {
        newViewController = [viewControllers_ objectAtIndex:2];
    }else if (item == friendsTab_) {
        newViewController = [viewControllers_ objectAtIndex:3];
    }

    [self.selectedViewController.view removeFromSuperview];
    [self.view addSubview:newViewController.view];

    self.selectedViewController = newViewController;
}

That’s gross. It works, it works just fine but it seems unmaintainable, fragile, and just bad. I building, by hand, a UIViewController which is really just a UITabBarController. So why not just make one of those? I tried in IB, but I couldn’t hook up the UITabBarController to the view. After struggling a bit, and a bit of google, I decided to just do it by hand, it isn’t that hard.

+ (UITabBarController *) createMeTabBarController
{
    //create a UITabBarController object
    UITabBarController *tabBarController=[[UITabBarController alloc]init];

    // create our view controllers
    MeReadOnlyViewController *meViewController = [[MeReadOnlyViewController alloc] initWithNibName:@"MeReadOnlyViewController" bundle:nil];
    meViewController.tabBarItem.image = [UIImage imageNamed:@"me-profile.png"];
    meViewController.title = @"Me";

    MeFotosViewController *fotosViewController = [[MeFotosViewController alloc] initWithNibName:@"MeFotosViewController" bundle:nil];
    fotosViewController.title = @"Fotos";
    fotosViewController.tabBarItem.image = [UIImage imageNamed:@"me-fotos.png"];

    MeBadgesViewController *badgesViewController = [[MeBadgesViewController alloc] initWithNibName:@"MeBadgesViewController" bundle:nil];
    badgesViewController.title = @"Badges";
    badgesViewController.tabBarItem.image = [UIImage imageNamed:@"me-badges.png"];

    MeFriendsViewController *friendsViewController = [[MeFriendsViewController alloc] initWithNibName:@"MeFriendsViewController" bundle:nil];
    friendsViewController.title = @"Friends";
    friendsViewController.tabBarItem.image = [UIImage imageNamed:@"me-friends.png"];

    tabBarController.viewControllers= [NSArray arrayWithObjects:meViewController, fotosViewController, badgesViewController, friendsViewController, nil];

This eliminates the MeTabViewController class and xib completely. Notice how similar the code is to the first code snippet I showed. I still need to build my array of UIViewControllers but now I must also set the icon and title for the UITabBarItems.

May 202012
 

I just learned something…

Why is the first line better than the 2nd line?

@property (nonatomic, weak) NSObject<ExplanationDelegate> * delegate;
@property (nonatomic, weak) id <ExplanationDelegate> delegate;

It so you can write code like this!

    if([_delegate respondsToSelector:@selector(rightButtonAction:)]) {
        [_delegate rightButtonAction];
    }

If you use id instead of NSObject then you can’t, it won’t compile, you get “No known instance method for selector ‘respondsToSelector:'” which can really have you scratching your head!

Mar 142012
 

I didn’t write this code but it is too awesome not to share. I had an NSString and needed to see if it contained another NSString. I found this post: http://stackoverflow.com/questions/2753956/string-contains-string-in-objective-c

and P i‘s answer is just perfect; make a category on NSString:

@interface NSString ( containsCategory )
- (BOOL) containsString: (NSString*) substring;
@end

@implementation NSString ( containsCategory )

- (BOOL) containsString: (NSString*) substring
{    
    NSRange range = [self rangeOfString : substring];
    BOOL found = ( range.location != NSNotFound );
    return found;
}

@end

This has been incorporated into the open source EnkiUtils.

Feb 022012
 

I noticed that my thumbnails didn’t look sharp on my Retina display. This makes sense since I was asking my server for 50 pixels when in fact I wanted 100 pixels. But how to encapsulate that?

I made a quick little method to give me my 50 or 100 dimension:

+ (int) thumbnailSize 
{
    return [EnkiUtilities pixelsForRetina:50];
}

This call the pixelForRetina class method:

+ (float) pixelsForRetina:(float) pixels
{
    if ([[UIScreen mainScreen] enki_isRetina]) {
        return pixels * 2;
    } else {
        return pixels;
    }
}

I really don’t like the hardcoded 2 in there, I really should grab scale from somewhere. And here is the UIScreen class category to help me out with the retina/non-retina distinction.

@implementation UIScreen(EnkiScreenRetinaAdditions)
- (BOOL)enki_isRetina {
    return [self respondsToSelector:@selector(displayLinkWithTarget:selector:)] && (self.scale == 2.0);
}
@end

This has been incorporated into the open source EnkiUtils.

Feb 012012
 

It is easy to draw images in an iOS app, but you can make them special by dropping a frame around them. For example, one of my apps uses the old Poloroid SX-70 type of frame as a branding element, so I’m always dropping UIImages into a frame so they look something like this:

CezanneFramed

This is very easy to do in CocoaTouch.

@interface EnkiUtilities : NSObject
+ (UIImage *) putThumbnailInFotoFrame:(UIImage *)image ;
@end

@implementation EnkiUtilities

+ (UIImage *) putThumbnailInFotoFrame:(UIImage *)image 
{
    UIImage *blankThumb =  [UIImage imageNamed:@"Blank Foto Thumb.png"];
    
    // put the thumbnail into the blank Foto frame
    CGSize size = CGSizeMake(blankThumb.size.width, blankThumb.size.height);
    
    if (UIGraphicsBeginImageContextWithOptions != NULL) {
        UIGraphicsBeginImageContextWithOptions(size, YES, 0.0);
    } else {
        UIGraphicsBeginImageContext(size);
    }
    
    CGPoint point = CGPointMake(0, 0);
    [blankThumb drawAtPoint:point];
    
    point = CGPointMake(3, 3);
    [image drawAtPoint:point];
    
    UIImage* finalImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return finalImage;
}
@end

Note the gross code on line 23 with the hardcoded (3,3). That works for my picture frame, you’ll probably want something different, or better yet, add that as a parameter to the method.

You just call it like this:

        log.thumbnailImage = [EnkiUtilities putThumbnailInFotoFrame: [UIImage imageNamed:@"cezanne.png"]];

Seems simple enough, so how did I resize it to be the correct size? I didn’t! In the real app I’m using Nimbus Kit NINetworkImageView to download my images from the server and Nimbus is handling that for me.

Jan 302012
 

This is deprecated now, it was fun to code it but I’ve since found MBProgressHUD and that is just fine.

I needed to upload image files to my server and this could take several seconds, or more if the cell service is poor, so I wanted to drop a progress bar on top of my dialog. Here’s the code I used to do it:

@interface EnkiProgressBar : UIView

@property (nonatomic, retain)  IBOutlet UIProgressView *progressBar;
@property (nonatomic, retain) IBOutlet UILabel *verbLabel;
@property (strong, nonatomic) IBOutlet UIProgressView *altProgressBar;
@property (nonatomic) CGFloat originY;

- (void) showProgressIndicator;
- (void) setLabel:(NSString *) theLabel;
- (void) setProgressPercent: (float) percent;

- (void) hideDimmedLayer;
- (void) showDimmedBackground;

@end

and the implementation

//
//  EnkiProgressBar.m
//
//  Created by Paul Cezanne on 1/30/12.
//  Copyright (c) 2012 Enki Labs. All rights reserved.
//

#import "EnkiProgressBar.h"

@implementation EnkiProgressBar

- (void) setLabel:(NSString *) theLabel
{
    _verbLabel.text = theLabel;

    if ([theLabel length] ==0) {
        [_altProgressBar setHidden:FALSE];
        [_progressBar setHidden:TRUE];
    } else {
        [_altProgressBar setHidden:TRUE];
        [_progressBar setHidden:FALSE];
    }
}

- (void) showProgressIndicator
{

    self.opaque = NO;

    CGRect pFrame = self.frame;

    pFrame.origin.y  +=  _originY;

    self.frame = pFrame;
    self.progressBar.progress = self.altProgressBar.progress = 0.0;
}

- (void) setProgressPercent: (float) percent
{
    self.progressBar.progress = self.altProgressBar.progress = percent;
}

- (void) showDimmedBackground
{
    self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5f];
}

- (void) hideDimmedLayer
{
    self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.0f];
}

@end

To use it, first load the xib file in your viewDidLoad method:

    NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:@"EnkiProgressBar" owner:self options:nil];
    progressBarView = [subviewArray objectAtIndex:0];

When your app needs to show a progress indicator just create and initialize one:

- (void) showProgressIndicator
{
    self.navigationController.navigationBarHidden = YES;
    [progressBarView setLabel:@"Uploading..."];
    [progressBarView setOriginY:self.view.bounds.origin.y];
    [progressBarView showDimmedBackground];
    [progressBarView showProgressIndicator];
    [self.view addSubview:progressBarView];
}

To remove it:

- (void) hideProgressIndicator
{
    self.navigationController.navigationBarHidden = NO;
    [progressBarView removeFromSuperview];
}

And somehow get a callback when your data is being uploaded. My app was using RestKit to communicate with the server and its callback method is didSendBodyData:

- (void)request:(RKRequest *)request didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
    float percent = (float) totalBytesWritten / (float)totalBytesExpectedToWrite;

    [progressBarView setProgressPercent:percent];
}

And here you can see it in action.

IMG_4088

So, where’s the xib we loaded? Just grab that from github: https://github.com/pcezanne/EnkiProgressBar or just clone it directly from https://github.com/pcezanne/EnkiProgressBar.git

Dec 312011
 

The UILabel is a great widget for displaying multi-line text, it isn’t just for single line entries. But how do you get it to word wrap and have the text at the upper left. Here’s a snippet I use all the time:

@interface UILabel (EnkiExtensions)
- (void)sizeToFitFixedWidth:(CGFloat)fixedWidth;
@end

@implementation UILabel (EnkiExtensions)
- (void)sizeToFitFixedWidth:(CGFloat)fixedWidth
{
    if (fixedWidth < 0) {
        self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, 0);
    } else {
        self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, fixedWidth, 0);
    }
    self.lineBreakMode = UILineBreakModeWordWrap;
    self.numberOfLines = 0;
    [self sizeToFit];
}
@end

It is pretty straightforword to call with only one gotcha:

    helpLabel_.text = @"You need to cut the blue wire and ground the red wire. Do not cut the red wire and ground the blue wire, that would be bad.";
    [helpLabel_ sizeToFitFixedWidth: _helpFPO.frame.size.width ];

redwire

What’s the gotcha? Notice the helpPFO variable? When you design your UI in Interface Builder you placed your UILabel but you also want to place a UIView in the exact same spot. Hook it up to the variable helpFPO. Why not just use _helpLabel’s frame to set the width? If you did that it could shrink over time if the contents changed and you called sizeToFitFixedWidth again. FPO is an old page layout term, it means For Position Only.

Also notice that I’ve only adjusted the width, not the height.

This has been incorporated into the open source EnkiUtils.

Dec 192011
 

This is a pretty classic first iPhone app problem. You have a UITableView with some cells and some of the cells have UITextFields and when you tap the text field the keyboard slides out of the bottom and your textfield is hidden.

There are scores of blogs and tutorials on how to fix this and I thought I’d share my all purpose code for this. It works on both UITableView views as well as views not in a table.

First, the code dump:

+ (void)keyboardWasShown:(NSNotification*)aNotification
                    view:(UIViewController *) view
              scrollView:(UIScrollView *)scrollView
             activeField:(UIView *) activeField
              activeCell:(UITableViewCell *) activeCell
{
    NSDictionary* info = [aNotification userInfo];
    CGRect rawKeyboardRect = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
    
    CGRect properlyRotatedCoords = [view.view.window convertRect:rawKeyboardRect toView:view.view.window.rootViewController.view];
    
    CGSize kbSize = properlyRotatedCoords.size;
    
    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
    scrollView.contentInset = contentInsets;
    scrollView.scrollIndicatorInsets = contentInsets;
    
    CGRect viewRect = view.view.frame;
    
    // If active text field is hidden by keyboard, scroll it so it's visible
    // do it for tables
    if (nil != activeCell) {
        viewRect.size.height -= kbSize.height;
        
        CGPoint origin = activeCell.frame.origin;
        origin.x += activeField.frame.origin.x;
        origin.y += activeField.frame.origin.y;
        origin.y -= scrollView.contentOffset.y;
        
        if (!CGRectContainsPoint(viewRect, origin) ) {
            CGFloat scrollAmount = activeCell.frame.origin.y + activeField.frame.origin.y - 30;          // the -30 is just to make it look better
            CGPoint scrollPoint = CGPointMake(0.0, scrollAmount);
            
            [scrollView setContentOffset:scrollPoint animated:YES];
        }

    } else {  // and do it when you are not in a table
        
        CGRect fieldRect = activeField.frame;
        
        float distanceFromBottom = (viewRect.origin.y + viewRect.size.height) - (fieldRect.origin.y + fieldRect.size.height);
        float jumpNeeded = kbSize.height - distanceFromBottom;
        
        // the -30 is just to make it look better
        [scrollView setContentOffset:CGPointMake(0,jumpNeeded +30) animated:YES];
    }
    
}

And the interface:

@interface EnkiUtilities : NSObject

+ (void)keyboardWasShown:(NSNotification*)aNotification 
                    view:(UIViewController *) view 
              scrollView:(UITableView *)scrollView 
             activeField:(UIView *) activeField
              activeCell:(UITableViewCell *) activeCell;
@end

Notice that it is a class method, not an instance method. I keep a class called EnkiUtilities around to hold useful things. To call it, we first set up our observer:

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWasShown:)
                                                 name:UIKeyboardDidShowNotification object:nil];

And in the keyboardWasShown method we call our utilities method:

- (void)keyboardWasShown:(NSNotification*)aNotification
{
    [EnkiUtilities keyboardWasShown:aNotification view:self scrollView:myTableView activeField:activeField activeCell:activeCell];

}

So what is activeField and activeCell? Lets look at our textViewDidBeginEditing method to see how those are set

-(void)textViewDidBeginEditing:(UITextView *)sender
{           
    activeField = sender;
    
    if ([sender isEqual:descriptionTextView]) {
        activeCell = descriptionCell;
        if (shouldClearDescription) {
            [descriptionTextView initWithLPLStyle:@""];
            shouldClearDescription = false;
        } 
    }else if ([sender isEqual:hintTextView]) {
        activeCell = hintCell;
        if (shouldClearHint) {
            [hintTextView initWithLPLStyle:@""];
            shouldClearHint = false;
        }    
    } else {
        activeCell = nil;
    }
}

This method is called when a UITextField begins editing. I set the activeField to the sender and then I set the activeCell to the cell that corresponds to the field.

The only bit remaining is to restore the view when the keyboard disappears.

- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
    UIEdgeInsets contentInsets = UIEdgeInsetsZero;
    myTableView.contentInset = contentInsets;
    myTableView.scrollIndicatorInsets = contentInsets;
}

When you are working without a UITableView, just put the text fields into a UIScrollView and pass that, not the UITableView, to keyboardWasShown.

This has been incorporated into the open source EnkiUtils.

Dec 062011
 

UITableViewCell has a different background color under iOS4 and iOS5. I wanted to support iOS4 for my app and I need to change the cell’s background color on error and then change it back again. I couldn’t change it back again on both iOS4 and iOS5. Hence this hacky class method

+ (UIColor *)defaultBackgroundColor:(UITableViewCell *)inCell
{
    
    if (NULL == FFUtiltiesBackgroundCellColor) {
        // iOS 4 and iOS5 have different color backgrounds for tabbed group dialogs
        // hence this gross hack. Must put it in a utility class someday.
        // see http://stackoverflow.com/questions/8405644/what-is-the-default-background-color-of-a-uitableviewstylegrouped-styled-uitable
        //
        //backgroundCellColor = usernameCell.contentView.backgroundColor;
        NSString *version = [[UIDevice currentDevice] systemVersion];
        NSString *firstLetter = [version substringToIndex:1]; 
        
        if (firstLetter == @"4") {
            FFUtiltiesBackgroundCellColor = [UIColor whiteColor];
        } else {
            FFUtiltiesBackgroundCellColor = [UIColor colorWithRed:(0xF7/255.0)green:(0xF7/255.0)  blue:(0xF7/255.0) alpha:1.0];
        }
    }

    return FFUtiltiesBackgroundCellColor;
}

Hey, it works. I don’t use this anymore, I no longer see a business case for support iOS4 for my apps. Then again, clients apps can make the case, so I’m certainly not deleting the code!

This has been incorporated into the open source EnkiUtils.