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;

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

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


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.

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

- (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.backgroundColor=[UIColor colorWithRed:0 green:0 blue:0 alpha:0];
int row=0;
int column=0;
[self.view becomeFirstResponder];
NSLog(@"Num Choices %d",[possibleChoices count]);
for (NSNumber *value in possibleChoices)
if ((row==1) &&(column==1))
UIButton *button=[[UIButton alloc]init] ;
button.tag=[value intValue]; 
button.backgroundColor=[UIColor redColor];
[button setTitle:[value stringValue] forState:UIControlStateNormal];
[button addTarget:self action:@selector(buttonChosen:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
if (column>=3)
[self becomeFirstResponder];

return self;

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

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)
if (currentProblemDisplay!=nil)
[currentProblemDisplay.view removeFromSuperview];
MathProblem *problem=[self getProblemAtIndex:problemNumber];

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


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