Monday, August 31, 2009

Notes on iPhone:Safari ComboBox in an app

One of the things lacking in the iphone is the combobox much like the one available to you when using safari on the iPhone. I decided to implement this functionality into my app, it's not perfect and could still use some more work to be as good as the one in Safari.

Here's a photo of the results. Without the pickerup
Heres one with the picker.

Okay the first thing your going to want is the picture for the drop down.
The basic implementation from a top level perspective is a UITextField that is not enabled. This is a simple approach that will get you what you want but could be better. The following creates the combobox in a given cell, this can be made more generic by making cell into a generic (id*) or (uiview*).


//params
//string default string to show if a user preference isn't already choosen
//field the UITextField you want to assign if you want to save it.
//cell the view you will add the textfield and button to
//action a selector for the uibutton
//fieldstring the user define string which may be empty so i pass it with the default string
- (void)comboboxwith:(NSString*)string withview:(UITextField*)field oncell:(UITableViewCell*)cell withSel:(SEL)action forfield:(NSString*)fieldstring{
//textfield creation
//basic reason for using UITextField it allows you to add a subview to the left or right
//of the text field
field =[[CustomTextField alloc] initWithFrame:CGRectMake(5, 3, self.tableView.frame.size.width-10, 38)];
field.borderStyle = UITextBorderStyleRoundedRect;
field.text = (fieldstring == nil || [fieldstring compare:@""] == 0) ? string : fieldstring;
field.font = [UIFont fontWithName:@"Helvetica" size:24.0];
field.autoresizingMask = UIViewAutoresizingFlexibleWidth;

//add the down arrow for list view
UIImageView *imgview = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"downarrow.png"]];
field.rightView = imgview;
field.rightViewMode = UITextFieldViewModeAlways;
[imgview release];
field.enabled=NO;
[cell addSubview:field];

//uibutton creation on top of uitextfield, plain transparent button that listens to touchs.
UIButton *bnt = [UIButton buttonWithType:UIButtonTypeCustom];
bnt.frame = CGRectMake(5, 3, self.tableView.frame.size.width-10, 38);
bnt.autoresizingMask = UIViewAutoresizingFlexibleWidth;
bnt.backgroundColor = [UIColor clearColor];
[bnt addTarget:self action:action forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:bnt];
}

As for loading the UIPickerView on selection of the combobox, the selector handles to loading of the uipickerview and toolbar with the done button as to exit from the uipickerview. As follows:


//load a uipicker with states, you can give the picker a tag, and a default state if
//loaded from a preference or something.
- (void)selectstate:(id)sender{
[self pickerwith:@"States" withId:0 withdefault:state];
[self adddone];
}

//this loads the uitoolbar and changes its location so that the done button is displayed
//above the uipickerview
- (void)adddone{
UIBarButtonItem *done = [[[UIBarButtonItem alloc]
initWithTitle:@"Done"
style:UIBarButtonItemStyleDone
target:self action:@selector(done:)] autorelease];
UIBarButtonItem *flex = [[[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:self action:nil] autorelease];
self.navigationController.toolbar.barStyle=UIBarStyleBlackTranslucent;
[self.navigationController.toolbar setFrame:CGRectMake(0,self.parentViewController.view.window.frame.size.height-(216+40), 320, 40)];
self.navigationController.toolbar.hidden = NO;
self.toolbarItems = [[[NSArray alloc] initWithObjects:flex, done, nil] autorelease];
}

//creates the actual picker
- (void)pickerwith:(NSString*)string withId:(int)tag withdefault:(NSString*)defaultstring{
//loads the array from a plist file
NSString *bPath = [[NSBundle mainBundle] resourcePath];
NSString *plistFile = [bPath stringByAppendingPathComponent:@"Root.plist"];
NSDictionary *settingsDictionary = [NSDictionary dictionaryWithContentsOfFile:plistFile];
selectionarray = [settingsDictionary objectForKey:string];
[selectionarray retain];

//if picker is not nil close a previous picker
if(picker != nil){
[self done:self];
}

//create picker
picker = [[UIPickerView alloc] initWithFrame:CGRectMake(0, self.parentViewController.view.window.frame.size.height-216, 320, 216)];
picker.delegate=self;
picker.dataSource=self;
picker.showsSelectionIndicator=YES;
picker.autoresizingMask = UIViewAutoresizingFlexibleWidth;

//if defaultstring is defined load the picker at that index.
if(defaultstring != nil ){
NSInteger ind = [selectionarray indexOfObject:defaultstring];
[picker selectRow:ind inComponent:0 animated:NO];
}

//since we are inside a tableview disable scrolling
//better response from picker
self.tableView.scrollEnabled=NO;
picker.tag = tag;
[self.view.window addSubview:picker];
}

//remove picker and toolbar should put toolbar back in place if needed.
//reload data
- (void)done:(id)sender{
self.navigationController.toolbar.hidden = YES;
self.toolbarItems = nil;
[picker removeFromSuperview];
[picker release];
picker=nil;
self.tableView.scrollEnabled=NO;
[self.tableView reloadData];
}

As for the UIPickerView delegates I am not going to cover as you can look that up elsewhere.

A better approach to this problem would be to use a uiview subclass, and put a uitextlabel on the left side and a uiimageview on the right side, do the same as above with the loading of the pictures and adding this view as a subview to the parent view.

Once you do that you have the combobox look without any functionality to do that you have two approaches, the first is implement touchesdidbegin and touchdidend, I could be wrong on function names this is more for future proofing, then use this to determine if your uiview has been touch properly and load up the picker as above. Another apporach is to implement the uiview subclass a a UIControl subclass which allows you to use the following:

-(void) addtarget:self withselector:action forEvent:UIEventType

this makes it a uibutton basicly and its a lot simpler to work around. Once you have it loading up the Picker all you have to do is in the didSelectRow delegate method for the UIPickerView is tell the UITextLabel to show the text currently selected.

I hope this was convient, and informational. The last thing I have to say is for further improvement the next view button should also be implemented.

2 comments:

  1. Gracias, esto es lo que estaba bucando.

    ReplyDelete
  2. self.toolbarItems = [[[NSArray alloc] initWithObjects:flex, done, nil] autorelease];

    can be written as

    self.toolbarItems = [NSArray arrayWithObjects:flex, done, nil];

    ReplyDelete