Saturday, March 13, 2010

Sight Words: Select Suggested Items

The Select Suggested words dialog is a very handy one.  It has some nice features.


  • There is a search bar that allows you to enter text.  Every time a character is entered, a fetchcontroller is updated with a new search criteria.
  • When an item is selected, it will fully populate the search bar.
  • When the add button is selected, it will create a GUI image of the search bar and animate it going towards the add button.
  • It is fairly generic.  It takes as parameters the table, and search key allowing it to hit any single table query for selecting/searching items.  When an item is selected a delegate is called with the selection data.
  • When the keyboard is displayed the list shrinks so that it doesn't go behind the keyboard.
It has a few flaws as well
  • These nice features are embedded in a special purpose class.
  • The add button is a standard button next to the search bar.  This doesn't look as nice as the standard (and easier) adding a button the navigation controller.
  • The .xib file is a problem when using this library.  I had it go out of date, and had to manually copy changes to the main project.  There probably is a better way to send xib files to a main project, but I haven't figured it out yet.



The original source and .xib file can be found at:

Original XIB file
Some Choice tidbits:
Shrinking the view


In viewDidLoad place the following code:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardAppearing:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDisappearing:) name:UIKeyboardWillHideNotification object:nil];

and then add the following two methods.   Note that suggestions is the outlet for the list being displayed.

- (void)keyboardDisappearing:(NSNotification *)notification {
suggestions.frame=originalSize;
}

- (void)keyboardAppearing:(NSNotification *)notification {
NSDictionary *keys=[notification  userInfo];
NSValue *value=[keys objectForKey:UIKeyboardBoundsUserInfoKey];
CGRect bounds;
[value getValue:&bounds];
CGRect myFrame=[suggestions frame];
originalSize=myFrame;
suggestions.frame=CGRectMake(myFrame.origin.x,myFrame.origin.y,myFrame.size.width,
myFrame.size.height-bounds.size.height);
}






Delegating item creation and actions:
By defining a simple protocol and ensuring a delegate exists we can delegate out the selection of an
existing item and also the actual action that takes place upon selection.

@protocol SuggesionActionHandler<NSObject>

- (id)   createNewItem:(NSString *) searchValue;
- (void) doActionForItem:(id) item;

@end

Adding an item

Adding an item: Note that this combines the animation and action into the same area.  It really should be separated out.  I'm not sure I like how I search for the currently existing item.  At the very least it should be broken out into another method.

- (IBAction) addCurrentItem:(id) sender
{
NSManagedObject *matchingItem=nil;  
NSString *currentText=searchBar.text;
UIGraphicsBeginImageContext(searchBar.bounds.size);
[searchBar.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

UIImageView *imageView=[[UIImageView alloc] initWithImage :viewImage];
imageView.frame=searchBar.frame;
[self.view addSubview:imageView];
[UIView beginAnimations:nil context:imageView];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.25];
[imageView setFrame:addButton.frame];

// Search for the existing item if one.
if (fetchController!=nil)
{
if (fetchController.sections!=nil)
{
for (id<NSFetchedResultsSectionInfo> sectionInfo in fetchController.sections)
{
for (NSManagedObject *object in [sectionInfo objects])
{
NSString *keyForSearch=[object valueForKey:self.searchVariable];
if ([keyForSearch isEqualToString: currentText])

{
matchingItem=object;
break;
}
}
if (matchingItem!=nil)
break;
}
}
}
if (delegate!=nil)
{
if (matchingItem==nil)
{
matchingItem=[delegate createNewItem:currentText];
}
[delegate doActionForItem:matchingItem];
}
else
{
NSLog(@"Delegate is nil, no action can be performed ");
}


[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
[UIView commitAnimations];

}


Updating a Fetch controller with a search predicate:
This was slightly tricky, since the examples assumed a hardcoded search variable.  I had to format the string twice so that I could build the format string with the field name prior to doing substitution.




NSFetchRequest *request=[[NSFetchRequest alloc] init];


NSEntityDescription *entity=[NSEntityDescription entityForName:managedTable 
 
  inManagedObjectContext:coreControl.managedObjectContext];
[request setEntity:entity];
[request setFetchBatchSize:20];
NSSortDescriptor *sortDesc=[[NSSortDescriptor alloc] initWithKey:searchVariable ascending:YES];
NSArray *sortDescs=[[NSArray alloc] initWithObjects:sortDesc,nil];
NSString *searchString=[searchText stringByAppendingString:@"*"];
NSString *predicateFormat=[[NSString allocinitWithFormat:@"%@ like[cd] %%@",searchVariable];
NSLog(@"Searching table %@ %@ for %@",managedTable,searchVariable,searchString);
NSPredicate *predicate = [NSPredicate
  predicateWithFormat:predicateFormat,
 
  searchString];
    NSLog(@"Pred Desc %@", [predicate description]);
[request setPredicate:predicate];
[request setSortDescriptors:sortDescs];
if (fetchController!=nil)
{
  [fetchController release];
}
self.fetchController=[[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:coreControl.managedObjectContext sectionNameKeyPath:nil cacheName:managedTable];

self.fetchController.delegate=self;
NSError *error;
[self.fetchController performFetch:&error] ;
// NSLog(@"Unresolved error %@", error);

// NSLog(@"Fetch count %d",fetchController.sections);
[self.suggestions reloadData];
[predicateFormat release];
[request release];
[sortDesc release];
[sortDescs release];

No comments:

Post a Comment