博客> 再探MVVM的使用
再探MVVM的使用
1小时前 评论:0 阅读:134 二日三点
ios 设计模式 MVVM

Demo界面一个是UITableView一个是UICollectionView 两个界面的相互交互 算是比较简单的界面。界面比较丑(囧)

 Simulator Screen Shot - iPhone 5s - 2018-05-14 at 11.41.31.png

放上我的结构图

 9871D484-CFA9-46F3-A77E-A69165263896.png

原理和我之前的一篇文章一样,文章地址:http://blog.cocoachina.com/article/69922

这个demo主要模拟一个商品大类的选择界面。主体来说MVVM设计模式的表现比较良好,C层中的代码量控制在了100以内。逻辑部分只用了一个API来更新UI,解耦合算比较好。

关于MVVM的具体原理我就不做过多的阐述,论坛中有些文章写的比较好的。我是将理论转化为具体应用实例来和大家一起学习,共同进步.

先来看下C层的代码吧

#import "ClassifyViewController.h"
#import "MKGoodClassSelectView.h"
#import "MKSubGoodClassSelectView.h"
#import "Masonry.h"
#import "MKGoodClassViewModel.h"
#import "MKSubGoodClassViewModel.h"

@interface ClassifyViewController ()

@property (strong, nonatomic) MKGoodClassSelectView *goodClassSelectView;//左侧商品类

@property (strong, nonatomic) MKSubGoodClassSelectView *subGoodClassSelectView;//右侧商品类

@end

@implementation ClassifyViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor grayColor];

    //1.创建UI视图
    self.goodClassSelectView = [MKGoodClassSelectView new];
    [self.view addSubview:self.goodClassSelectView];

    self.subGoodClassSelectView = [MKSubGoodClassSelectView new];
    [self.view addSubview:self.subGoodClassSelectView];

    [self.goodClassSelectView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.mas_equalTo(64);
        make.bottom.mas_equalTo(0);
        make.left.mas_equalTo(0);
        make.width.mas_equalTo(60);
    }];

    [self.subGoodClassSelectView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.goodClassSelectView.mas_top);
        make.bottom.equalTo(self.goodClassSelectView.mas_bottom);
        make.left.equalTo(self.goodClassSelectView.mas_right).offset(8);
        make.right.mas_equalTo(0);
    }];

    //2.加载数据
    [self upDataMKGoodClassSelectView];
//    [self upDataMKSubGoodClassSelectView:[NSDictionary new]];
}

- (void)upDataMKGoodClassSelectView {
    //发起网络请求
    [[[MKGoodClassViewModel alloc] init] setNetWorkingWithParams:nil
                                               withProgressBlock:^(double progress) {
        // 加载中的做法 一般用来做加载动画的计时
    }WithSuccessBlock:^(id  _Nullable responseObject) { //更新UI界面
        [MKGoodClassViewModel initalControlWithModel:(MKGoodClassModelList *)responseObject WithMKView:self.goodClassSelectView WithViewController:self buttonHandleSuccess:^(NSString *name,NSString *classId){
            NSLog(@"button Handle Success");
            UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:[NSString stringWithFormat:@"当前点击的商品类 id : %@ name : %@",classId,name] delegate:self cancelButtonTitle:@"确认" otherButtonTitles:nil];
            [alertView show];
            // 当点击左侧按钮的时候做更新右侧界面的处理
            [self upDataMKSubGoodClassSelectView:[NSDictionary new]];
        }];
    } WithFailBlock:^(id  _Nullable errorObject) {

    }];
}

- (void)upDataMKSubGoodClassSelectView:(NSDictionary *)params {
    //发起网络请求
    [[[MKSubGoodClassViewModel alloc] init] setNetWorkingWithParams:params
                                                  withProgressBlock:^(double progress) {
        // 加载中的做法 一般用来做加载动画的计时
    }WithSuccessBlock:^(id  _Nullable responseObject) {//更新UI界面
        [MKSubGoodClassViewModel initalControlWithModel:(MKSubGoodClassModelList *)responseObject WithMKView:self.subGoodClassSelectView WithViewController:self buttonHandleSuccess:^{
            NSLog(@"button Handle Success");
        }];
    }WithFailBlock:^(id  _Nullable errorObject) {

    }];
}

@end

可以看出C层的代码比较少具体的数据赋值和界面交互都没有在这里体现,这就是避免了C层的业务逻辑过多导致后期拆分,扩展,调试都比较麻烦

再来看几个自定义view

首先是左侧的商品大类

#import <UIKit>

@interface MKGoodClassSelectView : UIView

@property (strong, nonatomic) NSArray<NSString> *goodClassNameArray;

@property (copy, nonatomic) void (^tableViewCellDidSelected)(NSInteger index);

@end
#import "MKGoodClassSelectView.h"
#import "Masonry.h"

@interface MKGoodClassSelectView()<UITableViewDataSource>

@property (strong, nonatomic) UITableView *tableView;

@end

@implementation MKGoodClassSelectView

#pragma mark - lazy init

- (UITableView *)tableView {
    if(!_tableView) {
        _tableView = [UITableView new];
        _tableView.delegate = self;
        _tableView.dataSource = self;
    }
    return _tableView;
}

#pragma mark - setter

- (void)setGoodClassNameArray:(NSArray<NSString> *)goodClassNameArray {
    _goodClassNameArray = goodClassNameArray;
    [self.tableView reloadData];
}

- (instancetype)init {
    return [self initWithFrame:CGRectZero];
}

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        [self initalControl];
    }
    return self;
}

- (void)initalControl {

    [self addSubview:self.tableView];
    [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.mas_equalTo(0);
    }];

}

NSInteger const GoodClassTableViewHeight = 30;

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return GoodClassTableViewHeight;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.goodClassNameArray.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *mainCellIdentifier = @"CellIdentifier";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:mainCellIdentifier];

    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:mainCellIdentifier];
//        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

        cell.textLabel.numberOfLines = 0;
        cell.textLabel.font = [UIFont systemFontOfSize:13.f];
        cell.textLabel.textColor = [UIColor orangeColor];
    }

    cell.textLabel.text = self.goodClassNameArray[indexPath.row];

    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    _tableViewCellDidSelected ? _tableViewCellDidSelected(indexPath.row) : nil;
}

这里是直接调用属性的setter方法来做UI刷新

来看下它ViewModel层是怎么做的

#import <Foundation>
#import "MKGoodClassModel.h"
#import "MKGoodClassSelectView.h"
#import "GoodClassData.h"
#import "NetWorkingViewModel.h"

@interface MKGoodClassViewModel : NetWorkingViewModel

@property (strong, nonatomic) MKGoodClassModelList *list;

+ (void)initalControlWithModel:(MKGoodClassModelList *)model
                    WithMKView:(MKGoodClassSelectView *)view
            WithViewController:(UIViewController *)viewController
           buttonHandleSuccess:(void(^)(NSString *name,
                                        NSString *classId))buttonHandleSuccess;

@end
#import "MKGoodClassViewModel.h"

@implementation MKGoodClassViewModel

- (MKGoodClassModelList *)list {
    if (!_list) {
        _list = [GoodClassData setMKGoodClassModel];
    }
    return _list;
}

//这个方法是继续于NetWorkingViewModel的

- (void)setNetWorkingWithParams:(NSDictionary *)Params
              withProgressBlock:(ProgressBlock)progressBlock
               WithSuccessBlock:(SuccessBlock)successBlock
                  WithFailBlock:(FailBlock)failBlock {
    //这里直接使用懒加载的方式模拟网络数据
    successBlock(self.list);
}

+ (void)initalControlWithModel:(MKGoodClassModelList *)model
                    WithMKView:(MKGoodClassSelectView *)view
            WithViewController:(UIViewController *)viewController
           buttonHandleSuccess:(void(^)(NSString *name,
                                        NSString *classId))buttonHandleSuccess {
    //这里调用set方法来更新view的界面
    [view setGoodClassNameArray:(NSArray<NSString> *)[model.list valueForKeyPath:@"goodClassName"]];
    [view setTableViewCellDidSelected:^(NSInteger index) {
        //当被row被点击的时候 我们将一些数据回调出来
        buttonHandleSuccess([model.list[index] valueForKeyPath:@"goodClassName"],
                            [model.list[index] valueForKeyPath:@"goodClassId"]);
    }];
}

