3
\$\begingroup\$

I am building this pet project with UIKit. The App's main goal is to keep track of my daily expenses.

The AddExpenseViewController is responsible for capturing user input, parse it into a Expense instance, and send it back to the UIViewController containing all my expenses using the delegate pattern.

To capture user input I am using a UITableView with two custom cells containing UITextFields: ExpenseTitleTableViewCell and ExpenseValueTableViewCell. As this is supposed to work as a form, it's not possible to add new cells (i.e fields) to the UITableView.

Currently, I instantiate the cells in the tableView(_ tableView:, cellForRowAt:) -> UITableViewCell method using helper functions, like so:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 2 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { switch indexPath.row { case 0: return configureTitleCell() case 1: return configureValueCell() default: return UITableViewCell() } func configureTitleCell() -> UITableViewCell { let cell = ExpenseTitleTableViewCell(style: .value1, reuseIdentifier: ExpenseTitleTableViewCell.reuseIdentifier) cell.configureCell() return cell } func configureValueCell() -> UITableViewCell { let cell = ExpenseValueTableViewCell(style: .value1, reuseIdentifier: ExpenseValueTableViewCell.reuseIdentifier) cell.configureCell() return cell } 

A few things look off to me:

  • First: the view controller is responsible for instantiating the cells, and this generates duplicate code on configureTitleCell and configureValueCell. Should I use a pattern like Factory to split responsibilities?

  • Second: having the constant 2 in numberOfRowsInSection also looks unsafe to me

\$\endgroup\$
2
  • \$\begingroup\$Avoid repetition by using polymorphism\$\endgroup\$
    – ielyamani
    CommentedMar 30, 2023 at 19:35
  • \$\begingroup\$The problem is not that you have a constant number of rows in a table view. We do that all the time. But we do it because we want other table behaviors (e.g. scrolling, consistent look and view for a whole bunch of entries, etc.). But this does not seem remotely like a reasonable table view use-case. Maybe you can explain why you wanted to use a table view…\$\endgroup\$
    – Rob
    CommentedNov 15, 2023 at 2:05

2 Answers 2

1
\$\begingroup\$

Using a table view for this sort of thing is inappropriate IMO. A UITableView is designed for displaying an array of items, not bits and pieces of a single item. That's why having the constant in numberOfRowsInSection looks wrong.

Instead, you should have an ExpenseTitleView and ExpenseValueView that are both derived from UIView. Whatever is going on in the respective configure() methods can be put in the init of those two views.

If you are using Interface Builder to create the UI, you can just put instances of the two views directly in the xib or storyboard file. If you are building your UI programmatically, then override the loadView method of your view controller and call view.addSubview() passing an instance of each subview.

\$\endgroup\$
    0
    \$\begingroup\$

    This is a common scenario in enterprise iOS development. In order to make these constants as self-explanatory as possible, I use the following approach. (I am using Objective-C for iOS development, but it shouldn't be a problem to transfer this example to Swift)

    Declare an enumeration listing all of your sections:

    typedef NS_ENUM(NSInteger, AddExpenseSection) { AddExpenseSection_Title = 0, AddExpenseSection_Value }; 

    Then store a list of your sections as a property of your VC.

    @interface AddExpenseViewController () @property (nonatomic, strong) NSArray *sections; @end @implementation AddExpenseViewController - (void)viewDidLoad { [super viewDidLoad]; self.sections = @[@(AddExpenseSection_Title), @(AddExpenseSection_Value)]; } @end 

    Then the cases of the switch statement in table view data source / delegate methods become pretty verbose and self-explanatory:

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tv { return self.sections.count } - (UITableViewCell *)tableView... cellForRowAtIndexPath... { AddExpenseSection tableSection = (AddExpenseSection)self.sections[indexPath.section].integerValue; switch (tableSection) { case AddExpenseSection_Title: return [self titleCell]; case AddExpenseSection_Value: return [self valueCell]; } } 

    As a last note, I would mention that enums should be view controller specific when the list of sections is unique for the screen. A list of 2 sections (title/value) may be reused on different screens, so I would suggest to declare similar enums in UI utils file and refer it in specific VC files.

    \$\endgroup\$

      Start asking to get answers

      Find the answer to your question by asking.

      Ask question

      Explore related questions

      See similar questions with these tags.