Thursday, October 28, 2010

EGOTableViewPullRefresh for multiple UITableViews

Wanted to add a "Pull To Refresh" feature similar to Tweetie2, so i looked around to see if there was any reusable code (screenshot from Zattr).

Cocoanetics (ex-Dr. Touch) has a good article explaining"pull to refresh" internal workings, well worth investing the time and effort to read. Recommended! However he's solution was not reusable for me, since I don't have UITableViewController. Fortunately he referenced Enormego's solution using UITableViews, including a walk-through with changes.

Wednesday, October 20, 2010

How to Make Round Corners for UITableViewCell

Sometimes you need round border around one or more UITableViewCell. Or maybe a button look-a-like without going through the trouble of actually making one. Here's a quick way:

First of all, you MUST include QuartzCore header, else meet some interesting error notes:
#import <QuartzCore/QuartzCore.h>
Then add this code after your cell initialization routine, usually at cellForRowAtIndexPath:
CALayer *layer = cell.layer;
layer.borderColor = [[UIColor blueColor] CGColor];
layer.borderWidth = 3.0f;
layer.cornerRadius = 20.0f;
Like many things in iOS SDK, you get to a certain point with relative ease - and this is about it. If you want to have more control over your "bubble button" you either dig deep into Quartz sample code or implement something which looks same in a completely different way.

For example I replaced this (got a bit further with carefully positioned UIViews inside UITableViewCell) with hardcoded bitmaps and scaling them nicely.

Thursday, October 14, 2010

How to Control UISearchBar Background Color

When you have a list, you want to search it. How would you do that? No, don't code it by yourself. Done it, removed it.

By default each UITableView comes with search support via a built-in UISearchDisplayController. You just add and connect UISearchBar to your table in Interface Builder and provide some delegate methods to make it work. Pretty easy:
self.searchDisplayController.delegate =
self.searchDisplayController.searchBar.delegate =
self.searchDisplayController.searchResultsDataSource =
self.searchDisplayController.searchResultsDelegate = self.mySearchObject;
Controlling how your UISearchBar looks like is a bit more difficult. If you want to show search box and some other component(s) side by side, you can do it by putting UIToolbar at back and a short UISearchBar on top of it. By default their backgrounds are different, looking pretty ugly together. Like a hack, what it really is.

You can fix that by doing two things. First get rid of default background:
for (UIView *view in self.searchBar.subviews)
{
   if ([view isKindOfClass:NSClassFromString
     (@"UISearchBarBackground")])
   {
     [view removeFromSuperview];
     break;
   }
}
...and the second thing? Something that will be extremely hard to find out, unless you get it right by accident? You have to set UISearchBar style Black Translucent!

Sad to think how many hours I wasted experimenting Things That Will Not Work, just to find out that kind of miniscule solution. Also bet that this will not work one year from now...

Wednesday, October 13, 2010

Custom Data Formatter for NSIndexPath

Have been using UITableViews for a long time, always complained to myself why cannot I see section and row right away in debugger or variable popup. Something like with CGRect or CGSize. Well, this morning I was doing something completely different. Suddenly solving NSIndexPath debug problem was the much easier task:
section={(int)[$VAR section]}, row={(int)[$VAR row]}
Start debugging anything with a local NSIndexPath variable, even a temporary dummy one. Put breakpoint right after it. When debugger stops, double click on debugger window summary row for NSIndexPath variable and paste the string above there. Seems to stick even after restarting Xcode.

Now I can continue fixing The Real Problem.

Monday, October 11, 2010

How to Find Out Width of Truncated UILabel Text

Found out my tip "How to Float an Icon after UILabel" worked only in positive scenarios. If your text string is longer than UILabel width, icon could have been pushed out of sight. In worst case nobody would ever have seen the icon!
// Title
label = (UILabel *)[cell viewWithTag:1];
label.text = @"This is a very long title which is too long";

