此项目是将面包旅行跟面包猎人两个应用结合。用ReactiveCocoa+MVVM,最开始想用公司项目,但考虑到要脱敏,不方便。项目中的所有接口都是抓包,只是用来学习练手之用。
ReactiveCocoa(简称为RAC),是由Github开源的一个应用于iOS和OS开发的框架,RAC具有函数式编程和响应式编程的特性。现在ReactiveCocoa已经到RAC5,支持swift3,由于项目是使用的OC,所以我使用的是ReactiveCocoa2.5。如果你的项目是纯OC,也可以使用ReactiveObjC。
ReactiveCocoa的函数式编程方式的学习成本比较大,想要学习ReactiveCocoa的可以看这里。
MVVM是一个UI设计模式。它是MV模式集合中的一员。MV模式还包含MVC(Model View Controller)、MVP(Model View Presenter)等。这些模式的目的在于将UI逻辑与业务逻辑分离,以让程序更容易开发和测试。其中 ViewModel 的主要职责是处理业务逻辑并提供 View 所需的数据,这样 VC 就不用关心业务,自然也就瘦了下来。ViewModel 只关心业务数据不关心 View,所以不会与 View 产生耦合,也就更方便进行单元测试。
MVVM模式依赖于数据绑定,由于iOS没有数据绑定框架。但幸运的是ReactiveCocoa可以很方便的实现这个,所以ReactiveCocoa是实现MVVM的最佳方式。不通过ReactiveCocoa也可以实现MVVM一样可以实现,感兴趣的可以看这篇博客。
@implementation HTCityTravelNotesController
#pragma mark - Life Cycle
- (instancetype)initWithViewModel:(HTCityTravelViewModel *)viewModel
{
if (self = [super init]) {
_viewModel = viewModel;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = SetColor(251, 247, 237);
[self setNavigationBar];
[self bindViewModel];
}
#pragma mark - bind
- (void)bindViewModel
{
self.isSearch = NO;
[self.viewModel.travelCommand execute:@1];
self.bannerView.imageURLSignal = RACObserve(self.viewModel, bannerData);
self.tripBindingHelper = [HTTableViewBindingHelper bindingHelperForTableView:self.tripTableView sourceSignal:RACObserve(self.viewModel, travelData) selectionCommand:nil templateCell:@"HTCityTravelCell"];
@weakify(self);
// 下拉刷新
self.tripTableView.mj_header = [HTRefreshGifHeader headerWithRefreshingBlock:^{
@strongify(self);
[self.viewModel.travelCommand execute:@1];
}];
[[self.viewModel.travelCommand.executing skip:1] subscribeNext:^(NSNumber * _Nullable executing) {
@strongify(self);
if (!executing.boolValue) {
[self.tripTableView.mj_header endRefreshing];
}
}];
// 加载更多
self.tripTableView.mj_footer = [HTRefreshGifFooter footerWithRefreshingBlock:^{
@strongify(self);
[self.viewModel.travelMoreDataCommand execute:@1];
}];
[[self.viewModel.travelMoreDataCommand.executing skip:1] subscribeNext:^(NSNumber * _Nullable executing) {
@strongify(self);
if (!executing.boolValue) {
[self.tripTableView.mj_footer endRefreshing];
}
}];
// 搜索框
[[self
rac_signalForSelector:@selector(textFieldDidBeginEditing:)
fromProtocol:@protocol(UITextFieldDelegate)]
subscribeNext:^(RACTuple *tuple) {
@strongify(self)
if (tuple.first == self.searchTextField){
[self.rightButton setImage:[UIImage imageNamed:@""] forState:UIControlStateNormal];
[self.rightButton setTitle:@"取消" forState:UIControlStateNormal];
self.isSearch = YES;
}
}];
[[self rac_signalForSelector:@selector(textFieldDidEndEditing:) fromProtocol:@protocol(UITextFieldDelegate)] subscribeNext:^(RACTuple *tuple) {
@strongify(self)
if (tuple.first == self.searchTextField){
self.isSearch = NO;
[self.rightButton setImage:[UIImage imageNamed:@"tabbar_user_button_image_22x22_"] forState:UIControlStateNormal];
[self.rightButton setTitle:@"" forState:UIControlStateNormal];
}
}];
RAC(self.viewModel,isSearch) = RACObserve(self, isSearch);
// 导航栏navigationItem
self.rightButton.rac_command = self.viewModel.rightBarButtonItemCommand;
[self.viewModel.rightBarButtonItemCommand.executionSignals.switchToLatest subscribeNext:^(NSNumber *isSearch) {
@strongify(self)
if ([isSearch boolValue]) {
[self.searchTextField resignFirstResponder];
}else{
NSLog(@"进入个人中心");
}
}];
}
@implementation HTFindViewController
#pragma mark - Life Cycle
- (instancetype)initWithViewModel:(HTFindViewModel *)viewModel
{
if (self = [super init]) {
_viewModel = viewModel;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = SetColor(250, 250, 250);
self.navigationItem.title = @"发现";
[self bindViewModel];
}
#pragma mark - bind
- (void)bindViewModel
{
// findTableView
self.findBindingHelper = [HTTableViewBindingHelper bindingHelperForTableView:self.findTableView sourceSignal:RACObserve(self.viewModel, feedData) selectionCommand:self.viewModel.feedDetailCommand templateCell:@"HTFindFeedCell"];
// 刷新viedeoView
self.videoView.modelSignal = RACObserve(self.viewModel, videoData);
@weakify(self);
// 下拉刷新
self.findTableView.mj_header = [HTRefreshGifHeader headerWithRefreshingBlock:^{
@strongify(self);
[self.viewModel.feedDataCommand execute:@1];
}];
[[self.viewModel.feedDataCommand.executing skip:1] subscribeNext:^(NSNumber * _Nullable executing) {
@strongify(self);
if (!executing.boolValue) {
[self.findTableView.mj_header endRefreshing];
}
}];
// 加载更多
self.findTableView.mj_footer = [HTRefreshGifFooter footerWithRefreshingBlock:^{
@strongify(self);
[self.viewModel.feedMoreDataCommand execute:@1];
}];
[[self.viewModel.feedMoreDataCommand.executing skip:1] subscribeNext:^(NSNumber * _Nullable executing) {
@strongify(self);
if (!executing.boolValue) {
[self.findTableView.mj_footer endRefreshing];
}
}];
[self.viewModel.feedDataCommand execute:@1];
}
- iOS 8+
- Xcode 8+