4
\$\begingroup\$

Coming from Java and Android development and Objective-C still is a bit strange to me, but I just went in head first and started writing code. Before I started getting really far into it, I just want to throw some basic code here for suggestions about correct syntax, etc. I just want to make sure that my variable and function declarations are correct as well as the way I refer to UI elements is the recommended method.

I was told to make a separate ViewController class for each UI I build as below:

@interface CalculatorViewController : UITableViewController

@property (weak, nonatomic) IBOutlet UITextField *fatTextField;
@property (weak, nonatomic) IBOutlet UITextField *carbsTextField;
@property (weak, nonatomic) IBOutlet UITextField *fiberTextField;
@property (weak, nonatomic) IBOutlet UITextField *proteinTextField;
@property (weak, nonatomic) IBOutlet UITextField *servingsTextField;
@property (weak, nonatomic) IBOutlet UITextField *foodnameTextField;
@property (weak, nonatomic) IBOutlet UILabel *pointsLabel;

- (IBAction)calculate:(id)sender;
- (IBAction)reset:(id)sender;
- (IBAction)addtotracker:(id)sender;

- (void)resetCalculator;
- (void)calculatePoints;

Then inside the CalculatorViewController.m file I ended up with the following code snippets:

// UI FUNCTIONS
- (void) resetCalculator {

    // Reset all nutrion fact fields.
    _fatTextField.text = @"";
    _carbsTextField.text = @"";
    _fiberTextField.text = @"";
    _proteinTextField.text = @"";
    _servingsTextField.text = @"";

    // Reset food name & points label
    _foodnameTextField.text = @"";
    _pointsLabel.text = @"Points: 0";

    // Set Fat as firstresponder
    [self.fatTextField becomeFirstResponder];
}

- (void) calculatePoints {
    float fatFloat = [_fatTextField.text floatValue];
    float carbsFloat = [_carbsTextField.text floatValue];
    float fiberFloat = [_fiberTextField.text floatValue];
    float proteinFloat = [_proteinTextField.text floatValue];
    float servingsFloat = [_servingsTextField.text floatValue];

    float points = proteinFloat + carbsFloat + fatFloat - fiberFloat;
    points = points / 100;
    points = roundf(points);
    points = MAX(points, 0);

    _pointsLabel.text = [NSString stringWithFormat:@"Points: %f", points];
}

- (IBAction)calculate:(id)sender {
    NSLog(@"Calculate button pressed!");

    // Close the keyboard
    [self.view endEditing:YES];
}

- (IBAction)reset:(id)sender {
    NSLog(@"Reset button pressed!");

    [self resetCalculator];
}

- (IBAction)addtotracker:(id)sender {
    NSLog(@"Tracker button pressed!");
}
\$\endgroup\$
5
\$\begingroup\$

There are a lot of takes on how to structure things in Objective-C, largely based on the age of the language. It used to require you to use x, but now it lets you use y, kind of things. I like to go with the minimalist/OOAD-purist approach.

Your Header File

Your header file (CalculatorViewController.h) is your public interface for your class. This is what every other class sees when it compiles against it and has access to. As such, I keep it light. You don't want another class to set your fatTextField and if you did, you wouldn't want them to know the intimate details of how your form works so you'd pass a model object or something. I like my header to look like this:

@class ImportantStuff; // 4
@protocol CalculatorViewControllerDelegate; // 5

@interface CalculatorViewController : UITableViewController
- (id)initWithImportantStuff:(ImportantStuff *)importantStuff; // 1
@property (nonatomic, weak) id<CalculatorViewControllerDelegate> delegate; // 2
@property (nonatomic, readonly) ImportantStuff *importantStuff; // 3
@end

@protocol CalculatorViewControllerDelegate <NSObject> // 6
- (void)calculatorViewController:(CalculatorViewController *)calculatorViewController
    didFinishWithImportantStuff:(ImportantStuff *)importantStuff;
@end