// Icon
image = (UIImageView *)[cell viewWithTag:10];
image.image = [UIImage imageNamed:@"logo.png"];
Some measurements of different methods, just to check what is available:
CGSize frame1 = label.frame;
// 205x21
CGSize frame2 = [label sizeThatFits:label.frame.size];
// 291x19
CGSize frame3 = [label.text sizeWithFont:label.font constrainedToSize:label.frame.size];
// 163x19
CGSize frame4 = [label.text sizeWithFont:label.font constrainedToSize:label.frame.size lineBreakMode:label.lineBreakMode];
// 203x19
CGSize frame5 = [label.text sizeWithFont:label.font forWidth:label.frame.size.width lineBreakMode:label.lineBreakMode];
// 203x19
Next icon positioning. Here you should have calculations to compensate for different icon sizes (in case all of your icons are not same size):
// Icon positioning
CGSize frame = image.frame;
frame.origin.x = label.frame.origin.x + frame5.width + 4;
image.frame = frame;
Ok, now that's perfect (until next defect report)!

Thursday, October 7, 2010

How to Get Rid of Keyboard with a Tap on Background part2

There was a nice way to remove keyboard by using UITextView, but that has stopped working with iOS4. At least I got such a defect report for beta application. This time I will remove keyboard using UIButton, let's see how long that will work:
- (IBAction)removeKeyboard
{
    [textField1 resignFirstResponder];
    [textField2 resignFirstResponder];
}
Open your view in Interface Builder, create new custom full screen UIButton to cover everything, select UITextFields and UIButtons (etc.) one by one and "Send to Front" via Layout menu. Connect UIButton "Touch Up Inside" to your callback and that's it.

Nice, easy, simple, fast - what more can you ask for!

Wednesday, October 6, 2010

How to Float an Icon after UILabel

Want to place a floating icon right after a variable lenght text string - using Interface Builder? Wonderful helper that IB tool or so I thought...

First create a UITableViewCell template with IB, put there a fixed lenght UILabel followed by a UIImageView. No problems, their values are easy to setup at cellForRowAtIndexPath with view tag id numbers:
UILabel *label = nil;
UIImageView *image = nil;
CGRect frame;

// Title
label = (UILabel *)[cell viewWithTag:1];
label.text = @"Title text";
But how to "float" that image right after text string? UILabel is fixed sized, so that won't help. Have to check the size of label.text instead:
// Icon
image = (UIImageView *)[cell viewWithTag:10];
image.image = [UIImage imageNamed:@"logo.png"];

// Icon positioning
frame = image.frame;
CGSize f = [label sizeThatFits:label.frame.size];
frame.origin.x = label.frame.origin.x + f.width + 4;
image.frame = frame;
That's it, very simple! Just beware that your tag numbers MUST be unique.

I had a problem that Interface Builder wanted to set UITableViewCell tag same as my image tag. No errors, no warnings, no indications of problem - except that I couldn't get my image frame no matter how much I banged head into brickwall! Removing tag didn't help, it just automagically came back. Ouch!

Tuesday, October 5, 2010

How to Disable UITabBarItems but Only Temporarily

User is allowed to use only one UITabBar item, until he has done something e.g. made a selection on list. Other tabs are available after that. Access to tabs can be blocked again e.g. if user chooses to delete current "entry".

Tab access control is done in AppDelegate, since it owns UITabBarController and UITabBarItems. Just set up delegate handling - and that's the key: either there is a delegate to block tabs or there is none for free access:
@interface MyAppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate>
{
   UITabBarController *tabBarController;
}
...
- (BOOL)tabBarController:(UITabBarController *)aTabBarController
shouldSelectViewController:(UIViewController *)viewController
{
  if (
    ([aTabBarController.viewControllers objectAtIndex:1] == viewController) ||
    ([aTabBarController.viewControllers objectAtIndex:2] == viewController)
  )
  {
    // Disable switch to tab 1 and 2
    // Check: otherViewController to enable
    // Check: SomeViewObject to disable
    return NO;
  }
  else
  {
    // Tab ok at index 0
    return YES;
  }
}
Access control can be enabled in any other class/file:
- (void)blockTabAccess
{
  MyAppDelegate *app = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
  app.tabBarController.delegate = app;
  [app SwitchToTab:kTabFirst];
}
Access control can be disabled in any other class/file:
- (void)releaseTabAccess
{
  MyAppDelegate *app = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
  app.tabBarController.delegate = nil;
  [app SwitchToTab:kTabSecond];
}
Some consider it poor design to access AppDelegate directly from "anywhere" and further more to use it to control application state. My defense are the classic a) it's easy b) it's fast c) it works and d) sometimes central control place is a good thing.

Btw this works only as long as you don't need UITabBarControllerDelegate for any other purpose. You have been warned.