Saturday, March 13, 2010

Refactoring Suggested items. Creating an animation helper

The first refactoring I'd like to make is to take the animation code that is embedded into a specific callback and make it more general.  To do this I made two refactorings:


  1. Move the ability to make a graphical clone of a UIView to a category on UIView
  2. Create a new animation class for the purpose of holding common animations I would like to reuse, currently it only has one.

Making the UIView Category

The first choice is to make a category of UIView, this will be a place to hang functions that I would like to do to any view.  This will be a file UIView_helper.h and UIView_helper.m.

UIView_Helper.h

do
#import
#import

@interface UIView(AnimationHelper)

//
// Create a image of the current view, and give it a frame 
// identical to the current location.  The image view is autoreleased.
// 
- (UIImageView *)createImageOfView;

@end
This is pretty simple, a simple function that when called will create a UIImageView of the current view and return it.

UIView_Helper.m


#import "UIView_Images.h"
#import


@implementation UIView(AnimationHelper)



//
// Create a image of the current view, and give it a frame 
// identical to the current location.  The image view is autoreleased.
// 
- (UIImageView *)createImageOfView
{
// First getting a view of the current image.
UIGraphicsBeginImageContext(self.bounds.size);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
// this function returns an autoreleased image.
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *imageView=[[UIImageView alloc] initWithImage :viewImage];
imageView.frame=self.frame;
return [imageView autorelease];
}
@end

Making the Animation helper


The next refactoring is to extract the animation code into an object.  This will enable reuse, and keep my actual business logic separate from animation.  In addition it will allow me to update my animation to higher qualities as I get more time.  The current animation is functional, but not as pretty as the Apple animations.

The interface


// An animation helper for some animations to be shared between tasks.
@interface AnimationHelper : NSObject {

}
// This animation will take an image of the source image and move it
// towards the destionation.  This is intended to be a visual indicator.
// Right now all it does is set the frame of the image to be the frame of
// the destionation.  Eventually a more complicated sheering function might be
// nice to have.
- (void) sendViewImageToView : (UIView *)sourceView destinationView:(UIView*) destination;



@end

The code


The code makes use of the UIVIew category defined above, and declares the animation. NOte that to handle the callback for when the first animation is done, and I wish to remove the view, I declare a private category.
// Declare a private category for the animation being over.  This will
// remove the view passed in as context, allowing the image to disappear.
// Eventually more complicated chains of animations might be desired.
@interface AnimationHelper()



- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context;

@end


@implementation AnimationHelper

- (void) sendViewImageToView : (UIView *)sourceView destinationView:(UIView*) destination
{
UIImageView *sourceImage=[sourceView createImageOfView];
UIView *parentView=[sourceView superview];
[parentView addSubview:sourceImage];
[UIView beginAnimations:nil context:sourceImage];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[sourceImage setFrame:destination.frame];

// TODO make this a parameter.   Right now set very short because 
// visual indicating is nice, LONG visual indicating is NOT nice.
[UIView setAnimationDuration:0.25];
[UIView setAnimationDelegate:self];
// Call a selector when we are done with the animation so that it can be
// released.
[UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
[UIView commitAnimations];
}

// Once the animation is done, remove the view from it's superview.
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{
UIView *view=(UIView *)context;
// Remove from superview releases it.
[view removeFromSuperview];
}

@end

Now to use it, all I have to do is this call:
AnimationHelper *animationHelper=[[[AnimationHelper alloc] init] autorelease];
[animationHelper sendViewImageToView:searchBar destinationView:addButton];

The animation helper is declared autorelease because i can't release it in my function, it has a lifespan outside my function, and I didn't want to make it a property that I hang around and release later.  It doesn't crash yet, I'm not fully up on the autorelease rules yet.


The changes in this refactoring can easily be viewed at






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];

Friday, March 12, 2010

Foundation Code: CoreDataControl

The following code set is used as the start of a core data wrapper. Instead of having the same core data code in every class, use it one place.  This is the start, but certainly needs more work.  As the refactoring effort continues, code will be migrated into this class.

This is pretty standard stuff, any tutorial on core data has it, the only tricks are that I pass in NSStrings for table names, and that I don't put it in my application delegate.  The source for this can be found @

Original header

Original Source



Extending set to get a sorted array


// An extension of NSSet to get a sorted array from that set.

@interface NSSet(ArrayMethods)
- (NSArray*) getSortedArray:(NSString*) primaryKey;
@end




