LPDMvvmKit supports CocoaPods[1], add the following line to your Podfile[2]
pod LPDMvvmKitCopy the code
Divided into three Subspecs
LPDMvvmKit/Additions mainly provides code for some commonly used tool classes
pod LPDMvvmKit/AdditionsCopy the code
LPDMvvmKit/Controls currently provides some Controls, LPDToastView, LPDAlertView
pod LPDMvvmKit/ControlsCopy the code
LPDMvvmKit/Mvvm is the main feature provided by LPDMvvmKit, and since the first two Subspecs are dependent, just add the following lines
pod LPDMvvmKitCopy the code
Can clone and run, the main process is a demo to follow.
View Controller and View Model are decoupled
Mvvvm-related Objective-C libraries can be found on Github as follows:
lizelu/MVVM[3]
shenAlexy/MVVM[4]
leichunfeng/MVVMReactiveCocoa[5]
lovemo/MVVMFramework[6]
All of these libraries are good enough to understand that each of these libraries has its own usage scenarios, because these scenarios do not meet LPDMvvmKit’s definition of layers and the boundaries between layers, so the wheel has to be built.
LPDMvvmKit defines each layer
# | layer | define |
---|---|---|
1 | Model | POCO model[7] |
2 | View | A View or a ViewController, normally in a ViewController, the data binding between the View and the ViewModel, if the View is UITableViewCell, UICollectionViewCell, etc., It also does data binding in the View |
3 | ViewModel | Maintains data properties (holds Model), maintains state properties, and responds to logic for user operations (function, RACSignal, RACCommand) |
4 | Service | This layer provides external interfaces on which the system depends, such as the network call layer, system positioning, and so on |
Invoke Function, Subscribe RACSignal, bind RACCommand, etc. This is not difficult to do. Invoke Function, Subscribe RACSignal, bind RACCommand How do I jump pages from business logic moved to the ViewModel layer? So what we’re doing right now is simplifying navigation, rewriting navigation interfaces, all push, pop, present, Dismiss the operation interface are encapsulated two protocol that are related to navigation LPDNavigationControllerProtocol [8], LPDNavigationViewModelProtocol [9], When you present or push a View controller, you have to embed it in the Navigation View Controller, and similarly present or push a View Model, It must be nested in the Navigation View model, this does not add any more complexity, but does not need to be changed when navigation is needed.
Please refer to source code for more details, may not be the best solution, welcome issue
Examples of navigation one-to-one correspondence
LPDHomeViewModel *vm = [[LPDHomeViewModel alloc] ];
LPDHomeViewController *vc = [[LPDHomeViewController alloc] initWithViewModel:vm];
[.navigation lpd_pushViewController:vc animated:];
LPDHomeViewModel *vm = [[LPDHomeViewModel alloc] ];
[.navigation pushViewModel:vm animated:];Copy the code
[.navigation lpd_popViewControllerAnimated:];
[.navigation popViewModelAnimated:];Copy the code
[.navigation lpd_popToRootViewControllerAnimated];
[.navigation popToRootViewModelAnimated:];Copy the code
LPDHomeViewModel *vm = [[LPDHomeViewModel alloc] ];
LPDNavigationViewModel *nvm = [[LPDNavigationViewModel alloc] initWithRootViewModel:vm];
[.navigation lpd_presentViewController:[[LPDNavigationController alloc] initWithViewModel:nvm] animated: completion:];
LPDHomeViewModel *vm = [[LPDHomeViewModel alloc] ];
[.navigation presentViewModel:[[LPDNavigationViewModel alloc] initWithRootViewModel:vm] animated: completion:];
Copy the code
[.navigation lpd_dismissViewControllerAnimated: completion:];
[.navigation dismissViewModelAnimated: completion:];Copy the code
An example of a child ViewController
To implement child ViewControllers, now you can do this
LPDWaybillsViewModel *waybillsViewModel = [[LPDWaybillsViewModel alloc] ];
waybillsViewModel.title = ;
waybillsViewModel.waybillStatus = LPDWaybillStatusFetching;
[ addChildViewModel:waybillsViewModel];
waybillsViewModel = [[LPDWaybillsViewModel alloc] ];
waybillsViewModel.title = ;
waybillsViewModel.waybillStatus = LPDWaybillStatusDelivering;
[ addChildViewModel:waybillsViewModel];Copy the code
Example of a progress bar for form submission
Even simpler. One line of code
self.submitting = ; // Show
self.submitting = ; // hideCopy the code
An example of a loading progress bar
You need to set the beginLodingBlock and endLodingBlock to display and unload the progress bar, and then you don’t have to do anything else, and you just leave it to the framework to do it, and we’ll talk about the tableView and collectionView loading
[ beginLodingBlock:^(UIView *_Nonnull view) { UIView *contentView = [view viewWithTag:777777]; (contentView) { return; } contentView = [[UIView alloc] initWithFrame:view.bounds]; contentView. = 777777; contentView.backgroundColor = [UIColor clearColor]; [view addSubview:contentView]; UIView *loadingView = [[UIView alloc] initWithFrame:CGRectMake(, , , )]; loadingView.layer.cornerRadius = ; loadingView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:]; UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(, , , )]; imageView.animationImages = @[ [UIImage imageNamed:], [UIImage imageNamed:], [UIImage imageNamed:], [UIImage imageNamed:], [UIImage imageNamed:], [UIImage imageNamed:] ]; [loadingView addSubview:imageView]; imageView.center = CGPointMake(loadingView.width / , loadingView.height / ); [contentView addSubview:loadingView]; // loadingView.center = CGPointMake(contentView.width / 2, contentView.height / 2); ([[[UIDevice currentDevice] systemVersion] floatValue] >= ) { loadingView.center = [[UIApplication sharedApplication] .keyWindow convertPoint:CGPointMake(UIScreen.width / , UIScreen.height / ) toView:view]; } { loadingView.center = CGPointMake([UIApplication sharedApplication].keyWindow.center., [UIApplication sharedApplication].keyWindow.center. - ); } [imageView startAnimating]; }]; [ endLodingBlock:^(UIView *_Nonnull view) { UIView *contentView = [view viewWithTag:777777]; (contentView) { [contentView removeFromSuperview]; } }];Copy the code
An example of a drop-down refresh
The drop-down refresh, which uses MJRefresh by default, can be extended and then customized with initHeaderBlock to implement the drop-down refresh. It’s not too easy to implement the drop-down refresh by adding two lines of code in a subclass of LPDScrollViewController, Implement loadingSignal in the corresponding ViewModel
self.scrollView = self.tableView;
self.needLoading = ;Copy the code
Pull up to load more examples
MJRefresh is used by default for more pull-up loadings. Currently, customization is not supported. The implementation of more pull-up loadings is very simple
self.needLoadingMore = ;Copy the code
See demo for more details
data binding
Data binding is the root of MVVM decoupling. As mentioned above, the problem of view Model and View Controller coupling can also be solved through signal flow of ReactiveCocoa to achieve navigation synchronization. There are two types of data binding objects, property and collection. It is a prerequisite for data binding that changes in these two objects can be notified. Corresponding property changes in Objective-C can be implemented through KVO, of course, using RAC is easier, which will not be explained here. However, there is no good way to send corresponding notification of collection changes, and corresponding classes (such as NSMutableArray, NSMutableDictionary and other collection classes) can be extended to achieve this purpose. These collections ultimately exist as data sources for a UITableView or UICollectionView, and the framework now implements both UITableView and UICollectionView through delegates, which is pretty cumbersome (and I’m not alone). I really hope to be able to completely solve the two problems, so I have LPDTableViewModelProtocol [10], LPDCollectionViewModelProtocol [11] and so on a series of code, Mainly in order to solve the UITableView and UICollectionView ViewModel data binding and simplify the relevant code, the amount of code required to achieve more, interested or see the code is more intuitive. The main design idea is as follows: Add and delete interfaces are encapsulated as a protocol. Instead of using a delegate, you just need to call the corresponding interface. In addition, common operations such as didSelect are changed from the delegate method to RACSignal. The code can be aggregated so that the related code logic does not need to be added to multiple discrete functions.
Some examples in the demo, loading tableView data
-()reloadTable { (self.datas && self.datas.count > ) { NSMutableArray *cellViewModels = [NSMutableArray array]; (LPDPostModel *model in self.datas) { LPDTablePostCellViewModel *cellViewModel = [[LPDTablePostCellViewModel alloc]initWithViewModel:.tableViewModel]; cellViewModel.model = model; [cellViewModels addObject:cellViewModel]; } [.tableViewModel replaceSectionWithCellViewModels:cellViewModels withRowAnimation:UITableViewRowAnimationTop]; }{ [.tableViewModel removeAllSections]; }}Copy the code
Add a cell
LPDPostModel *model = [[LPDPostModel alloc]init];
model.userId = 111111;
model.identifier = 1003131;
model.title = First Chapter;
model.body = GitBook allows you to organize your book into chapters, each chapter is stored in a separate file like this one.;
LPDTablePostCellViewModel *cellViewModel = [[LPDTablePostCellViewModel alloc]initWithViewModel:.tableViewModel];
cellViewModel.model = model;
[.tableViewModel insertCellViewModel:cellViewModel atIndex: withRowAnimation:UITableViewRowAnimationLeft];
Copy the code
Adding Cells in Batches
NSMutableArray *cellViewModels = [NSMutableArray array]; LPDTableDefaultCellViewModel *cellViewModel1 = [[LPDTableDefaultCellViewModel alloc] initWithViewModel:.tableViewModel]; cellViewModel1.text = ; Cellviewmodel1.detail = Royal jelly made; cellViewModel1.image = [UIImage imageNamed:]; [cellViewModels addObject:cellViewModel1]; LPDTableValue1CellViewModel *cellViewModel2 = [[LPDTableValue1CellViewModel alloc] initWithViewModel:.tableViewModel]; cellViewModel2.text = ; Cellviewmodel2.detail = Royal jelly made; cellViewModel2.image = [UIImage imageNamed:]; [cellViewModels addObject:cellViewModel2]; LPDTableValue2CellViewModel *cellViewModel3 = [[LPDTableValue2CellViewModel alloc] initWithViewModel:.tableViewModel]; cellViewModel3.text = ; Cellviewmodel3. detail = Royal jelly made; [cellViewModels addObject:cellViewModel3]; LPDTableSubtitleCellViewModel *cellViewModel4 = [[LPDTableSubtitleCellViewModel alloc] initWithViewModel:.tableViewModel]; cellViewModel4.text = ; Cellviewmodel4.detail = Royal jelly made; cellViewModel4.image = [UIImage imageNamed:]; [cellViewModels addObject:cellViewModel4]; [.tableViewModel insertCellViewModels:cellViewModels atIndex: withRowAnimation:UITableViewRowAnimationLeft];Copy the code
Delete a Cell
[.tableViewModel removeCellViewModelAtIndex: withRowAnimation:UITableViewRowAnimationRight];Copy the code
The cell didSelect
[[[.waybillsTableViewModel.didSelectRowAtIndexPathSignal deliverOnMainThread] takeUntil:[ rac_willDeallocSignal]] subscribeNext:^(RACTuple *tuple) { @strongify(self); __kindof <LPDTableCellViewModelProtocol> cellViewModel = tuple.second; LPDWaybillModel *waybillModel = cellViewModel.model; (waybillModel.cancelCode == ) { LPDWaybillDetailViewModel *detailViewModel = [[LPDWaybillDetailViewModel alloc] ]; detailViewModel.waybillId = waybillModel.waybillId; [.navigation pushViewModel:detailViewModel animated:]; } }];Copy the code
Current supported operations
@property (nonatomic, strong, readonly) RACSignal *willDisplayCellSignal;
@property (nonatomic, strong, readonly) RACSignal *willDisplayHeaderViewSignal;
@property (nonatomic, strong, readonly) RACSignal *willDisplayFooterViewSignal;
@property (nonatomic, strong, readonly) RACSignal *didEndDisplayingCellSignal;
@property (nonatomic, strong, readonly) RACSignal *didEndDisplayingHeaderViewSignal;
@property (nonatomic, strong, readonly) RACSignal *didEndDisplayingFooterViewSignal;
@property (nonatomic, strong, readonly) RACSignal *didHighlightRowAtIndexPathSignal;
@property (nonatomic, strong, readonly) RACSignal *didUnhighlightRowAtIndexPathSignal;
@property (nonatomic, strong, readonly) RACSignal *didSelectRowAtIndexPathSignal;
@property (nonatomic, strong, readonly) RACSignal *didDeselectRowAtIndexPathSignal;
@property (nonatomic, strong, readonly) RACSignal *willBeginEditingRowAtIndexPathSignal;
@property (nonatomic, strong, readonly) RACSignal *didEndEditingRowAtIndexPathSignal;Copy the code
For more information about LPDTableViewModel, see UITableView modification. [12]