سؤال

I'm having an issue with some really simple code in Reactive Cocoa and I can't understand what I'm doing wrong.

I'm simply trying to bind the enabled state of a UIButton to a signal. That signal takes the text of a UITextField and runs it through a regex. This way I can enable the button when the regex matches.

This code gives me an exception while loading the View Controller:

// Poperties used in the view controller
@property (strong, nonatomic) NSRegularExpression *regex;
@property (weak, nonatomic) IBOutlet UITextField *myTextField;
@property (weak, nonatomic) IBOutlet UIButton *continueButton;

// ... other stuff ...

// Code in my "view did load" method
RACSignal *mySignal = RACObserve(self, myTextField);

[mySignal deliverOn:[RACScheduler mainThreadScheduler]];

@weakify(self);
[mySignal map:^(NSString *value) {
    @strongify(self);
    NSUInteger matches = [self.regex numberOfMatchesInString:value
                                                     options:0
                                                     range:NSMakeRange(0, [value length])];
    return @(matches > 0);
}];

RAC(self.continueButton, enabled) = mySignal;

Clearly the exception explains that I'm trying to get the selector "enabled" from a UITextField, and the UITextField does not have this selector. I don't understand why I'm getting this error when I'm calling the enabled selector in my continueButton, not myTextField.

Basically the exception is:

[UITextField charValue]: unrecognized selector sent to instance 0xa2c7830

And the debugger stops in the line:

[object setValue:x ?: nilValue forKeyPath:keyPath];

Instance 0xa2c7830 is a UITextField. object is a UIButton and keyPath is @"enabled". I just don't see anything wrong with this setup, except that its being called on my UITextField instead of UIButton!

Thanks for your help!

هل كانت مفيدة؟

المحلول

The hint is here:

[UITextField charValue]

The unrecognized selector isn't enabled; it's charValue. Okay, why would we be asking a UITextField to turn into a char? Well...if we wanted to cast it down to a boolean, then it's possible the KVO mechanics think it's an NSNumber and try to treat it as such. But it's not an NSNumber -- hence the exception.

But wait, didn't we map the signal to create an NSNumber? Why are we trying to cast it down to a boolean in the first place? And why is it a UITextField instead of an NSString? (We'll come back to that later)

Ah. Well, we actually didn't map the signal into something else, because map: isn't destructive.

By that I mean map: creates a new signal. It doesn't modify mySignal. We call map:, creating a new signal, but then we never do anything with it -- it just sits there, and nothing subscribes to it, and then it's deallocated. But we want to subscribe to it!

RAC(self.continueButton, enabled) = [mySignal map:^(NSString *value) {
    @strongify(self);
    NSUInteger matches = [self.regex numberOfMatchesInString:value
                                                     options:0
                                                     range:NSMakeRange(0, [value length])];
    return @(matches > 0);
}];

Aha! Now we're binding the enabled property to the transformed signal -- which is what we wanted all along. But something is still amiss.

Something bad is going to happen when we try to do the regex match...if I had to guess [UITextField length]: unrecognized selector sent to instance 0xWhatever

UITextField again? What's up with that guy?

The answer to that comes back to mySignal. What is mySignal actually sending? We want it to send text...but if that new exception I just made up is correct, it's sending a UITextField! Why? Well, mySignal is RACObserve(self, myTextField). But wait! That will send new values of the text field -- not new values of the text field's text. What you want is:

RACSignal *mySignal = self.myTextField.rac_textSignal;

That will send new values of the text field's text, instead of new values of the text field.

Phew. That should work. As a tip: the log* helpers are very useful for debugging.

RAC(self.continueButton, enabled) = [mySignal logAll];

That'll show you that mySignal is sending UITextFields instead of NSStrings.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top