@implementation NSSet(ArrayMethods)
- (NSArray*) getSortedArray:(NSString*) primaryKey
{
NSSortDescriptor *sortDesc =
[[NSSortDescriptor alloc] initWithKey:primaryKey
ascending:YES
  selector:@selector(localizedCaseInsensitiveCompare:) ];
NSArray *descArray=[[NSArray alloc] initWithObjects:sortDesc,nil] ;
NSArray *returnValue=[[self allObjects] sortedArrayUsingDescriptors:descArray] ;
[descArray release];
[sortDesc release];

return returnValue ;
}
@end


The initial sight word application



Over the last few months I've been working on my sight word application. I've learned a lot about objective c and iPhone development and now I have something that words. I'm going to post and discuss my current code base and note the changes and polish that have to take place before it is useable. After that I will blog the process of refactoring to make a clean polished application.   The reason I'm not starting from scratch is that the initial development was a learning experience in how to use Objective-C and the Cocoa Development libraries.   I now have a better understanding of them, and can write about what I did, as I refactor my code to be more modular and better designed.



This version of the Sight Words Application is rough, and has bugs. In particular it has memory management issues and isn't quite fully functional.



What it can do is:

  1. Let the user create and delete lists.
  2. Quickly add new words to a list, with suggestions
  3. Display sightwords and allow collecting correct and incorrect words.
  4. Creating new words from the incorrect words.
  5. Importing data from a flat file (which is currently hardcoded).


The current screen structure is:









  • Select List dialog – Allows selection of the sight word list to work with.
    • Clicking a table entry will bring up the Display words dialog
    • Selecting the accessory button will bring up the Display Current words dialog.
    • Clicking the add button will bring up an Add List dialog.

  • Add List Dialog
    • This is currently a modal pane slid over the current view as an overlay. I intend to migrate this to a standard table driven detail editor. It allows entering a word list and category (currently unused)

  • Display Words Dialog
    • This will take the current word list and display it. It will allow the user to select a word as incorrect or correct. At the end it will let them either redisplay all words, the incorrect words, or make a new list based on the incorrect words.

  • Display words in Word List
    • This is intended to allow the user to edit the current word list and select words for content. It currently doesn't work.

  • Add Words Dialog
    • This is a nice dialog; it displays a search bar and lets you select words to display. It then uses a fetched result controller to fetch all the words matching the search bar contents, and then select words. When the add button is pushed the word is sent to the list being edited.

Monday, February 1, 2010

Creating an array from a set

I'm working with Core Data, and ran into the problem that I need ordered data from a result set from Core Data. the difficulty is that it's not a primary query, but from a relationship. My solution was to create a category for NSSet that allows me to create an ordered array (based on a sort criteria):

// An extension of NSSet to get a sorted array from that set.


@interface NSSet(ArrayMethods)

- (NSArray*) getSortedArray:(NSString*) primaryKey;

@end


@implementation NSSet(ArrayMethods)

- (NSArray*) getSortedArray:(NSString*) primaryKey

{

NSSortDescriptor *sortDesc =

[[NSSortDescriptor alloc] initWithKey:primaryKey

ascending:YES

selector:@selector(localizedCaseInsensitiveCompare:) ];

NSArray *descArray=[[NSArray alloc] initWithObjects:sortDesc,nil] ;

NSArray *returnValue=[[self allObjects] sortedArrayUsingDescriptors:descArray] ;

[descArray release];


return returnValue ;

}

@end


There are probably better ways to do this, and I might find a memory error in there at some point in time,(although I think I have it right).

Properties and Retain

I just found the answer to a problem that has been bedeviling me for a while, I had a crash with a deallocated object. The object was a property and I was POSITIVE that I had the proper memory management. I could workaround it with an extra retain, but I hated that solution.

I finally found an answer on Stack Overflow by Chris Hanson:


I had a property named currentWords that I was setting equal to another value:

@property (nonatomic,retain) NSArray * currentWords;


and using

currentWords=result;


The problem was that invoking a variable this way DOES not invoke the retain or other characteristics of the automatically created setter. The proper way to represent this is:


self. currentWords=result;


I'm sure for someone used to Objective-C this is obvious, but coming from C# and Java this was not an obvious error, and caused me much debugging. I had a workaround with an extra retain, but I don't want to have bogus code present, I want to solve the problem for real.


Monday, November 30, 2009

Static Iphone libraries.

As I started to work with Xcode I started to get utility routines that I would like to use across multiple projects. The first 'solution' is to just copy those into the new application. From a software engineering standpoint this is not bad, it is very bad.

I found a VERY useful post at
http://blog.stormyprods.com/2008/11/using-static-libraries-with-iphone-sdk.html

This provides in great detail how to establish a static library which can then be linked into your applications. Any routine that will be used in more than one application should be in a static library.