1. 程式人生 > >[iOS]MVVM

[iOS]MVVM

demo:https://download.csdn.net/download/u012881779/10696246 之前使用MVC模式開發,覺得還蠻好用就一直使用著。 最近接觸MVVM比較頻繁,發現相比於MVC它會將網路請求從控制器中分離出來,這樣能有效的為ViewController瘦身。 今天有點時間,就呼叫高德地圖獲取地區的介面寫了一個分層選擇地區的demo。 結構: Controller

#import "GADistrictsViewController.h"
#import "GADistrictsCell.h"
#import "GADistrictsModel.h"
#import "GADistrictsViewModel.h"

@interface GADistrictsViewController () <UITableViewDelegate, UITableViewDataSource>
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (strong, nonatomic) NSMutableArray *dataMArr;
@property (strong, nonatomic) GADistrictsViewModel *viewModel;

@end

@implementation GADistrictsViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [_tableView setSeparatorStyle:NO];
    _tableView.estimatedRowHeight = 100;
    _tableView.rowHeight = UITableViewAutomaticDimension;
    
    if (!_viewModel) {
        _viewModel = [GADistrictsViewModel new];
    }
    @weakify(self)
    NSMutableDictionary *exeDict = [NSMutableDictionary new];
    [exeDict setObject:@"bf4783277065f752f68490c4bf2b79e0" forKey:@"key"];
    [exeDict setObject:@"3" forKey:@"subdistrict"];
    [[_viewModel.districtsCommand execute:exeDict] subscribeNext:^(id  _Nullable result) {
        //NSLog(@"%@",result);
        GADistrictsModel *tempModel = (GADistrictsModel *)[((GAResultDataModel *)result).districts firstObject];
        self_weak_.dataMArr = tempModel.districts;
        [self_weak_.tableView reloadData];
    } error:^(NSError * _Nullable error) {
        
    }];
}

#pragma mark UITableViewDataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return _dataMArr.count;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    GADistrictsModel *sectionModel = [_dataMArr objectAtIndex:section];
    if (_dataMArr.count > section) {
        sectionModel = [_dataMArr objectAtIndex:section];
        NSInteger newIndex = 0;
        if (sectionModel.select) {
            for (int i = 0 ; i < sectionModel.districts.count ; i ++) {
                GADistrictsModel *iModel = [sectionModel.districts objectAtIndex:i];
                newIndex = newIndex + 1;
                if (iModel.select) {
                    for (int j = 0 ; j < iModel.districts.count ; j ++) {
                        //GADistrictsModel *jModel = [iModel.districts objectAtIndex:j];
                        newIndex = newIndex + 1;
                    }
                }
            }
            return newIndex+1;
        }
    }
    return 1;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    GADistrictsModel *sectionModel = [_dataMArr objectAtIndex:indexPath.section];
    if (_dataMArr.count > indexPath.section) {
        sectionModel = [_dataMArr objectAtIndex:indexPath.section];
    }
    GADistrictsModel *model;
    if (sectionModel.select) {
        if (indexPath.row == 0) {
            model = sectionModel;
        } else {
            NSInteger newIndex = 0;
            for (int i = 0 ; i < sectionModel.districts.count ; i ++) {
                GADistrictsModel *iModel = [sectionModel.districts objectAtIndex:i];
                newIndex = newIndex + 1;
                if (indexPath.row == newIndex) {
                    model = iModel;
                    break;
                }
                if (iModel.select) {
                    for (int j = 0 ; j < iModel.districts.count ; j ++) {
                        GADistrictsModel *jModel = [iModel.districts objectAtIndex:j];
                        newIndex = newIndex + 1;
                        if (indexPath.row == newIndex) {
                            model = jModel;
                            break;
                        }
                    }
                }
            }
        }
    } else {
        model = sectionModel;
    }

    if ([[model class] isSubclassOfClass:[GADistrictsModel class]]) {
        GADistrictsCell *cell = [tableView dequeueReusableCellWithIdentifier:@"GADistrictsCell"];
        if (!cell) {
            cell = [[[NSBundle mainBundle] loadNibNamed:@"GADistrictsCell" owner:nil options:nil] objectAtIndex:0];
            cell.selectionStyle = UITableViewCellSelectionStyleNone;
            [cell setBackgroundColor:[UIColor clearColor]];
        }
        cell.model = model;
        [cell.nameBut setTitle:model.name forState:UIControlStateNormal];
        cell.codeLab.text = [model.adcode stringValue];
        if ([model.level isEqualToString:@"province"]) {
            cell.nameBut.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 0);
            [cell.nameBut setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
            cell.codeLab.textColor = [UIColor blueColor];
        } else if ([model.level isEqualToString:@"city"]) {
            cell.nameBut.contentEdgeInsets = UIEdgeInsetsMake(0, 32, 0, 0);
            [cell.nameBut setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
            cell.codeLab.textColor = [UIColor blackColor];
        } else if ([model.level isEqualToString:@"district"] || [model.level isEqualToString:@"street"]) {
            cell.nameBut.contentEdgeInsets = UIEdgeInsetsMake(0, 64, 0, 0);
            [cell.nameBut setTitleColor:[UIColor lightGrayColor] forState:UIControlStateNormal];
            cell.codeLab.textColor = [UIColor lightGrayColor];
        }
        return cell;
    }
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:0 reuseIdentifier:@"UITableViewCell"];
    }
    return cell;
}