Highlighted lines:

  1. Create an initializer with everything you need to get the class started on work.
  2. Add a delegate or some other message passing pattern to send notifications back.
  3. Maybe, have some exposed properties for information that interfacing classes may want.
  4. As general practice, you should only reference the bare minimum number of headers in your header since it reduces circular dependencies and compile time. @class tells the compiler that a class with that name exists and it shouldn't worry about the details.
  5. With delegates you often have a chicken and egg scenario. The class references the protocol and the protocol references the class. Like with @class you can say you'll inherit it later.
  6. Populate your delegate with meaningful hooks.

Your Implementation File, Class Extension

Now that you've removed all those properties from your public header, you're getting compiler errors and need to stick them somewhere. The right spot is in your class extension, the thing that looks like a category but isn't. Xcode should have generated it as follows at the top of CalculatorViewController.m

@interface CalculatorViewController ()
@end

Just stick all your @property's there.

As far as the method declarations, you don't need them. In C you need to declare functions ahead of time, but not with modern Objective-C. The only reason you need to declare them is if you're trying to expose them outside of your implementation.

Your Implementation File, Implementation

Things look good here. Just a couple things:

  1. Directly accessing instance variables (ivars), e.g. _pointsLabel has gone out of fashion. There's a lot of reasons for this with potential risks associated with both doing it and not doing it, but there's even a pedantic compiler warning you can turn on to catch it. Generally, you should use ivars only in getters, setters, and initializers, places where you need direct access or want to suppress side effects.

  2. There's no reason to have both -reset: and -resetCalculator. There are two interesting points here: First, somewhere in the codebase exists #define IBAction void so you can turn any void returning function into an IBAction assuming it has the right arguments. Second, controller actions support three structures: -(IBAction)myAction:sender, -(IBAction)myAction:sender event:event and -(IBAction)myAction with no arguments. You can just use -(IBAction)resetCalculator.

  3. addToTracker, with camel case. Probably just a typo considering everywhere else. Be aware that Interface Builder provides zero compile time checking so if you change the signature or remove a method you must edit it in Interface Builder or it will crash your app.

The most important thing

No matter what style, or principles, or philosophies you adopt as an iOS developer, every other iOS developer will disagree with you, and you with them. Maybe it's the awesomeness/awfulness of the language or the tools or the platform or the status, but its a very opinionated group of developers.

\$\endgroup\$
  • \$\begingroup\$ Could you clarify what you said about the method declarations in terms of IBActions? Does that mean that for IBActions, you don't need to declare them at all in your header, and you can just write them in your implementation? Also, your last point is very much the truth. For instance, I sometimes feel weird declaring "private" variables as properties because I don't need all the extra stuff that comes with properties. Other people feel that using ivars at all is akin to letting monkeys bang on your keyboard. To each his own. \$\endgroup\$ – boztalay Jan 7 '14 at 7:35
  • \$\begingroup\$ @boztalay Interface Builder will scan your files for interface and implementation methods defined with IBAction and just uses this for generating a list of binding points for when you create connections. That said, any method just used in the same @implementation it's implemented in does not need to be declared in an @interface. Unlike C functions, the compiler is comfortable with method definitions lower in the file. \$\endgroup\$ – Brian Nickel Jan 7 '14 at 15:36
  • \$\begingroup\$ @boztalay And on instance variables, I just used one yesterday for a case where there's no good reason to access the storage type directly. The exposed methods were shouldDisplaySiteForGroup:context: and invalidateGroupsToDisplaySites. They are the only methods accessing the backing store and a property felt too formal. There's time and a place for everything, I guess. \$\endgroup\$ – Brian Nickel Jan 7 '14 at 15:41
  • \$\begingroup\$ Thanks for replying, I didn't know that about IBActions. That should clean up some of my @interfaces significantly. \$\endgroup\$ – boztalay Jan 8 '14 at 3:09
  • \$\begingroup\$ This is a pretty complete answer. I just don't understand the posted bit about @protocols. Not because I don't understand protocols... just because I don't think is very relevant here. Moreover, init-ing view controllers is quickly becoming "the old way", and if you're using storyboards, you won't be init-ing a view controller. \$\endgroup\$ – nhgrif Feb 20 '14 at 23:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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