1
\$\begingroup\$

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

enter image description here

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;
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

A UILabel is a view. Make your FormulaLabel class into just a Formula class that inherits from NSObject instead of UILabel.

@interface Formula : NSObject

Store data for one formula in the Formula class. The Formulas could be stored in the view controller via NSSet, NSArray, or NSDictionary. This doesn't violate MVC because the Model would be stored inside the controller and not the view. I generally use a "container class" that controls adding, removing, modifying, and setting a current data set.

In your view, create just one label for each data field in a formula, only during initialization of the current view, and set the labels properties with an update method. If the properties such as font stays the same, you only need to do so during init, or just set in IB and it will get set in awakeFromNib.

- (void)updateLabelsForFormula:(Formula*)formula
{
    NSString *label1Str;
    NSString *label2Str;

    if (formula == nil) {
        //passing nil will clear the labels
        NSString *blank = @"";
        label1Str = blank;
        label2Str = blank;
    } else {
        label1Str = [formula someStringForProperty];
        label2Str = [formula someStrongForOtherProperty];
    }
    // set label properties such as text, size, etc
    self.label1.text = label1Str;
    self.label2.text = label2Str;
}

I would use an array to store the individual components, then build a string from the array.

...

@property (nonatomic, strong) NSMutableArray *components;
- (void)addComponent:(NSString*)component;     // add to compnents array
- (void)removeLastComponent;  // remove last component in array;
- (NSString*)stringFromComponents;  // return a string built from components in array

...

Much more to be said here, but this should get you started with separating model data from view properties.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.