#pragma mark UITableViewDelegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    GADistrictsCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    GADistrictsModel *model = cell.model;
    if (model) {
        model.select = !model.select;
        [_tableView reloadData];
    }
}

@end

Cell

#import <UIKit/UIKit.h>
#import "GADistrictsModel.h"

@interface GADistrictsCell : UITableViewCell
@property (weak, nonatomic) IBOutlet UIButton *nameBut;
@property (weak, nonatomic) IBOutlet UILabel *codeLab;
@property (weak, nonatomic) GADistrictsModel *model;

@end

Model

#import "JSONModel.h"

/**
 @protocol GADistrictsModel這個協議是必須要新增的,而且要加在@interface GAResultDataModel之前。
 不然在GAResultDataModel中初始化districts欄位時,就係統會要求這樣寫“NSArray <GADistrictsModel *> *districts”。
 這種寫法存在問題,它並不會將陣列中的NSDictionary初始化為GADistrictsModel。
 */
@protocol GADistrictsModel

@end

@interface GAResultDataModel : JSONModel
@property (strong, nonatomic) NSNumber *newcount;
@property (assign, nonatomic) BOOL info;
@property (assign, nonatomic) NSInteger infocode;
@property (strong, nonatomic) NSString *status;
// "Optional"可選屬性 (就是說這個屬性可以為null或者為空)
@property (strong, nonatomic) NSDictionary <Optional> *suggestion;
@property (strong, nonatomic) NSArray <GADistrictsModel> *districts;

@end


@interface GADistrictsModel : JSONModel
@property (strong, nonatomic) NSNumber *adcode;
@property (strong, nonatomic) NSString *center;
@property (strong, nonatomic) NSString *level;
@property (strong, nonatomic) NSString *name;
// "Ignore"忽略屬性 (就是完全忽略這個屬性)。應為在國家那一級這個欄位返回的陣列型別,在城市那一級這個欄位返回的字串型別。如果不能忽略就需要再單獨建立一個model。
@property (strong, nonatomic) NSString <Ignore> *citycode;
@property (strong, nonatomic) NSArray  <GADistrictsModel>*districts;
@property (assign, nonatomic) BOOL select;

@end

#import "GADistrictsModel.h"

@implementation GAResultDataModel

// 設定所有的屬性為可選(所有屬性值可以為空)
+ (BOOL)propertyIsOptional:(NSString *)propertyName {
    return YES;
}

// key對映
+ (JSONKeyMapper *)keyMapper {
    return [[JSONKeyMapper alloc] initWithModelToJSONDictionary:@{@"newcount": @"count" }];
}

@end


@implementation GADistrictsModel

+ (BOOL)propertyIsOptional:(NSString *)propertyName {
    return YES;
}

@end

ViewModel

#import <Foundation/Foundation.h>
#import "ReactiveObjC.h"

@interface GADistrictsViewModel : NSObject
// 獲取地區
@property (nonatomic,strong) RACCommand *districtsCommand;

@end

#import "GADistrictsViewModel.h"
#import "GADistrictsAPI.h"
#import "GADistrictsModel.h"

@implementation GADistrictsViewModel

- (RACCommand *)districtsCommand {
    if (!_districtsCommand) {
        _districtsCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
            
            return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
                [GADistrictsAPI aMapDistrictsWithParameters:input Success:^(id result) {
                    GAResultDataModel *tempResultModel = [[GAResultDataModel alloc] initWithDictionary:result error:nil];
                    // 傳送訊號
                    [subscriber sendNext:tempResultModel];
                    [subscriber sendCompleted];
                } failed:^(NSError *error) {
                    [subscriber sendError:error];
                }];
                return nil;
            }];
        }];
    }
    return _districtsCommand;
}

@end

API

#import <Foundation/Foundation.h>
#import "AFNetworking.h"

typedef void (^SuccessBlock)(id result);
typedef void (^FailedBlock)(NSError *error);

@interface GADistrictsAPI : NSObject

/// 高德地圖-獲取省市縣
+ (void)aMapDistrictsWithParameters:(NSDictionary *)parameter Success:(SuccessBlock)success failed:(FailedBlock)failed;

@end

#import "GADistrictsAPI.h"

@implementation GADistrictsAPI

+ (void)aMapDistrictsWithParameters:(NSDictionary *)parameter Success:(SuccessBlock)success failed:(FailedBlock)failed {
    NSString *tempUrl = [NSString stringWithFormat:@"https://restapi.amap.com/v3/config/district?key=%@&subdistrict=%@",[parameter objectForKey:@"key"],[parameter objectForKey:@"subdistrict"]];
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    [manager GET:tempUrl parameters:[NSMutableDictionary dictionary] progress:^(NSProgress * _Nonnull uploadProgress) {
        
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        success(responseObject);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        failed(error);
    }];
}

@end

示意圖: