首先可以定义一个枚举
typedef enum{
ViewStatusEdit =1,
ViewStatusRetrieve =2,
ViewStatusNew =3//外面调用的时候,可以只设置edit就行
}ViewStatus;
这个用来标示当前表单处于一个什么样的状态。考虑到新增跟修改外面要加一个区分比较麻烦,那么可以把这个封装在里面,外面调的时候只要告诉表单处于编辑状态即可。例如:
//判断是编辑还是新增
if([NSString isBlankString: _newModel.id]){//只要id为空,那么肯定是新增
_viewStatus = ViewStatusNew;
}
newModel为表单要转换的一个实体,表单要显示的数据都在这个model里面,要提交的信息也都在这里,所以可以假设model张这样:
@interface Model :NSObject
@property(nonatomic, copy) NSString *token;
@property(nonatomic, copy) NSString *id;
@property(nonatomic, copy) NSString *type;
@property(nonatomic, copy) NSString *date;
@property(nonatomic, copy) NSString *time;
@property(assign, nonatomic)float data;
@property(nonatomic, copy) NSString *remark;
@end
初始化数据
我们controller这里只做数据显示,不去处理初始化数据的内容,因为如果加上这段代码可能导致它会很乱。所以我们将初始化放在了viewModel层处理。
+(Model*)getInitModel{
Model*model = [[Model alloc] init];
model.token=@"token";
model.type=@"type";
model.date=@“date";
model.time=@“time";
model.data=5.0;
return model;
}
在这里就可以将想要初始化的数据写上去了。那么在实例化这个controller的时候就需要传入两个参数了。
ViewStatus //表示页面当前的状态
model //带有页面展示的数据
controller的初始化
虽然我们提供了初始化方法,但万一有同学不知道,采用其它初始化方法如init呢?那么我们可以在viewDidLoad方法中,在[super viewDidLoad];之后写上
//如果没有正确调用init方法的处理
_newModel = _newModel ? : [ViewModel getInitModel];
if(_viewStatus ==0){
if([NSString isBlankString:_newModel.id]){
_viewStatus = ViewStatusNew;
}else{
_viewStatus = ViewStatusRetrieve;
}
}
这样一来,即使外面没有调用正确的初始化方法,我表单页面一样可以正常使用
录入项配置
一般表单应该都是用tableView来做的,所以这里只讨论tableView的情况。对于表单来说录入项一般都是固定那几个,所以可以将录入项配置起来,如:
+ (NSArray*) viewConfig{
NSArray*viewConfig=@[
@{
@"key":@“type",
@"title":@"类型"
},
@{
@"key":@“date",
@"title":@"日期"
},
@{
@"key":@“time",
@"title":@"时间"
},
@{
@"key":@"data",
@"title":@"数值"
},
@{
@"key":@“remark",
@"title":@"备注"
}
];
returnviewConfig;
}
这里最好是把key都配置成跟Model属性一样的值,方便在cellForRowAtIndexPath填充cell的时候可以直接使用KVC来取值。这么做一个不好的地方就是一旦Model里面的属性名改变了,而viewConfig没有及时改过来的话,是取不到值的。不过我觉得一来属性名一旦确认后较少去改;二来如果直接用属性访问的话,一旦属性过多,那赋值的代码量就偏大。以防万一我们可以在Model里面特意写上一个提示“如果修改属性名需要修改viewConfig的对应字段”,如
/**
*如果修改属性名需要修改viewModel中viewConfig的对应字段
*/
@interface Model :NSObject
@property(nonatomic, copy) NSString *token;
@property(nonatomic, copy) NSString *id;
@property(nonatomic, copy) NSString *type;
@property(nonatomic, copy) NSString *date;
@property(nonatomic, copy) NSString *time;
@property(assign, nonatomic)float data;
@property(nonatomic, copy) NSString *remark;
@end
所以在cellForRowAtIndexPath中就可以这么写(因为我这边的例子都不是直接在table上面录入的,而是用其它弹出层带有的框录入)
cell.textLabel.text= [item objectForKey:@"title"];
cell.detailTextLabel.text= [_newModel valueForKey:[item objectForKey:@"key"]];
可编辑性
由于业务不同,表单里面的每一项都可能存在可编辑与否的问题,那么我们可以将这部分的逻辑直接写到一个方法里面,当遍历到这个项的时候去根据这个方法来判断是否可以编辑。这样做可以将这部分业务的代码给统一起来,一来好找,二来代码更加规范了。
#pragma mark -cell是否可以编辑
-(BOOL)isCellCanEdit:(NSDictionary*)dict{
if([dict[@"key"] isEqualToString:@"type"]){
return NO;
}
if([dict[@"key"] isEqualToString:@“date"]){
if(_viewStatus==ViewStatusNew){
return YES;
}else{
return NO;
}
}
if([dict[@"key"] isEqualToString:@“time"]){
if(_viewStatus==ViewStatusNew){
return YES;
}else{
return NO;
}
}
if([dict[@"key"] isEqualToString:@“data"]){
if(_viewStatus==ViewStatusNew){
return YES;
}else{
if([_newModel.monitorDate isEqualToString:[NSDate currentDateStringWithFormat:@"yyyy-MM-dd"]]){
return YES;
}
return NO;
}
}
return YES;
}
那么在cellForRowAtIndexPath中就可以这么写
NSDictionary*item =_viewConfig[indexPath.row];
//控制cell的可编辑状态
cell.userInteractionEnabled= [self isCellCanEdit:item];
提交
因为我们传进来的_newModel其实就是要提交的那个model,所以当用户在表单上操作后,对应元素的值就可以直接赋值给_newModel对应的属性。而且需要做是否可以提交的处理,因为如果达到要求的时候,一般应该应该是把保存按钮置为可点状态。所以我们可以把这个判断封装起来,每次值改变后就调用这个方法,可以确保提交按钮的状态改变。
#pragma mark -页面是否可以提交,会改变按钮是否可以点击
-(void)viewCanSave{
if(_viewStatus==ViewStatusEdit){
//修改
if([[_newModel mj_JSONString] isEqualToString:[_oldModel mj_JSONString]]){
//证明没改过
self.navigationItem.rightBarButtonItem.enabled=false;
}else{
self.navigationItem.rightBarButtonItem.enabled=true;
}
}else{
//新增
//此处略(写上判断是否为空即可,至于输入合法性,可以留到保存按钮触发后提交前处理)
}
}
修改
当表单进入修改状态时,一般都会有这样的需求:当且仅当表单的元素有修改过,才允许提交。而我们每次用户操作后都把值赋值给了_newModel了,那么要怎么来判断是否用户有修改过呢?我们可以再申明一个变量_oldModel,然后当初始化的时候将_newModel copy一份给_oldModel。当然,要copy的话,这个Model需要遵守NSCopying协议,并且实现copyWithZone:方法,具体大家再网上搜索了。建议这里最好做深拷贝。那么viewDidLoad方法可以变成这样:
[super viewDidLoad];
//如果没有正确调用init方法的处理
_newModel = _newModel ? : [ViewModel getInitModel];
if(_viewStatus ==0){
if([NSString isBlankString:_newModel.id]){
_viewStatus = ViewStatusNew;
}else{
_viewStatus = ViewStatusRetrieve;
}
}
//判断是编辑还是新增
if([NSString isBlankString:_newModel.id]){
_viewStatus = ViewStatusNew;
}
//只有在编辑(非新增情况)才需要
if(_viewStatus == ViewStatusEdit){
_oldModel = [_newModel copy];
}
//…..
合法性验证
建议把合法性验证放在请求保存接口之前,而不是放在输入改变的时候。那么我们可以封装一个方法,把所有的合法性验证全都放在这个方法里面,然后请求保存接口之前先调用。
#pragma mark -合法性验证
-(BOOL)isViewValid{
if(_newModel.data>10){
//这里可以用HUD给用户抛出提示
return NO;
}
//其它的合法验证
return YES;
}