cojoj
cojoj

Reputation: 6475

Handling action from UITableViewCell in MVVM

So I have this BaseCell class which also has this BaseCellViewModel. Of course on top of this lives some FancyViewController with FancyViewModel. The case here is that BaseCell has UIButton on it which triggers this IBAction method - that's fine and that's cool as I can do whatever I want there, but... I have no idea how should I let know FacyViewController about the fact that some action happened on BaseCell.

I can RACObserve a property in FancViewModel as it has NSArray of those cell view models, but how to monitor actual action and notify about exact action triggered on cell?

First thing that came to my mind is the delegation or notifications, but since we have RAC in our project it would be totally stupid not to use it, right?


[Edit] What I did so far...

So, it turns out youc can use RACCommand to actually handle UI events on specific button. In that case I've added:

@property (strong, nonatomic) RACCommand *showAction;

to my BaseCellViewModel with simple implementation like:

- (RACCommand *)showAction {
    return [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        NSLog(@"TEST");
        return [[RACSignal empty] logAll];
    }];
}

And following this pattern I had to do something in my BaseCell which turned out to be quite simple and I ended up with adding:

- (void)configureWithViewModel:(JDLBasePostCellViewModel *)viewModel {
    self.viewModel = viewModel;
    self.actionButton.rac_command = self.viewModel.showAction;
}

And... It works! But...
I need to present UIActionSheet whenever this happens and this can be show only when I need the current parentViewController and since I don't have this kind of information passed anywhere I don't know what to do right now.
FancyViewModel holds a private @property (nonatomic, strong) NSMutableArray <BaseCellViewModel *> *cellViewModels;, but how can I register something on FancyViewController to actually listen for execution of RACCommand on BaseCellViewModel?

Upvotes: 4

Views: 1403

Answers (3)

Michał Ciuba
Michał Ciuba

Reputation: 7944

As you already have a RACCommand in BaseCellViewModel, you can use one of its convenience signals. For example, you can track its state using executing signal:

[baseCellViewModel.showAction.executing subscribeNext:^(NSNumber *executing) {
  //do something if the command is executing
}];

Bindings with RACObserve will work as well, if you need them.

You can also get the latest value from the command's underlying signal (but in the code you posted it won't work, as you use [RACSignal empty] with doesn't send any 'next' values):

 [[baseCellViewModel.command.executionSignals switchToLatest] subscribeNext:^(id x) {
    //do something with the value
  }];

Note that you should subscribe to this signals when you create BaseCellViewModel, not in configureWithViewModel as the latter will be called many times (resulting in many subscriptions for the same signal).

Upvotes: 0

Tomek Cejner
Tomek Cejner

Reputation: 1222

UITableViewCell are volatile, reusable components. They come and go, and your button will do as well.

How about following @danh suggestion and once control is in View Controller, formulate a RAC signal programmatically.

Since I feel rather belonging to RxSwift camp :) I cannot provide source snippet, but this answer is probably what I meant.

Upvotes: 0

danh
danh

Reputation: 62676

There are a few ways that the cell might communicate with the view controller. A common on is via delegation. Have the cell declare a public delegate, like:

// BaseCell.h
@protocol BaseCellDelegate;

@interface BaseCell : UITableViewCell
@property(nonatomic, weak) id<BaseCellDelegate> delegate;
// ...
@end

@protocol BaseCellDelegate <NSObject>
- (void)baseCell:(BaseCell *)cell didReceiveAction:(NSString *)actionName;
@end

When the button is pressed, work out what you'd like to tell the delegate, and tell it:

// BaseCell.m
- (IBAction)buttonWasPressed:(id)sender {
    self.delegate baseCell:self didReceiveAction:@"someAction";
}

Then, in the view controller, declare that you conform to the protocol:

// FancyViewController.m
@interface FancyViewController () <BaseCellDelegate>

in cellForRowAtIndexPath, set the cell's delegate:

// dequeue, etc
cell.delegate = self;

You'll now be required to implement this in the vc:

- (void)baseCell:(BaseCell *)cell didReceiveAction:(NSString *)actionName {
    // the cell got an action, but at what index path?
    NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
    // now we can look up our model at self.model[indexPath.row]
}

Upvotes: 1

Related Questions