1

I'm building a rather long form with QuickDialog and Reactive Cocoa and I would love to be able to write a factory method to setup my QElements from the same form.

Here's my example of how I'm doing it now.

self.root = [[QRootElement alloc] init];
self.root.title = self.title;
self.root.grouped = YES;

self.basic = [[QSection alloc] init];
self.basic.title = @"Basic Information";
[self.root addSection:self.basic];

QEntryElement *address = [[QEntryElement alloc] init];
address.title = @"Address";
RAC(address, textValue) = [RACObserve(self, viewModel.address) distinctUntilChanged];
RAC(self, viewModel.address) = [RACObserve(address, textValue) distinctUntilChanged];
[self.basic addElement:address];

QEntryElement *city = [[QEntryElement alloc] init];
city.title = @"City";
RAC(city, textValue) = [RACObserve(self, viewModel.city) distinctUntilChanged];
RAC(self, viewModel.city) = [RACObserve(city, textValue) distinctUntilChanged];
[self.basic addElement:city];

QEntryElement *state = [[QEntryElement alloc] init];
state.title = @"State";
RAC(state, textValue) = [RACObserve(self, viewModel.state) distinctUntilChanged];
RAC(self, viewModel.state) = [RACObserve(state, textValue) distinctUntilChanged];
[self.basic addElement:state];

QEntryElement *postal = [[QEntryElement alloc] init];
postal.title = @"Zip";
RAC(postal, textValue) = [RACObserve(self, viewModel.postalCode) distinctUntilChanged];
RAC(self, viewModel.postalCode) = [RACObserve(postal, textValue) distinctUntilChanged];
[self.basic addElement:postal];

QEntryElement *phone = [[QEntryElement alloc] init];
phone.title = @"Phone";
RAC(phone, textValue) = [RACObserve(self, viewModel.phoneNumber) distinctUntilChanged];
RAC(self, viewModel.phoneNumber) = [RACObserve(phone, textValue) distinctUntilChanged];
[self.basic addElement:phone];

I would like to be able to call a method like:

-(QEntryElement *)racEntryElementWithTitle:(NSString *)title andModel(ViewModel *)model andProperty:(NSString*)property;

Is there some way to do this?

Ben M.
  • 430
  • 2
  • 11

2 Answers2

1

Generally, if you want two-way data binding, you should use a RACChannel, so you don't have to explicitly use -distinctUntilChanged to prevent the two-way data binding infinite loop. To do this with KVO, you create two RACChannels (one for each KVO'd property) and assign one channel's followingTerminal to the other's followingTerminal property. This is normally done for you by the RACChannelTo() macro, but if you want to dynamically supply the key paths as NSStrings, you can't use that macro; you need to dive in and use the RACKVOChannel class directly:

(This code is untested.)

- (QEntryElement *)entryElementWithTitle:(NSString *)title boundWithModel(ViewModel *)model atKeyPath:(NSString*)keyPath
{
    QEntryElement *ee = [[QEntryElement alloc] init];
    ee.title = title;
    RACChannel *modelChannel = [[RACKVOChannel alloc] initWithTarget:model keyPath:keyPath nilValue:@""];
    RACChannelTo(ee, textValue, @"") = modelChannel.followingTerminal;
    return ee;
}


// later...

self.root = [[QRootElement alloc] init];
self.root.title = self.title;
self.root.grouped = YES;

self.basic = [[QSection alloc] init];
self.basic.title = @"Basic Information";
[self.root addSection:self.basic];

[self.basic addElement:[self entryElementWithTitle:@"Address" boundWithModel:self.viewModel atKeyPath:@"address"];
[self.basic addElement:[self entryElementWithTitle:@"City"    boundWithModel:self.viewModel atKeyPath:@"city"];
[self.basic addElement:[self entryElementWithTitle:@"State"   boundWithModel:self.viewModel atKeyPath:@"state"];
[self.basic addElement:[self entryElementWithTitle:@"Zip"     boundWithModel:self.viewModel atKeyPath:@"postalCode"];
[self.basic addElement:[self entryElementWithTitle:@"Phone"   boundWithModel:self.viewModel atKeyPath:@"phoneNumber"];
erikprice
  • 6,240
  • 3
  • 30
  • 40
0

The problem you are no doubt facing is those macros. One approach to this would be to run the preprocessor to see what the output of the macro looks like.

Fore example, this line:

RAC(postal, textValue) = [RACObserve(self, viewModel.postalCode) distinctUntilChanged];

Expands to the following:

[[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(postal) nilValue:(((void *)0))][@(((void)(__objc_no && ((void) postal. textValue, __objc_no)), "textValue"))] = [[(id)(self) rac_valuesForKeyPath:@(((void)(__objc_no && ((void)self.viewModel.postalCode, __objc_no)), "viewModel.postalCode")) observer:self] distinctUntilChanged];

This should allow you to parameterise the parts you require.

ColinE
  • 68,894
  • 15
  • 164
  • 232