Showing posts with label sight words. Show all posts
Showing posts with label sight words. Show all posts

Thursday, April 22, 2010

Sight Words: Workflow and refactoring

Refactoring and extra functionality

Now that I have added the ability to merge words from multiple lists into my current list, I want the ability to merge words from my current list INTO other lists.   While doing this I'm also going to refractor the current functions so that the word selection stuff is in one place. 

In my previous code I had two routines specifically for the merge from lists functionality, I pull the common code out, and made two new routines that take parameters.  One routine that selects a set of words and then calls another function, and a second routine that selects a set of lists and calls a set of routines.  I also renamed the callback routines so that the workflow is explicit.

A case could be made for factoring this into a separate class, but I don't think it's necessary, all the context information is present in this class, and it's not a lot of code. 

Selecting lists

Note that I now take the label for the next action, and the function to be called as parameters.
- (void) doWordListSelection:(NSString*)nextAction nextFunction:(SEL) action
{
JLTableContainer *container=
[JLTableContainer createFetchControlledTable:nil 
  forEntity:@"WordList forSimpleKey:@"ListName" 
 inContext: [SightWordState Instance].control.managedObjectContext 
createSectionsBy:nil 
controlledBy:nil];
[container updatePredicate:nil];
UISelectionMaster * selector=[[UISelectionMaster alloc] init];
selector.listToSelect=container;
[self.navigationController pushViewController:selector animated:true];
[selector startSelection];
[selector showToolbar:nextAction target:self action:action];
self.listSelectDialog=selector;
}

Selecting words

- (void) doWordSelection:(NSString*)nextAction nextFunction:(SEL) action wordSet:(NSSet*) set
{
JLTableContainer *container=
[JLTableContainer createSetController:nil forList:set sortKey:@"wordName"
inContext:[SightWordState Instance].control.managedObjectContext
controlledBy:nil];
UISelectionMaster * selector=[[UISelectionMaster alloc] init];
selector.listToSelect=container;
[self.navigationController pushViewController:selector animated:true];
[selector startSelection];
self.wordSelectDialog=selector;
[selector showToolbar:nextAction target:self action:action];
}

The merge words from multiple lists into the current workflow

Note that I modified the names to violate standard coding standards a bit.  I want to make the purpose and workflow very clear.  I was loosing track of which routine was which, and this way it is explicit:

- (IBAction) copyFromLists1_selectLists:(id) sender
{
[self doWordListSelection:@"Select Lists" nextFunction:@selector(copyFromLists2_selectWords:)];
}
- (IBAction) copyFromLists2_selectWords:(id) sender
{
NSSet *selectedLists=[listSelectDialog currentSelectedItems];
NSMutableSet *words=[[NSMutableSet alloc]init];

for (WordList *list in selectedLists)
{
[words unionSet:list.containedWords];
}
[self doWordSelection:@"Add Words" nextFunction:@selector(copyFromLists3_doCopy:) wordSet:words];

}
// the final action.
- (IBAction) copyFromLists3_doCopy:(id) sender
{
[currentWordList addContainedWords:[wordSelectDialog currentSelectedItems]];
[self.navigationController popToViewController:self animated:true ];
self.wordSelectDialog=nil;
self.listSelectDialog=nil;
[[SightWordState Instance] save];
}
 
 

The merge words from the current list into multiple lists workflow

- (IBAction) mergeToLists1_selectWords:(id) sender
{
[self doWordSelection:@"Merge To Lists" nextFunction:@selector(mergeToLists2_selectLists:) wordSet:currentWordList.containedWords];
}

- (IBAction) mergeToLists2_selectLists:(id) sender
{
[self doWordListSelection:@"Do Merge" nextFunction:@selector(mergeToLists3_doMerge:)]; 
}
 
- (IBAction) mergeToLists3_doMerge:(id) sender
{
for (WordList *wordList in [listSelectDialog currentSelectedItems])
{
[wordList addContainedWords:[wordSelectDialog currentSelectedItems]]; 
}
[self.navigationController popToViewController:self animated:true ];
self.wordSelectDialog=nil;
self.listSelectDialog=nil;
[[SightWordState Instance] save];
}
A few things to note when renaming stuff, the : at the end of the selector is important, and the refactoring of the routines did not pick up the selectors when I did it.   It took a few debugging cycles to get it right.  Refactoring mostly fixed the nibs, but I had to do some reconnections to get everything working correctly.