@end

可以看到直接将我们的数据层对象 网络请求 View界面的数据更新都放在这里完成 当然点击的界面的交互 我就直接采取block方式(delegate性能上要比block要好点,但是我比较懒,so)

同样的我们来看下右侧列表的view和viewmodel层

#import <UIKit>
#import "MKSubGoodClassModel.h"

@interface MKSubGoodClassSelectView : UIView

@property (strong, nonatomic) MKSubGoodClassModelList *subGoodClassModelList;

@end
#import "MKSubGoodClassSelectView.h"
#import "Masonry.h"
#import "MKSubGoodClassCollectionViewCell.h"
#import "MKSubGoodClassReusableView.h"

@interface MKSubGoodClassSelectView()<UICollectionViewDelegate>

@property (strong, nonatomic) UICollectionView *collectionView;

@end

@implementation MKSubGoodClassSelectView

#pragma mark - Lazy init

- (void)setSubGoodClassModelList:(MKSubGoodClassModelList *)subGoodClassModelList {
    _subGoodClassModelList = subGoodClassModelList;
    [self.collectionView reloadData];
}

- (instancetype)init {
    return [self initWithFrame:CGRectZero];
}

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        [self initalControl];
    }
    return self;
}

- (void)initalControl {

    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
    [layout setScrollDirection:UICollectionViewScrollDirectionVertical];
    layout.minimumInteritemSpacing = 0.0f;
    layout.minimumLineSpacing = 0.0f;

    self.collectionView = [[UICollectionView alloc]initWithFrame:CGRectZero collectionViewLayout:layout];
    self.collectionView.delegate = self;
    self.collectionView.dataSource = self;
    self.collectionView.backgroundColor = [UIColor whiteColor];
    [self addSubview:self.collectionView];
    [self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.mas_equalTo(0);
    }];

    [self.collectionView registerClass:[MKSubGoodClassReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:NSStringFromClass([MKSubGoodClassReusableView class])];

    [self.collectionView registerClass:[MKSubGoodClassCollectionViewCell class] forCellWithReuseIdentifier:NSStringFromClass([MKSubGoodClassCollectionViewCell class])];

}

#pragma mark - UICollectionView delegate & data source

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return self.subGoodClassModelList.list.count;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {

    MKSubGoodClassModel *model = self.subGoodClassModelList.list[section];

    return model.goodClass.count;
}

//对外开放的类是GCModularViewCell,所有使用本控件的cell继承此类
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    MKSubGoodClassCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([MKSubGoodClassCollectionViewCell class]) forIndexPath:indexPath];
    return cell;
}

//cell之间默认是没有间距的,如要实现间距在cell内部自己留margin
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
    return 0;
}

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
    return 0;
}

//cell的宽度都一样,由总宽度/cell个数得到;高度是dataSource回调
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    return CGSizeMake(45,45);
}

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
    if (kind == UICollectionElementKindSectionHeader) {
        MKSubGoodClassReusableView *cell = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:NSStringFromClass([MKSubGoodClassReusableView class]) forIndexPath:indexPath];
        [cell setText:[self.subGoodClassModelList.list[indexPath.row] valueForKeyPath:@"className"]];
        return cell;
    }else {
        return [UICollectionReusableView new];
    }
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
    return CGSizeMake(0, MKSubGoodClassReusableView_height);
}

//点击选中的delegate回调转发
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    //这里你可以写你的回调方法
}

@end
#import <Foundation>
#import "MKSubGoodClassModel.h"
#import "MKSubGoodClassSelectView.h"
#import "SubGoodClassData.h"
#import "NetWorkingViewModel.h"

@interface MKSubGoodClassViewModel : NetWorkingViewModel

@property (strong, nonatomic) MKSubGoodClassModelList *list;

