I am making an app that allows the user to type chemical equations (H2O + NaCl) to the screen. The user taps, inside a tableviewcell, a custom textfield and types on a custom keyboard (both subclasses of uiviews). The vc doesn't have an actual instance of the keyboard, and the keyboard instead appears because it is the inputView property of the textfield. Also, the text field doesn't add text directly to itself, but instead places each compound inside a label, which is then added as its subview. Currently, the textfield stores the order of compounds internally, and the specific text is stored inside the labels. When the view controller needs the text, it creates a model class and takes the data from the textfield and stores it into the model.
According to MVC, the views shouldn't be handling their own data, something that my textfield and label seriously infringe. I need help determining what parts of my code to transfer into the view controller and model. Some specific questions I have are
- what class should implement the keyboard's protocol
- whether the vc or the textfield should add labels as the textfield's subviews
- how much data should be kept in the model, and how much (if any) can remain in the textfield/labels. -- how the vc should exchange info between model and textfields
Currently, the code is organized as follows:
Keyboard
It appears when textfield is tapped. When a key is pressed, it notifies its delegate, sometimes sending a key as a parameter.
@protocol KeyInput <UITextInputTraits>
- (void) newCompound;
-(void) closeKeyboard;
- (void) addElement:(NSString*) currentElement;
- (void) changeCharge:(NSString*) chargeIncrease;
- (void) changeState:(NSString*) stateName;
- (void) deleteCharacter;
@end
@interface FormulaKeyboard : UIView {
id <KeyInput> delegate;
UIButton *element1;
}
@property (nonatomic, retain) id <KeyInput> delegate;
-(IBAction) addCompound:(id)sender; //calls [delegate newCompound]
-(IBAction) close:(id)sender;
- (IBAction) deletePressed:(id)sender;
-(IBAction) element1Pressed:(id)sender; // calls [delegate addElement:@"Na"]
-(IBAction) element2Pressed:(id)sender;
-(IBAction) element3Pressed:(id)sender;
-(IBAction) element4Pressed:(id)sender;
-(IBAction) openParenPressed:(id)sender; // rest are similar
-(IBAction) closedParenPressed:(id)sender;
-(IBAction) plusPressed:(id)sender;
-(IBAction) minusPressed:(id)sender;
-(IBAction) solidPressed:(id)sender;
-(IBAction) liquidPressed:(id)sender;
-(IBAction) gasPressed:(id)sender;
-(IBAction) aqueousPressed:(id)sender;
Textfield
It complies to the KeyInput
protocol. When an element on the keyboard is pressed consecutively, the subscript increases (H + H + O would result in H2O); similarly pressing the '+' consecutively will increase the charge of ions. This process is handled in the protocol methods, and knowledge of the last keys pressed are stored inside the actual labels. Additionally, the element order, charge, and physical state are all stored as separate strings as properties of the label, and the textfield appends all the strings to create the complete formula.
- (id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
self.userInteractionEnabled = YES;
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:@"FormulaKeyboard" owner:self options:nil];
for (id object in bundle) {
if ([object isKindOfClass:[FormulaKeyboard class]])
keyboard = (FormulaKeyboard *)object;
}
self.inputView = keyboard;
keyboard.delegate = self;
consecutiveElementCount = 0;
chargeIndex = 4;
self.equationOrder = [[NSMutableArray alloc] init];
startingPoint = 5;
subscriptList = [[NSArray alloc] initWithObjects:@"", @"₂", @"₃", @"₄", @"₅", @"₆", @"₇", @"₈", @"₉", @"₁₀", @"₁₁", @"₁₂", nil];
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self becomeFirstResponder];
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
#pragma mark -- KeyInput Protocol Methods
- (void)newCompound {
//add plus sign
if (self.currentLabel != nil) {
startingPoint = currentLabel.frame.origin.x + currentLabel.frame.size.width + 3;
UILabel *plus = [[UILabel alloc] initWithFrame:CGRectMake(startingPoint, 5, 10, 10)];
plus.text = @"+";
plus.font = [UIFont systemFontOfSize:13];
[plus sizeToFit];
[self addSubview:plus];
startingPoint = plus.frame.origin.x + plus.frame.size.width + 3;
}
self.currentLabel = [[FormulaLabel alloc] initWithFrame:CGRectMake(startingPoint, 2, 10, 22)];
self.currentLabel.elementFormula = [[NSMutableString stringWithString:@""] retain];
self.currentLabel.charge = [[NSString alloc] initWithString:@""];
self.currentLabel.state = [[NSString alloc] initWithString:@""];
[self addSubview:self.currentLabel];
[equationOrder addObject:self.currentLabel];
}
- (void)addElement:(NSString *)currentElement {
if (self.currentLabel != nil) {
if (![currentElement isEqualToString:self.currentLabel.lastElementPressed]) {
// when new element is pressed
[self.currentLabel.elementFormula appendString:currentElement];
self.currentLabel.lastElementPressed = currentElement;
consecutiveElementCount = 1;
subscriptLength = 0;
[self.currentLabel.elementOrder addObject:currentElement];
[self.currentLabel.subscriptOrder addObject:[subscriptList objectAtIndex:0]];
}
// don't increase subscript after 12 or of open parentheses
else if (consecutiveElementCount < 12 && (![currentElement isEqualToString:@"("])) { consecutiveElementCount++;
[self.currentLabel.subscriptOrder replaceObjectAtIndex:[self.currentLabel.subscriptOrder count] - 1 withObject:[subscriptList objectAtIndex:consecutiveElementCount - 1]];
}
NSRange range = NSMakeRange (([self.currentLabel.elementFormula length] - subscriptLength), subscriptLength);
[self.currentLabel.elementFormula deleteCharactersInRange:range];
NSString *tempSubscript = [subscriptList objectAtIndex:consecutiveElementCount - 1];
[self.currentLabel.elementFormula appendString:tempSubscript];
subscriptLength = [tempSubscript length];
[self synthesizeFormula];
}
}
- (void)changeCharge:(NSString *)chargeIncrease {
if (self.currentLabel != nil) {
NSArray *chargeList = [[[NSArray alloc] initWithObjects:@"⁴⁻", @"³⁻", @"²⁻", @"⁻", @"", @"⁺", @"²⁺", @"³⁺", @"⁴⁺", nil] autorelease];
if ([chargeIncrease isEqualToString:@"+"] && chargeIndex < ([chargeList count] - 1))
chargeIndex += 1;
if ([chargeIncrease isEqualToString:@"-"] && chargeIndex > 0)
chargeIndex -= 1;
self.currentLabel.charge = [chargeList objectAtIndex:chargeIndex];
[self synthesizeFormula];
}
}
- (void) changeState:(NSString *)stateName {
if (self.currentLabel.state != nil) {
self.currentLabel.state = stateName;
[self synthesizeFormula];
}
}
- (void)deleteCharacter {
if (![self.currentLabel.elementFormula isEqualToString:@""] && (self.currentLabel != nil)) {
NSString *lastElement = [self.currentLabel.elementOrder objectAtIndex:[self.currentLabel.elementOrder count] - 1];
NSString *lastSubscript = [self.currentLabel.subscriptOrder objectAtIndex:[self.currentLabel.subscriptOrder count] - 1];
int removeLength = [lastElement length] + [lastSubscript length];
NSRange range = NSMakeRange([self.currentLabel.elementFormula length]- removeLength, removeLength);
[self.currentLabel.elementFormula deleteCharactersInRange:range];
[self.currentLabel.elementOrder removeObjectAtIndex:[self.currentLabel.elementOrder count]-1];
[self.currentLabel.subscriptOrder removeObjectAtIndex:[self.currentLabel.subscriptOrder count]-1];
[self synthesizeFormula];
}
}
- (void)synthesizeFormula {
NSMutableString *synthesizedFormula = [[[NSMutableString alloc] initWithString:self.currentLabel.elementFormula] autorelease];
[synthesizedFormula appendString:self.currentLabel.charge];
[synthesizedFormula appendString:self.currentLabel.state];
self.currentLabel.text = synthesizedFormula;
[self.currentLabel sizeToFit];
}
- (void)closeKeyboard {
[self resignFirstResponder];
}
Label
Where the individual compounds are stored.
@interface FormulaLabel : UILabel {
NSMutableString *elementFormula;
NSString *charge;
NSString *state;
NSString *lastElementPressed;
NSMutableArray *elementOrder;
NSMutableArray *subscriptOrder;
}
@property (nonatomic, retain) NSMutableString *elementFormula;
@property (nonatomic, retain) NSString *charge;
@property (nonatomic, retain) NSString *state;
@property (nonatomic, retain) NSString *lastElementPressed;
@property (nonatomic, retain) NSMutableArray *subscriptOrder;
@property (nonatomic, retain) NSMutableArray *elementOrder;