Tuesday, April 20, 2010

Fetched LIst Controller: Using a predicate on a relationship.

In my previous blog entry I was pretty proud of figuring out the way to select multiple word lists and then build a predicate that found the union of all the words belong to those lists.  It wasn't clear from the documentation and websearches how to do it.   It worked great on the simulator.  However when I put it on the device, it was extremely slow (which is probably why there wasn't great documentation on how to do it).

I ended up just abandoning using a fetched data controller for this information, and just building a complete set that contains all the words I want.  I modified:


JLTableContainer *container=
[JLTableContainer createFetchControlledTable:nil    forEntity:@"WordInformation" forSimpleKey:@"wordName"    inContext: [SightWordState Instance].control.managedObjectContext 
createSectionsBy:nil  controlledBy:nil];
NSArray *selectedLists=[listSelectDialog currentSelectedItems:@"ListName"];
NSPredicate *predicate= [NSPredicate predicateWithFormat:@"ANY containedIn in %@",selectedLists ]; 
[container updatePredicate:predicate];
UISelectionMaster * selector=[[UISelectionMaster alloc] init];
selector.listToSelect=container;
[self.navigationController pushViewController:selector animated:true];
[selector startSelection];
self.wordSelectDialog=selector;
[selector showToolbar:@"Add Words" target:self action:@selector(wordsSelected:)];
To:
- (IBAction) listsSelected:(id) sender
{

NSArray *selectedLists=[listSelectDialog currentSelectedItems:@"ListName"];
NSMutableSet *words=[[NSMutableSet alloc]init];

for (WordList *list in selectedLists)
{
[words unionSet:list.containedWords];
}
JLTableContainer *container=
[JLTableContainer createSetController:nil forList:words sortKey:@"wordName"
inContext:[SightWordState Instance].control.managedObjectContext
controlledBy:nil];
UISelectionMaster * selector=[[UISelectionMaster alloc] init];
selector.listToSelect=container;
[self.navigationController pushViewController:selector animated:true];

[selector startSelection];
self.wordSelectDialog=selector;
[selector showToolbar:@"Add Words" target:self action:@selector(wordsSelected:)];

}
I didn't get timing numbers, but there was a perceptibly strong performance improvement for the second set of code.  This is one of the reasons I'm writing a general library, I can change how something is implemented with (hopefully) minimal impact to the remainder of the code.


Sunday, April 18, 2010

Navigation: Fixing toolbars

Toolbars on navigation items


