首先,这是一种如何添加一个viewmodel到我的一个视图,只是试试它。 (我想这里这里参考)。
在ViewController中viewDidLoad:
@weakify(self) //Setup signals RAC(self.navigationItem.title) = self.viewmodel.nameSignal; RAC(self.specialtyLabel.text) = self.viewmodel.specialtySignal; RAC(self.bioButton.hidden) = self.viewmodel.hiddenBioSignal; RAC(self.bioTextView.text) = self.viewmodel.bioSignal; RAC(self.profileImageView.hidden) = self.viewmodel.hiddenProfileImageSignal; [self.profileImageView rac_liftSelector:@selector(setImageWithContentsOfURL:placeholderImage:) withObjectsFromArray:@[self.viewmodel.profileImageSignal,[RACTupleNil tupleNil]]]; [self.viewmodel.hasOfficesSignal subscribeNext:^(NSArray *offices) { self.callActionSheet = [[UIActionSheet alloc] initWithTitle:@"Choose Office" delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil]; self.directionsActionSheet = [[UIActionSheet alloc] initWithTitle:@"Choose Office" delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil]; self.callActionSheet.delegate = self; self.directionsActionSheet.delegate = self; }]; [self.viewmodel.officesSignal subscribeNext:^(NSArray *offices){ @strongify(self) for (LMOffice *office in offices) { [self.callActionSheet addButtonWithTitle: office.name ? office.name : office.address1]; [self.directionsActionSheet addButtonWithTitle: office.name ? office.name : office.address1]; //add offices to maps CLLocationCoordinate2D coordinate = {office.latitude.doubleValue,office.longitude.doubleValue}; MKPointAnnotation *point = [[MKPointAnnotation alloc] init]; point.coordinate = coordinate; [self.mapView addAnnotation:point]; } //zoom to include all offices MKMapRect zoomRect = MKMapRectNull; for (id <MKAnnotation> annotation in self.mapView.annotations) { MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate); MKMapRect pointRect = MKMapRectMake(annotationPoint.x,annotationPoint.y,0.2,0.2); zoomRect = MKMapRectUnion(zoomRect,pointRect); } [self.mapView setVisibleMapRect:zoomRect animated:YES]; }]; [self.viewmodel.openingsSignal subscribeNext:^(NSArray *openings) { @strongify(self) if (openings && openings.count > 0) { [self.openingsTable reloadData]; } }];
viewmodel.h
@property (nonatomic,strong) LMProvider *doctor; @property (nonatomic,strong) RACSubject *fetchDoctorSubject; - (RACSignal *)nameSignal; - (RACSignal *)specialtySignal; - (RACSignal *)bioSignal; - (RACSignal *)profileImageSignal; - (RACSignal *)openingsSignal; - (RACSignal *)officesSignal; - (RACSignal *)hiddenBioSignal; - (RACSignal *)hiddenProfileImageSignal; - (RACSignal *)hasOfficesSignal;
viewmodel.m
- (id)init { self = [super init]; if (self) { _fetchDoctorSubject = [RACSubject subject]; //fetch doctor details when signalled @weakify(self) [self.fetchDoctorSubject subscribeNext:^(id shouldFetch) { @strongify(self) if ([shouldFetch boolValue]) { [self.doctor fetchWithCompletion:^(NSError *error){ if (error) { //TODO: display error message NSLog(@"Error fetching single doctor info: %@",error); } }]; } }]; } return self; } - (RACSignal *)nameSignal { return [RACAbleWithStart(self.doctor.displayName) distinctUntilChanged]; } - (RACSignal *)specialtySignal { return [RACAbleWithStart(self.doctor.primarySpecialty.name) distinctUntilChanged]; } - (RACSignal *)bioSignal { return [RACAbleWithStart(self.doctor.bio) distinctUntilChanged]; } - (RACSignal *)profileImageSignal { return [[[RACAbleWithStart(self.doctor.profilePhotoURL) distinctUntilChanged] map:^id(NSURL *url){ if (url && ![url.absoluteString hasPrefix:@"https:"]) { url = [NSURL URLWithString:[NSString stringWithFormat:@"https:%@",url.absoluteString]]; } return url; }] filter:^BOOL(NSURL *url){ return (url != nil && ![url.absoluteString isEqualToString:@""]); }]; } - (RACSignal *)openingsSignal { return [RACAbleWithStart(self.doctor.openings) distinctUntilChanged]; } - (RACSignal *)officesSignal { return [RACAbleWithStart(self.doctor.offices) distinctUntilChanged]; } - (RACSignal *)hiddenBioSignal { return [[self bioSignal] map:^id(NSString *bioString) { return @(bioString == nil || [bioString isEqualToString:@""]); }]; } - (RACSignal *)hiddenProfileImageSignal { return [[self profileImageSignal] map:^id(NSURL *url) { return @(url == nil || [url.absoluteString isEqualToString:@""]); }]; } - (RACSignal *)hasOfficesSignal { return [[self officesSignal] map:^id(NSArray *array) { return @(array.count > 0); }]; }
我正在使用信号的方式吗?具体来说,使用bioSignal更新数据以及使用hiddenBioSignal直接绑定到textView的隐藏属性是有意义的吗?
我的主要问题是移动的问题,已经由代理处理到viewmodel(希望)。代理在iOS世界中是如此常见,我想要找出最好的,甚至只是一个适度可行的解决方案。
例如,对于UITableView,我们需要提供一个委托和一个dataSource。我应该在我的控制器NSUInteger numberOfRowsInTable有一个属性,并绑定到viewmodel上的信号?我真的不清楚如何使用RAC提供我的TableView与tableView中的单元格:cellForRowAtIndexPath:。我只需要做这些“传统”的方式,还是可能有一些类型的信号提供程序的单元格?或者也许最好离开它是怎么回事,因为viewmodel不应该真正关心构建视图,只是修改视图的来源?
此外,是否有一个比我使用主题(fetchDoctorSubject)更好的方法?
任何其他意见,也将赞赏。这项工作的目标是使预取/缓存viewmodel层,可以在需要时在后台加载数据时发出信号,从而减少设备上的等待时间。如果任何可重用的东西来自这个(除了模式),它当然是开源的。
编辑:另一个问题:它看起来像根据文档,我应该使用属性的所有信号在我的viewmodel而不是方法?我想我应该在init中配置它们?或者我应该离开它,因为getter返回新的信号?
关于一些细节。
It looks like according to the documentation,I should be using properties for all of the signals in my viewmodel instead of methods? I think I should configure them in init? Or should I leave it as-is so that getters return new signals?
是的,我们通常只使用反映其模型属性的属性。我们将其配置在ininit有点像:
- (id)init { self = [super init]; if (self == nil) return nil; RAC(self.title) = RACAbleWithStart(self.model.title); return self; }
记住,视图模型只是特定用途的模型。纯朴的旧对象与普通的旧属性。
Am I right in the way I’m using signals? Specifically,does it make sense to have
bioSignal
to update the data as well as ahiddenBioSignal
to directly bind to the hidden property of a textView?
如果生物信号的隐藏是由一些特定的模型逻辑驱动的,那么将它作为视图模型上的一个属性暴露是有意义的。但是尽量不要把它看作隐藏的术语。也许它更多的有效性,加载等。东西没有绑定到具体如何呈现。
For a UITableView,for example,we need to provide both a delegate and a dataSource. Should I have a property on my controller NSUInteger numberOfRowsInTable and bind it to a signal on the viewmodel? And I’m really unclear on how to use RAC to provide my TableView with cells in tableView: cellForRowAtIndexPath:. Do I just need to do these the “traditional” way or is it possible to have some sort of signal provider for the cells? Or maybe it’s best to leave it how it is,because a viewmodel shouldn’t really be concerned with building the views,just modifying the source of the views?
最后一行是完全正确的。您的视图模型应该为视图控制器提供要显示的数据(数组,集合,无论什么),但是视图控制器仍然是表视图的委托和数据源。视图控制器创建单元格,但单元格由视图模型中的数据填充。如果你的单元格比较复杂,你甚至可以有一个单元格视图模型。
Further,is there a better approach than my use of a subject (fetchDoctorSubject)?
考虑在这里使用RACCommand。它会给你一个更好的方式来处理并发请求,错误和线程安全。命令是从视图到视图模型的一种非常典型的通信方式。
Should I have an active property as in the viewmodel example in ReactiveCocoa’s github account?
它只是取决于你是否需要它。在iOS上,可能不太需要比OS X,您可以有多个视图和视图模型分配但不是“活动”一次。
希望这有帮助。看起来你通常在正确的方向前进!