Search code examples
bindingreactive-cocoa

Why is my Reactive Cocoa not being called to check textfield for changes?


Im new to RCA. I found this tut online ReactiveCocoaSamurai

Its pretty simple...In the original project they separate the model from the view controller, as they should :) But when I try to do it, I can't bind (or connect) the view controller property to the model property.

Everything is set up correctly because if I put all the code in the view controller class, it WORKS! The reason why it works is because I don't need the line of code in the view controller that binds the model to the view controller properties.

But when I try to split it up as the tutorial has it, I get an error at that line.

This works when in the view controller:

The Signal method

-(RACSignal *)forbiddenNameSignal {
    return [RACObserve(self, username) filter:^BOOL(NSString *newName) {
        return [self.forbiddenNames containsObject:newName]; //This yields a YES BOOLEAN?
    }];
}

This goes in the viewDidLoad of the viewController:

[[self.usernameField.rac_textSignal distinctUntilChanged] subscribeNext:^(NSString *x) {self.username = x;}];

 [self.mymodel.forbiddenNameSignal subscribeNext:^(NSString *name) {
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Forbidden Name!"
                                                            message:[NSString stringWithFormat:@"The name %@ has been forbidden!",name]
                                                           delegate:nil
                                                  cancelButtonTitle:@"Ok"
                                                  otherButtonTitles:nil];
            [alert show];
            self.mymodel.username = @"";
 }];

Of course the UITextField is in storyboards and hooked up and the forbidden array is defined in viewDidLoad as well.

This doesn't work:

LoginModel.m

-(id)init {
    self = [super init];
    if(!self) return nil;
    //forbidden names array
    self.forbiddenNames = @[ @"Peter",@"Piper",@"Picker"];
    return self;
}


-(RACSignal *)forbiddenNameSignal {
    return [RACObserve(self, username) filter:^BOOL(NSString *newName) {
        return [self.forbiddenNames containsObject:newName]; //This yields a YES BOOLEAN?
    }];
}

ViewController.m viewDidLoad:

self.mymodel = [LoginModel new];
RAC(self.usernameField.text) = [RACAbleWithStart(self.mymodel.username) distinctUntilChanged];
[[self.usernameField.rac_textSignal distinctUntilChanged] subscribeNext:^(NSString *x) {
            self.username = x;
        }];
[self.mymodel.forbiddenNameSignal subscribeNext:^(NSString *name) {
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Forbidden Name!"
                                                            message:[NSString stringWithFormat:@"The name %@ has been forbidden!",name]
                                                           delegate:nil
                                                  cancelButtonTitle:@"Ok"
                                                  otherButtonTitles:nil];
            [alert show];
            self.mymodel.username = @"";
        }];

I get an error at the RAC line saying expected identifier & warning of "signalWithStarting..." deprecated for "valuesForKeyPath".

What I have tried:

RAC(self, usernameField) = [RACObserve(self, mymodel.username) distinctUntilChanged];

and ...

RAC(self, usernameField) = RAC(self, mymodel.username);

Any ideas how to fix this?

Or about why indenting code is not working either :(


Solution

  • I am not sure I fully understand your problem, but there are a few things that don't look right in your code.

    Firstly, treat tutorials regarding ReactiveCocoa on the web with caution! When ReactiveCocoa 2.0 came out a few months ago there were a lot of breaking API changes. The RACAbleWithStart macro you have been trying to use is deprecated.

    Also, this code looks wrong to me:

    [[self.usernameField.rac_textSignal distinctUntilChanged]
        subscribeNext:^(NSString *x) {self.username = x;}];
    

    This is taking the rac_textSignal and assigning the next event value to the username property. Firstly, I am pretty sure distinctUntilChanged is not required here, the signal will only emit events when the text field changes. Secondly, to assign the a signal to a property you can simply use the RAC macro:

    RAC(self, username) = self.usernameField.rac_textSignal;
    

    I see that the root of your problem is that you are trying to bind a property on your LoginModel to a property in your view controller. ReactiveCocoa has the RACChannelTo macro which is specifically for this purpose, however I have never used it, and have an idea that will side-step it!

    In my opinion your LoginModel should become a view model, and it should expose a username in order that it models the view (because that is what view models do!). You can then bind this within your view controller like so:

    RAC(self.loginViewModel, username) = self.usernameField.rac_textSignal;
    

    You then have to turn the username property change into a signal within your view model, so that you can use your filter ... so within your LoginViewModel initialization code ...

    [[RACObserve(self, username)
      filter:^BOOL(NSString *username) {
        return [self.forbiddenNames containsObject:username]; 
      }
      subscribeNext:^BOOL(NSString *forbiddenName) {
        // do your thang!
      }];
    

    I hope that helps!