In my previous version of the code, I was loosing my toolbar items when I changed views.  A quick perusal of the documentation reveled that instead of manually configuring the toolbar, I should be setting the toolbarItems property on my class.  (see
http://developer.apple.com/iphone/library/documentation/UIKit/Reference/UINavigationController_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006934-CH3-SW30

So I changed my toolbar from:

- (void) showToolbar:(NSString *) buttonName target:(id) target action:(SEL) action
{
[self.navigationController setToolbarHidden:false];
self.toolbarItems =items;
    UIBarButtonItem *item= [[UIBarButtonItem alloc] initWithTitle:buttonName style:UIBarButtonItemStyleBordered target:target action:action];
NSArray *items = [NSArray arrayWithObjects: item, nil];
self.navigationController.toolbar.items =items;
[item release];
}

So I changed my toolbar to:

- (void) showToolbar:(NSString *) buttonName target:(id) target action:(SEL) action
{
[self.navigationController setToolbarHidden:false];
    UIBarButtonItem *item= [[UIBarButtonItem alloc] initWithTitle:buttonName style:UIBarButtonItemStyleBordered target:target action:action];
NSArray *items = [NSArray arrayWithObjects: item, nil];
self.toolbarItems =items;
[item release];
}

Now it works great, of course now that I still have the first level item on my toolbar, my code crashes when they choose it, because I am maintaining a separate callback for which dialog is active. 


To fix this, instead of having one current dialog variable, I just added two variables listSelectDialog and wordSelectDialog.  Now the code works great.






Saturday, April 17, 2010

List Selection: Creating a chain of events and predicates across relationships.

The previous code can be modified to create a chain of events:

  1. The user is presented with a set of word lists.  They select which ones they want.
  2. The user is then presented with all the words in those word lists.  The select which ones they want.
  3. Finally the user accepts all the words selected and those words are merged to the current word list.

I accomplished all of this in my main list editor, a generic class is used to select the nodes:

Modify selection list to expose selected results

First I modified my UISelectionMaster to expose the currently selected items.  

- (NSArray *) currentSelectedItems:(NSString*) sortKey;



- (NSArray *) currentSelectedItems:(NSString *)sortKey
{
if (self.selectionController==nil)
return nil;
NSArray *returnValue=[self.selectionController.selectedItems getSortedArray:sortKey];
return returnValue;
}

Simple, and I might later modify it to just return a set.

Add first step in the chain

From the previous blog entry, but I've added a member property to contain the currently active dialog.  This way the callback can use it.
- (IBAction) mergeFromLists:(id) sender
{
JLTableContainer *container=
[JLTableContainer createFetchControlledTable:nil
  forEntity:@"WordList"  
forSimpleKey:@"ListName" 
   inContext: [SightWordState Instance].control.managedObjectContext 
createSectionsBy:nil 
controlledBy:nil];
[container updatePredicate:nil];
UISelectionMaster * selector=[[UISelectionMaster alloc] init];
selector.listToSelect=container;
[self.navigationController pushViewController:selector animated:true];
[selector startSelection];
[selector showToolbar:@"Select Lists" target:self action:@selector(listsSelected:)];

self.currentDialog=selector;
}


Feed results of first step into another instance of the same selection 


The tricky part was building my predicate from the previous selected words.  My object model has few elements, but it does have a two way relationship between words and word lists.  It turns out that given a list of ManagedObjects, you can feed that into a predicate against a relationship.   


I'm not sure if this is more or less efficient than iterating over all the words in all the lists and making a new list.  For sight words the performance difference will not be relevant.

- (IBAction) listsSelected:(id) sender
{


JLTableContainer *container=

[JLTableContainer createFetchControlledTable:nil
  forEntity:@"WordInformation" forSimpleKey:@"wordName" 
   inContext: [SightWordState Instance].control.managedObjectContext 
createSectionsBy:nil 
controlledBy:nil];
NSArray *selectedLists=[currentDialog currentSelectedItems:@"ListName"];
NSPredicate *predicate= [NSPredicate predicateWithFormat:@"ANY containedIn in %@",selectedLists ]; 
[container updatePredicate:predicate];
UISelectionMaster * selector=[[UISelectionMaster alloc] init];
selector.listToSelect=container;
[self.navigationController pushViewController:selector animated:true];
[selector startSelection];
self.currentDialog=selector;
[selector showToolbar:@"Add Words" target:self action:@selector(wordsSelected:)];

}



Take the results of the second selection and use them, pop back to the top of the list


Note that the really neat popToViewController allows me to pop multiple layers of dialogs back to the starting one.   Its pretty handy.   Once again I suspect this is not the most efficient way to use core data, however I'm still learning about core data, and for this application it shouldn't matter.

- (IBAction) wordsSelected:(id) sender
{
    for (WordInformation *wordInfo in [currentDialog currentSelectedItems:@"wordName"])
{
[currentWordList addContainedWordsObject:wordInfo];
}
[self.navigationController popToViewController:self animated:true ];
[[SightWordState Instance] save];
}

Left over bugs

The selection still needs some 'select all/clear all' options, as well as potentially extra filters.  In addition the toolbars are a little flaky, when navigating back to a previous option, the toolbar is lost.

Monday, April 5, 2010

Selection Controller: Decorating with checkmarks

The first stage was to create a selection controller which allows any list to be decorated with selections.  In this case I extended my previous table list so that it can decorate one of my tables and intercept the selections and modify the cells.  The goal is that I can easily add selection to any list.

Usage:
The following method will create a unique selector for a particular table, in this case I'm creating a new table from scratch, however this should work when changing to 'select mode' on another list (such as multiple delete).

Right now I'm just using the Checkmark accessory to select items.  Eventually I want to add custom images on the left (such as the mail app on the iPhone).


JLTableContainer *container=
[JLTableContainer createFetchControlledTable:nil 
  forEntity:@"WordList"
    forSimpleKey:@"ListName"
  inContext:
[SightWordState Instance].control.managedObjectContext 
createSectionsBy:nil 
controlledBy:nil];
[container updatePredicate:nil];

Now after creating the list, add the selector.
UISelectionMaster * selector=[[UISelectionMaster alloc] init];
selector.listToSelect=container;
[self.navigationController pushViewController:selector animated:true];
Now the selector takes one of my JLTableContainer objects and decorates it with selection items:

@interface JLSelectionController : NSObject {
id<JLTableController> originalController;
id<JLCellProvider> originalCellProvider;
NSMutableSet *selectedItems;
UITableView *tableView;
}
@property (nonatomic,retain) id originalController;
@property (nonatomic,retain) id originalCellProvider;
@property (nonatomic,retain) NSMutableSet *selectedItems;
@property (nonatomic,retain) UITableView *tableView;
- (void) toggleObject:(id) objToToggle;
- (void) addToContainer:(JLTableContainer*) container;
- (void) removeFromContainer:(JLTableContainer*) container;

Here is the implementation.  What this is doing is creating a new level of controller for the strategy pattern keeping track of the old pattern.  In addition it creates a new cellForObject method, which takes the lower level one and changes the accessory type.  (I want to change it to use an icon instead).

@implementation JLSelectionController
@synthesize originalController;
@synthesize originalCellProvider;
@synthesize selectedItems;
@synthesize tableView;
- (UITableViewCell *)cellForObject:(id) object  atIndexPath:(NSIndexPath *)indexPath forTable:(UITableView*) table
{
tableView=table;
// Delegate to previous provider.
UITableViewCell *returnCell=[originalCellProvider 
cellForObject:object 
atIndexPath:indexPath forTable:table] ;
// Check it for now, later I'd like to make a custom image and put it at the left (ala mail).
if ([selectedItems  containsObject:object])
{
returnCell.accessoryType=UITableViewCellAccessoryCheckmark;
}
else {
returnCell.accessoryType=UITableViewCellAccessoryNone;
}

return returnCell;

}

- (void) rowSelected:(id) selectedObject
{
[self toggleObject:selectedObject];
}
- (void) acessorySelected:(id) selectedObject
{
[self toggleObject:selectedObject];
}
- (void) toggleObject:(id) objToToggle
{
if (selectedItems==nil)
selectedItems=[[NSMutableSet alloc]init];
if ([selectedItems containsObject:objToToggle])
{
[selectedItems removeObject:objToToggle];
}
else {
[selectedItems addObject:objToToggle];
}
if (tableView!=nil)
{
[tableView reloadData];
}

}
- (void) dealloc
{
[super dealloc];
[self.selectedItems release];
}
- (void) addToContainer:(JLTableContainer*) container
{
self.originalController=container.tableController;
container.tableController=self;
self.originalCellProvider=container.cellProvider;
container.cellProvider=self;
[container connectObjects];
container.table =container.table;
[container.table reloadData];
}
- (void) removeFromContainer:(JLTableContainer*) container
{
container.tableController=self.originalController;
container.cellProvider=self.originalCellProvider;
[container.table reloadData];

}

I have this working for one case.  The next step will be to extend eventing so that choices can be made on toolbars and navigation bars for the items and passing the results on in a workflow.

In addition I suspect the reload data for the table is a performance problem, I did it when I was having trouble getting the checkmarks to update.




Friday, March 26, 2010

The list edit form

The next form is the core of managing my lists. It dispays the name of the list and the category and allows direct editing of these features. Categories are intended to be use for filtering and sorting so that a larger number of lists can be used.

Next a series of large friendly buttons allow transfer to other areas:
Edit list
This brings up the current contents of the list the user can then delete words in bulk or add new words.

Merge from other lists
This will allow the user to select one or more other lists. After selecting these lists a dialog will be brought up showing the combined set of words in those lists. The user can then select which words they want. Select and clear all will be available.

Merge to list
This will bring up a dialog that allows the user to select words to merge. Once done a dialog will be brought up allowing the user to select lists to send these words to.

A common factor in these dialogs is the selection of lists.  The core feature of this application vs other sight word applications is intended to be the easy manipulation of lists.

Below is the initial cut of the dialog.  After I get the application working I intend to do an 'aesthetic' run-through to add landscape capability and to make it look prettier.  This is the primary reason I didn't do this as a table, I think it is easier to implement as a standard dialog, and it offers more freedom to make modifications.



Now this screenshot isn't the most attractive dialog, but it includes the essential information.  

I do intend to clean it up prior to release, and to also make another .nib file that contains a landscape version of the dialog.












I also refactored the sight word display, since I am having it initiated from two locations, I made a static method on the SightWordDisplay class that shows the dialog.   I also intend to clean up this some more, I'm not satisfied with my 'singleton' sight word state.