Friday, May 21, 2010

Math Drill: Refactoring into a subview

I next made several refactorings for the math drill project.

Protocols instead of classes


 Instead of passing entire classes around, I made some protocols that define behavior and create an id parameter for this.   This gets rid of several 'forward class' declarations, and allows for classes to be included easier.

Protocol 1 is for the math definition itself, it has the commonly exposed functions that are needed outside.

The next one is for the controller.  It defines the action of selecting a choice (we don't care what it is), and switching to an arbitrary problem.

//The first protocol defines a class that can define a set of problems and
// also select choices.
@protocol MathProblemDefinition
// Called when this problem is seleced.
- (IBAction) setFocus:(id) choice;
// called to indicae that this problem has a choice selected.
- (bool) setChoice:(int) choice;
- (NSMutableArray*) calculateChoices:(int) maxNumChoices;
@end

// A protocol for a controller that this view will notify when it comes into
// focus.
@protocol MathProblemController

- (void) choiceDone;
- (void) setProblemNumber:(int) problemNumber;

@end

A dedicated view controller for the problem display


I defined a separate class for the problem display.  This allows less coupling, and make the actual controller simpler.   I later want to make the UI better.
Header:

@interface MathChoiceDisplay : UIViewController {
NSSet *choices;
id<MathProblemController> controller;
id<MathProblemDefinition> problem;
}
@property (nonatomic,retain) id problem;
@property (nonatomic,retain) NSSet *choices;
@property (nonatomic,retain) id controller;
- (id) initWithChoices:(UIView *) targetView choices:(NSArray*)possibleChoices;
- (IBAction) buttonChosen:(id)sender;


Implementation:
}
- (id) initWithChoices:(UIView *) targetView choices:(NSArray*)possibleChoices
{
int width=targetView.frame.size.width;
int height=targetView.frame.size.height;
self.view =[[UIView alloc] initWithFrame:CGRectMake(targetView.frame.origin.x-width, targetView.frame.origin.y-height,
targetView.frame.size.width*3, targetView.frame.size.height*3)];
self.view.center=targetView.center;
self.view.backgroundColor=[UIColor colorWithRed:0 green:0 blue:0 alpha:0];
int row=0;
int column=0;
[self.view becomeFirstResponder];
self.view.userInteractionEnabled=true;
NSLog(@"Num Choices %d",[possibleChoices count]);
for (NSNumber *value in possibleChoices)
{
if ((row==1) &&(column==1))
column++;
UIButton *button=[[UIButton alloc]init] ;
button.tag=[value intValue]; 
button.frame=CGRectMake(column*width,height*row,width,height);
button.backgroundColor=[UIColor redColor];
button.userInteractionEnabled=true;
[button setTitle:[value stringValue] forState:UIControlStateNormal];
[button addTarget:self action:@selector(buttonChosen:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
column++;
if (column>=3)
{
column=0;
row++;
}
}
[self becomeFirstResponder];

return self;
}

- (IBAction) buttonChosen:(id)sender
{
UIButton *button=(UIButton*) sender;
int choice=button.tag;
[problem setChoice:choice];
[controller choiceDone ];
 
 
}
 
@end

Usage: This becomes much cleaner and allows it to be seen what is doing.

- (void) setProblemNumber:(int) problemNumber
{
NSLog(@"Choosing problem %d",problemNumber);
MathProblem *oldProblem=[self getProblemAtIndex:currentProblem];
if (oldProblem!=nil)
oldProblem.view.alpha=0.5;
if (currentProblemDisplay!=nil)
{
[currentProblemDisplay.view removeFromSuperview];
self.currentProblemDisplay=nil;
}
currentProblem=problemNumber;
MathProblem *problem=[self getProblemAtIndex:problemNumber];

if (problem==nil)
return;
self.currentProblemDisplay= [[MathChoiceDisplay alloc] initWithChoices:problem.view choices:[problem calculateChoices:8]]; 
self.currentProblemDisplay.problem=problem;
self.currentProblemDisplay.controller=self;
[self.displayArea addSubview:currentProblemDisplay.view];

problem.view.alpha=1;
}


I also refactored the controller to make the 'selecting math problem based on index' easier:
- (MathProblem*) getProblemAtIndex:(int) index
{
MathProblem *returnValue=nil;
if ((index>=0)&&(index<[currentProblems count]))
{
returnValue=(MathProblem*)[currentProblems objectAtIndex:index];
}
return returnValue;
}

No comments:

Post a Comment