-1

I'm trying to express the following scenario in ReactiveCocoa and MVVM.

  1. There's a table view which shows a list of Bluetooth devices nearby
  2. On row selection we start a process of connecting to the selected device and display an activity indicator as an accessoryView of the selected cell.

Now we have alternative endings:

  1. When connected successfully we dismiss the table view controller and pass device handle to the parent view controller or rather parent view model.
  2. When during connecting process user taps another table view cell then we cancel the previous process and start a new one with the selected device.
  3. On error show a message.

I have a problem with ending no 2. I came up with RACCommand in my view model that triggers the process of connection. Then in didSelectRowAtIndexPath I execute that command.

ViewModel:
- (RACCommand *)selectionCommand {
    if (!_selectionCommand) {
        _selectionCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
            return [self selectionSignal];
        }];
    }

    return _selectionCommand;
}

- (RACSignal *)selectionSignal {
    // not implemented for real
    return [[[RACSignal return:@"ASDF"] delay:2.0] logAll];
}

ViewController:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
    UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
    [activityIndicatorView startAnimating];
    cell.accessoryView = activityIndicatorView;

    [[self.viewModel.selectionCommand execute:indexPath] subscribeCompleted:^{
        [activityIndicatorView stopAnimating];
        cell.accessoryView = nil;
    }];
}

This shows and hides the activity view during the connection process but only when I wait for it to finish without tapping on other cells. I ask for a guidance on how such behaviour could be completed. (It also feels like this isn't the right place to subscribe to the signal, right? Should it go to viewDidLoad?)

Lubiluk
  • 704
  • 5
  • 12

2 Answers2

-1

Apparently I asked the wrong question. It should say "How to cancel a RACCommand". The answer is: takeUntil: can do that (source: https://github.com/ReactiveCocoa/ReactiveCocoa/issues/1326). So if I modify my command creation method to look like below everything starts to work like I expected. Now it cancels itself when it is used again. Notice that allowsConcurrentExecution must be set to YES to enable this behaviour, otherwise the signal will emit errors saying that RACCommand is currently not enabled.

- (RACCommand *)selectionCommand {
    if (!_selectionCommand) {
        @weakify(self);
        _selectionCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
            @strongify(self);
            return [[self selectionSignal] takeUntil:self->_selectionCommand.executionSignals];
        }];
        _selectionCommand.allowsConcurrentExecution = YES;
    }

    return _selectionCommand;
}
Lubiluk
  • 704
  • 5
  • 12
-2

I do this by attaching a block operation to a custom UITableViewCell sub class. I make my tableViewCells part of this subClass and then when I'm laying out my tableviewcells in the view controller, I call to exposed block header in the UITabbleViewCell subclass where it's exposed in this subclasses header file and attach a touch even to the block operation. The custom UITableViewCell needs a tapgesture recognizer and this will do the trick, well it will do the trick as long as in your UITableViewCell custom sub class you also expose the various elements of each blooth tooth tableview cell, that is, create customized setters and getters. This is the easiest way to do it and it takes about 15 lines of code and ZERO third party libraries.

header file:

@property (nonatomic, copy) void (^detailsBlock)();

implementation file:

_tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(cellTapped:)];
[_tapGesture setDelegate:self];
[_tapGesture setCancelsTouchesInView:FALSE];
[self addGestureRecognizer:_tapGesture];

- (void)cellTapped:(UITapGestureRecognizer*)sender
{
    if ([self detailsBlock]) {
        [self detailsBlock]();
    }
}

making the block work for a tableview in the viewcontroller

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"something" forIndexPath:indexPath];


    [cell setDetailsBlock:^{
        [self termsButtonPressed];
    }];

    return cell;
}

-(void)termsButtonPressed
{
     //do work
}
Larry Pickles
  • 4,615
  • 2
  • 19
  • 36