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.