+ (void)initalControlWithModel:(MKSubGoodClassModelList *)model
                    WithMKView:(MKSubGoodClassSelectView *)view
            WithViewController:(UIViewController *)viewController
           buttonHandleSuccess:(void(^)())buttonHandleSuccess;

@end
#import "MKSubGoodClassViewModel.h"

@implementation MKSubGoodClassViewModel

- (MKSubGoodClassModelList *)list {
    if (!_list) {
        _list = [SubGoodClassData setMKSubGoodClassModelList];
    }
    return _list;
}

//这个方法是继续于NetWorkingViewModel的
- (void)setNetWorkingWithParams:(NSDictionary *)Params
              withProgressBlock:(ProgressBlock)progressBlock
               WithSuccessBlock:(SuccessBlock)successBlock
                  WithFailBlock:(FailBlock)failBlock {
    //这里直接使用懒加载的方式模拟网络数据
    successBlock(self.list);
}

+ (void)initalControlWithModel:(MKSubGoodClassModelList *)model
                    WithMKView:(MKSubGoodClassSelectView *)view
            WithViewController:(UIViewController *)viewController
           buttonHandleSuccess:(void(^)())buttonHandleSuccess {
    //这里调用set方法来更新view的界面
    [view setSubGoodClassModelList:model];

    //上面的buttonHandleSuccess 你可以将点击的商品的下标回调出来 用法和 MKGoodClassViewModel 一样
    //我就偷下懒
}

@end

这个其实跟上面的设计差不多

那么这两个界面的交互,其实就是靠视图的block来完成的。 C层中有这么两个方法

//这个方法是左侧视图请求接口进行数据刷新的方法
- (void)upDataMKGoodClassSelectView {
    //发起网络请求
    [[[MKGoodClassViewModel alloc] init] setNetWorkingWithParams:nil
                                               withProgressBlock:^(double progress) {
        // 加载中的做法 一般用来做加载动画的计时
    }WithSuccessBlock:^(id  _Nullable responseObject) { //更新UI界面
        [MKGoodClassViewModel initalControlWithModel:(MKGoodClassModelList *)responseObject WithMKView:self.goodClassSelectView WithViewController:self buttonHandleSuccess:^(NSString *name,NSString *classId){
            //这里我们得到了点击了哪个按钮的回调,把相应的数据返回回来
            NSLog(@"button Handle Success");
            UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:[NSString stringWithFormat:@"当前点击的商品类 id : %@ name : %@",classId,name] delegate:self cancelButtonTitle:@"确认" otherButtonTitles:nil];
            [alertView show];
            // 当点击左侧按钮的时候做更新右侧界面的处理
            [self upDataMKSubGoodClassSelectView:[NSDictionary new]];
        }];
    } WithFailBlock:^(id  _Nullable errorObject) {

    }];
}

//这个方法是右侧视图的刷新
- (void)upDataMKSubGoodClassSelectView:(NSDictionary *)params {
    //发起网络请求
    [[[MKSubGoodClassViewModel alloc] init] setNetWorkingWithParams:params
                                                  withProgressBlock:^(double progress) {
        // 加载中的做法 一般用来做加载动画的计时
    }WithSuccessBlock:^(id  _Nullable responseObject) {//更新UI界面
        [MKSubGoodClassViewModel initalControlWithModel:(MKSubGoodClassModelList *)responseObject WithMKView:self.subGoodClassSelectView WithViewController:self buttonHandleSuccess:^{
            NSLog(@"button Handle Success");
        }];
    }WithFailBlock:^(id  _Nullable errorObject) {

    }];
}

这两个方法就是视图点击的时候在C层进行处理的方法 其实就是获取点击数据进行UI刷新

这个Demo可以看出确实减少C层臃肿的代码端,而且十分容易做代码拆分,写完之后,相似的界面可以直接使用,避免了重复轮子的可能,后期就算更改逻辑,也只是修改ViewModel层。致此,你可以愉快的搬砖了!

Demo下载地址: Enter your link description here: https://gitee.com/huangyangyang/MVVM

收藏
0
sina weixin mail 回到顶部