Is it wrong to set viewModel as table views datasource?

Viewed 1566

I have seen many codes where set ViewModel as a table view data source, and many which don't.

1.Setting data source to the ViewModel some time makes sense as the data source methods mainly deal with the presentation logic.

2.On the other hand setting the ViewModel as the data source implies that you are implementing cellForRowAtIndexPath etc which makes it not independent of UIKit.

What is the best way to architecture your apps, Kindly clarify?

3 Answers

The answer would be, there is no best way to architecture an app. There are many good ways to organize your classes according to your needs. Here is an example of how I organize my viewModel in order to show data in the table view:

PaymentSectionItem is my ViewModel

PaymentSectionItem.h

@interface PaymentSectionItem : NSObject

@property (assign, nonatomic) NSUInteger itemID;
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSArray *elements;
@property (assign, nonatomic) kTransactionType transactionType;

+ (NSArray<PaymentSectionItem *> *)allSectionItemsWithData;

@end

PaymentSectionItem.m

@implementation PaymentSectionItem

#pragma mark - Custom Accessors

- (NSString *)localizedTitle {
    NSString *title = [NSString stringWithFormat:@"%@_section_title", self.name];
    return NSLocalizedString(title, @"Section title");
}

- (NSString *)localizedDescription {
    NSString *title = [NSString stringWithFormat:@"%@_section_description", self.name];
    return NSLocalizedString(title, @"Section description");
}

#pragma mark - Constructor

- (instancetype)initWithSectionItem:(kSectionItem)sectionItem {
    self = [super init];
    if (self) {
        [self setupFromHomeSectionItem:sectionItem];
    }
    return self;
}

#pragma mark - Private

- (void)setupFromHomeSectionItem:(kSectionItem)sectionItem {
    self.itemID = sectionItem;
    switch (sectionItem) {
        case kSectionItem1: {
            self.name = @"phones";
            self.elements = [Payment findPaymentsType1];
            break;
        }
        case kSectionItem2: {
            self.name = @"autopay";
            self.elements = [Payment findPaymentsType2];
            break;
        }
        case kSectionItem3: {
            self.name = @"trustfund";
            self.elements = [Payment findPaymentsType3];
            self.transactionType = kTransactionTypeTrustFund;
            break;
        }
        case kSectionItem4: {
            self.name = @"debitlink";
            self.elements = [Payment findPaymentsType4];
            self.transactionType = kTransactionTypeDebitLink;
            break;
        }
        case kSectionItem5: {
            self.name = @"pindebit";
            self.elements = [Payment findPaymentsType5];
            self.transactionType = kTransactionTypePINDebit;
            break;
        }
    }
}

#pragma mark - Public

+ (NSArray<PaymentSectionItem *> *)allSectionItemsWithData {
    NSMutableArray *items = [NSMutableArray new];
    [items addObject:[[PaymentSectionItem alloc] initWithSectionItem:kSectionItem1]];
    [items addObject:[[PaymentSectionItem alloc] initWithSectionItem:kSectionItem2]];
    [items addObject:[[PaymentSectionItem alloc] initWithSectionItem:kSectionItem3]];
    [items addObject:[[PaymentSectionItem alloc] initWithSectionItem:kSectionItem4]];
    [items addObject:[[PaymentSectionItem alloc] initWithSectionItem:kSectionItem5]];
    return items;
}

ViewController.h

- (void)viewDidLoad {
    [super viewDidLoad];
    self.items = [PaymentSectionItem allSectionItemsWithData];
}



#pragma mark - UITableViewDataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return self.items.count;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.items[section].elements.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    GTLAutoPaySectionItem *sectionItem = self.items[indexPath.section];
    NSString *identifier = [self identifierForSectionItem:sectionItem atIndex:indexPath];
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
    UITableViewCell<PaymentCellProtocol> *paymentCell = (UITableViewCell<PaymentCellProtocol> *)cell;
    [paymentCell setupCellFromValue:sectionItem.elements[indexPath.row] withSectionItem:sectionItem];
    return cell;
}

As you see, in my case I have a table view with many sections and each section has elements. That's exactly what I do in my ViewModel. Feel free to ask if you have any other question.

NO, it is not wrong to set view model as data source, acting as data model.

Well, the book says, keep M, V & C separate for readability and maintainability. But now we have multiple design patterns to keep things simpler. Basically in a Model we only store the data, and its manipulation are done at view level, that is a good way to achieve the View.

You can not always have a tightly coupled or loosely coupled M & V. It depends on the complexity of these two.

ViewModel

What you are suggesting is not wrong. In my opinion, a view model is trivial logic only, like fonts and colours. As far as I am aware no one has declared a rule for this but I would expect a view model to be simple.

Where your table view is located should be irrelevant to your model, in my opinion. Let's say the superview of your tableView was a UITableViewCell. In that case, I would connect the logic through callbacks.

So the callback would be this.

protocol EventTableViewCellDatasource: class {
  func showTimesTableViewDatasource() -> UITableViewDataSource
}

And the cell would do this

class EventTableViewCell: UITableViewCell {

  weak var datasource: EventTableViewCellDatasource?

  @IBOutlet weak private var tableView: ShowTimesTableView! {
    didSet {
      tableView.dataSource = datasource?.showTimesTableViewDatasource()
    }
  }

  ...
Related