preface
There are many online documents about the use of Reactive Cocoa, but the application demo is either too small or too large for beginners. After a period of self-study, I have written a complete demo, which fully follows the ARCHITECTURE design of MVVM. Making portal
This article is not suitable for children’s shoes without any Reactive Cocoa foundation. Please refer to the basics of Reactive Cocoa. IOS ReactiveCocoa most commonly used API (can be used as a manual query) ReactiveCocoa official GitHub An architectural overview of ReactiveCocoa V2.5 source code parsing
Run the demo
- Please proceed before running
pod install
- After running, the search button is unavailable if the search box is not entered. Enter the search box
The movie name
orThe director of
And so on, such as Zhang Yimou, the search button can be used, click the button can get the relevant movie search results; - The demo network data adopts the movie search function in Douban Api V2;
Programming instructions
The main interface
- The main interface consists of two classes
HomeViewController
andHomeViewModel
Because model is too simple, it is defined directly in ViewModel HomeViewModel
Defines the search criteriasearchConditons
String and signal whether the string is empty with the button availablesearchBtnEnableSignal
Binding;HomeViewController
First place the ViewModelsearchConditons
The string is bound to the content of the input box, and then the search button’senable
Property and ViewModelsearchBtnEnableSignal
Binding;- The above two steps can be realized by judging whether the content of the input box is empty to determine the search button
enable
Properties; - Click the button to jump to the page;
HomeViewModel is defined as follows:
#import <Foundation/Foundation.h>
#import <ReactiveObjC.h>
@interface HomeViewModel : NSObject
@property (nonatomic, copy) NSString *searchConditons;
@property (nonatomic, strong, readonly) RACSignal *searchBtnEnableSignal;
@end
Copy the code
#import "HomeViewModel.h"
@implementation HomeViewModel
-(instancetype)init{
if (self = [super init]) {
[self setUp];
}
return self;
}
- (void)setUp{
[self setupSearchBtnEnableSignal];
}
- (void)setupSearchBtnEnableSignal {
_searchBtnEnableSignal = [RACSignal combineLatest:@[RACObserve(self, searchConditons)] reduce:^id(NSString *searchConditions){
return @(searchConditions.length);
}];
}
@end
Copy the code
HomeViewController is defined as follows:
#import "HomeViewController.h"
#import "MovieViewController.h"
#import "UIButton+FillColor.h"
#import "HomeViewModel.h"
#import <UIButton+JKBackgroundColor.h>
@interface HomeViewController ()
@property (weak, nonatomic) IBOutlet UITextField *textContent;
@property (weak, nonatomic) IBOutlet UIButton *btnSearch;
@property(nonatomic, strong) HomeViewModel *homeVM;
@end
@implementation HomeViewController
-(HomeViewModel *)homeVM{
if(! _homeVM) { _homeVM = [[HomeViewModel alloc]init]; }return _homeVM;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
_btnSearch.enabled = false;
[_btnSearch setBackgroundColor:[UIColor lightGrayColor] forState:UIControlStateDisabled];
[_btnSearch setBackgroundColor:[UIColor blueColor] forState:UIControlStateNormal]; RAC(self.homeVM, searchConditons) = self.textContent.rac_textSignal; RAC(self.btnSearch, enabled) = self.homeVM.searchBtnEnableSignal; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated.} - (IBAction)onClick (UIButton *)sender {// enter the next interface UIStoryboard * storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
MovieViewController * destViewController = [storyboard instantiateViewControllerWithIdentifier:@"MovieViewController"];
destViewController.conditions = _textContent.text;
[self.navigationController pushViewController:destViewController animated:YES];
}
@end
Copy the code
Search results interface
- The search results interface consists of three classes
Movie
andMovieViewModel
As well asMovieViewController
; Movie
Including film title, date, director, leading actors and pictures; In fact,Douban Api V2There’s a lot more movie data to be returned, so here’s a selection;
#import <Foundation/Foundation.h>
@interface Movie : NSObject
@property(nonatomic, strong) NSString * title;
@property(nonatomic, strong) NSString * year;
@property(nonatomic, strong) NSArray *casts;
@property(nonatomic, strong) NSArray *directors;
@property(nonatomic, strong) NSDictionary *images;
+ (instancetype)movieWithDict:(NSDictionary *)dict;
@end
Copy the code
#import "Movie.h"
@implementation Movie
+(instancetype)movieWithDict:(NSDictionary *)dict{
Movie *movie = [[Movie alloc]init];
movie.year = dict[@"year"];
movie.title = dict[@"title"];
movie.casts = dict[@"casts"];
movie.directors = dict[@"directors"];
movie.images = dict[@"images"];
return movie;
}
@end
Copy the code
MovieViewModel
It includes business logic code: define commands, network requests, get data, send data,
Note: RACSignal is used here, not RACSignal. Beginners may struggle to understand the difference between the two. RACCommand works both ways: the speaker gives a speech, the audience below hears it and gives feedback, and the speaker responds to that feedback. In this demo, the MovieViewController sends a command, the MovieViewModel receives the command, makes a network request, and sends the network data packet, and the MovieViewController parses and displays the received data.
The definition is as follows:
#import <Foundation/Foundation.h>
#import <ReactiveObjC.h>
@interface MovieViewModel : NSObject
@property (nonatomic, strong, readonly) RACCommand *requestCommand;
@property (nonatomic, copy, readonly) NSArray *movies;
@end
Copy the code
#import "MovieViewModel.h"
#import "NetworkManager.h"
#import "Movie.h"
@implementation MovieViewModel
-(instancetype)init{
if (self = [super init]) {
[self setup];
}
return self;
}
- (void)setup {
_requestCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
NSLog(@"% @", input);
RACSignal *requestSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NetworkManager *manager = [NetworkManager manager];
[manager getDataWithUrl:@"https://api.douban.com/v2/movie/search" parameters:input success:^(id json) {
[subscriber sendNext:json];
[subscriber sendCompleted];
} failure:^(NSError *error) {
}];
return nil;
}];
return [requestSignal map:^id _Nullable(id _Nullable value) {
NSMutableArray *dictArray = value[@"subjects"];
NSArray *modelArray = [dictArray.rac_sequence map:^id(id value) {
return [Movie movieWithDict:value];
}].array;
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"year" ascending:NO];
_movies = [modelArray sortedArrayUsingDescriptors:@[sortDescriptor]];
NSLog(@"% @",_movies.description);
return nil;
}];
}];
}
@end
Copy the code
MovieViewController
It includes sending commands, data parsing, and data display. The definition is as follows:
#import <UIKit/UIKit.h>
@interface MovieViewController : UITableViewController
@property(nonatomic, copy)NSString *conditions;
@end
Copy the code
#import "MovieViewController.h"
#import "Movie.h"
#import "MovieViewModel.h"
#import "MovieCell.h"
#import <YYWebImage/YYWebImage.h>
#import <ProgressHUD.h>
#import <SVProgressHUD.h>
#import "UITableView+FDTemplateLayoutCell.h"
@interface MovieViewController ()
@property (nonatomic, strong)MovieViewModel *movieVM;
@end
@implementation MovieViewController
-(MovieViewModel *)movieVM{
if(! _movieVM) { _movieVM = [[MovieViewModel alloc]init]; }return_movieVM; } - (void)viewDidLoad { [super viewDidLoad]; [self.movieVM.requestCommand.executionSignals.switchToLatest subscribeNext:^(id x) { [self.tableView reloadData]; [SVProgressHUD dismiss]; }]; NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; parameters[@"q"] = _conditions;
[self.movieVM.requestCommand execute:parameters];
[SVProgressHUD show];
self.tableView.fd_debugLogEnabled = YES;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.movieVM.movies.count;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return [tableView fd_heightForCellWithIdentifier:@"cellID" configuration:^(MovieCell* cell) {
[self configureCell:cell atIndexPath:indexPath];
}];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MovieCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cellID" forIndexPath:indexPath];
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(MovieCell *)cell atIndexPath:(NSIndexPath *)indexPath {
Movie *movie = self.movieVM.movies[indexPath.row];
NSDictionary *dicImage = movie.images;
NSString *imageStr = dicImage[@"large"];
NSURL *imageUrl = [NSURL URLWithString:imageStr];
// progressive
[cell.movieImageView yy_setImageWithURL:imageUrl options:YYWebImageOptionProgressive];
// progressive with blur and fade animation (see the demo at the top of this page)
[cell.movieImageView yy_setImageWithURL:imageUrl options:YYWebImageOptionProgressiveBlur | YYWebImageOptionSetImageWithFadeAnimation];
cell.title.text = movie.title;
NSString *year = @"Release time :";
cell.year.text = [year stringByAppendingString:movie.year];
NSString *directors = @"Director:";
for (NSDictionary *dict in movie.directors) {
NSString *directname = dict[@"name"];
directors = [directors stringByAppendingFormat:@"% @.",directname];
}
cell.directors.text = directors;
NSString *casts = @"Starring:";
for (NSDictionary *dict in movie.casts) {
NSString *castname = dict[@"name"];
casts = [casts stringByAppendingFormat:@"% @.",castname];
}
cell.casts.text = casts;
}
Copy the code
Reference: IOS ReactiveCocoa most commonly used API (can be used as a manual query) ReactiveCocoa official GitHub An architectural overview of ReactiveCocoa V2.5 source code parsing