FXForm iOS智能表单样式制定
前言:
对于表单制作,通常我们会使用到iOS中的控件UITableView来进行制定,最终将根据用户写入的数据进行提交会展示。如果工程中不同的业务都需要用表单,那么我们可以用统一的制定样式将表单组件话,这样业务方遵守一定的规则就可以简单的制作出一份表单。 FXForm对于像设置页面或者数据输入页面是一个很好的选择,通过配置model的字段信息,就可以很快地额建立一个表单。下面就简单介绍下它的使用方法,及一些注意事项。
步骤一:创建一个form 如下:
@interface MyForm : NSObject
@property (nonatomic, copy) NSString *email;
@property (nonatomic, copy) NSString *password;
@property (nonatomic, assign) BOOL rememberMe;
@end
FXForm会根据字段类型“NSString”、“BOOL”、字段名称,“email”、“password"自动生成所需类型cell。上面的配置字段依次生成:带有UITextField类型的cell,且输入键盘为email类型,带有UITextField类型的cell,且textfield的secureTextEntry属性自动设置为yes,带有UISwitch控件的cell.对cell上的控件进行操作,会自动将改变过后的值赋值给model的字段(当向UITextField中输入文本,email、password会自动更新)
步骤二:显示一个基本表单(方式一)
展示一个表单有两种方法可以选择,一种是:FXForms提供了一个FXFormViewController,它是UIViewcontroller的子类,你可以通过FXFormViewController建立一个表单:
FXFormViewController *controller = [[FXFormViewController alloc] init]; controller.formController.form = [[MyForm alloc] init];
FXFormViewController自动包含一个UITableView。你也可以自己创建一个tableVIew,负责给它的tableView属性。大多数情况下,你需要让自己的UIViewController继承FXFormViewController,这样你就可以添加表单的创建逻辑和操作。由于FXForm管理的控制器都是以push的形式来实现转场的,所以应当将FXFormViewController(或者它的子类)放置在UIViewController中,但这并不是强制性的,这种情况是为了处理:当一个form包含subForm时,subForm会被压入导航栈,如果没有将form放置在导航中,那么subform就不会被显示。
像UITableViewController一样,FXFormViewController会将tableView设置为控制器的主视图(main view),但这不是强制的,你也可以将tableView设置成main View的subView。 和UITableViewController一样,FXFormViewController实现了UITableViewDelegate协议,如果你的视图控制器继承了FXFormViewController,你可以改写UITableViewDelegate,和UIScrollViewDelegate方法来实现自定义行为.FXFormViewController并不是tableView的直接代理,它是formController的代理,formController是FXFormController的实例。formController才是tableView的代理和数实现,然后将tableView的代理以协议的方式回调给FXFormViewController。
注意:和UITableViewController区别的是,UITableViewDataSource完全由FxFormController来实现,不建议你来改写或者截断tableView的dataSource方法是实现
步骤三:显示一个表单 (方式二)
FXFormViewController是很灵活,但是有时强制使用一个特定基类的UIViewController时,可能不是那么方便。比如:你可能希望用一个通用的UIViewController作为整个工程的视图控制器的基类,或者在一个view中显示表单。 对于前者,你可以将FXFomrViewController作为childViewController,对于后面那种情况却不适用。用FXForm但是不关联FXFormViewController,你可以直接适用FXFormController.用FXFormController来展示表单,你只需设置它的form和tableView属性就行了,余下的formController会自动处理。你也可以选择性的绑定FXFormController的delegate到当前控制器,来处理tableview的一些事件。当用自定义的表单视图控制器,一些交互性的处理,仍然适用(比如键盘的遮挡问题),但是,你需要添加视图的一些其他逻辑,比如在UIViewController的的viewWillAppear方法中,reload tableView
下面是实例代码:
@interface MyFormViewController : UIViewController
@property (nonatomic, strong) IBOutlet UITableViewtableView; @property (nonatomic, strong) FXFormControllerformController;
@end
@implementation MyFormViewController
(void)viewDidLoad { [super viewDidLoad];
//we'll assume that tableView has already been set via a nib or the -loadView method self.formController = [[FXFormController alloc] init]; self.formController.tableView = self.tableView; self.formController.delegate = self; self.formController.form = [[MyForm alloc] init]; }
(void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated];
//reload the table [self.tableView reloadData]; }
@end
*步骤四:调整表单展示 FXFomrs的最大的优点是,可以智能的猜测处理减少不必要的工作。但是有时候它不能猜测所有的工作,甚至有时候它会猜错,那我们怎么纠正它呢? 情景一:你可能不需要让所有的properies作为form field显示。你可能需要一个私有property,在form model的内部使用,或者你仅仅是为了调整form field的展示顺序。可以通过改写下列方法来实现:
(NSArray *)fields { return @[@"field1", @"field2", @"field3"]; }
返回的数组坑内包含:字符串、字典。该方法会在每次给formController的form赋值时会被调用。这就意味着你可以动态的配置表单,比如,你可以根据form的其他字段展示和隐藏一些field form。 为了调整form filed的一些展示,你可以通过两种方式进行调整:一种是在form 中添加如下方法:-(NSDictionary *)[name]Field,这里的name就是字段名。例如,如果想配置email的title:
-(NSDictionary *)emalField{ return @{FXFormFieldTitle: @"Email Address"};
} 或者你可以直接通过-fileds方法直接进行配置:
(NSArray *)fields { return @[ @{FXFormFieldKey: @"email", FXFormFieldTitle: @"Email Address"}, ...other fields... ]; }
以上两种方法本质上是一样的
情景二:如果你想配置额外的form filed(比如button、label)但是不关联form的property,你可以在-fileds 方法中进行配置。如果你想在表单末端配置一个button或者label,那么可以使用-extralFileds方法,两种方法作用一样,但是后一种“ leaves in place the default fields inferred from the form class:”
(NSArray *)extraFields { return @[ @{FXFormFieldTitle: @"Extra Field"}, ]; }
情景三:如果你不需要展示-fields中返回的form field字段,想要排除某些form field字段,那么可以使用- (NSArrayy *)excludedFields方法,将不需要展示的字段放进数组中。
(NSArray *)excludedFields { return @[ @"someProperty", @"someOtherProperty", ]; }
情景四:表单分组
对于要分组的表单,你只需要给分组的字段配置“FXFormFieldHeader”或者“FXFormFieldFooter”即可,FXFormFiedlHeader/Footer,可以是一个string,或者是一个UIView对象。
(NSArray *)fields { return @[ @{FXFormFieldKey: @"field1", FXFormFieldHeader: @"Section 1"}, @"field2", @{FXFormFieldKey: @"field3", FXFormFieldHeader: @"Section 2"}, @"field4", ]; }
总结:通过以上四个步骤就可以实现表单定制了。但这还远远不能满足我们的业务需求,比如对于没有提供的表单项样式,我们就需要自定义cell,然后注册自定义cell。那么下面我们通过简单介绍FXFomrField的properties和自定义cell的注册和使用。
static NSString *const FXFormFieldKey = @"key";
注释:form对象字段的名称,如果form对应的字段并没不是一个真正的属性(setter和getter方法的声明),这个key就是getter方法的名字,有些能够看见的field可能不会有key,比如在-fields方法中添加一个button。
static NSString *const FXFormFieldType = @"type";
注释:字段的类型,决定该字段在表单中会如何显示,一个cell,可能会对一个多种type,比如自带UITextField的cell,可能会根据不同的type来显示不同的键盘。type会根据property的声明自动匹配,当然我们也可以改写,比如NSString *email,会匹配成type为email键盘类型,我们也可以直接更改type 为label,那么cell就会显示为label类型的cell而不是自带UITextField类型的cell,线已列举的type类型可以在FXForm文件中查看,当然你也可以自定义任何一个字符串作为一个type,可以绑定自定义的cell。
static NSString *const FXFormFieldClass = @"class";
注释:字段类型,对于原始数据,它会被用来封装成OC对象(比如:数字类型的属性会转换为NSNumber类型,struct类型会转换为NSValue类型),这个转换都是自动进行的,所以很少自己进行设置,在-fields和-extraFields方法中,有时需要用它来精准指定。比如:你添加一个ViewController或者一个subform字段,有时class不能够自动指定,这是就可以通过配置class(class类或者类名)。
static NSString *const FXFormFieldCell = @"cell";
注释:用来展示field的cell,默认情况下该字段不会字段层面指定。FXFormController维护field type和cell class的映射,允许你在form层面改写默认cell和type的映射关系,而不仅仅是字段层面。如果你确实是需要指定一个一次性cell type,你可以进行改写处理。你可以给该cell提供一个class 类型对象或者代表class的name字符串
static NSString *const FXFormFieldTitle = @"title";
注释:这是字段显示的标题,FXForms会自动将驼峰样式转换为标题样式,你可以通过strings file来设置配置tile
static NSString *const FXFormFieldPlaceholder = @"placeholder";
注释:当字段的value值为空时显示的占位值,通常情况下该值为NSString类型,也可以为NSData(data 类型字段),UIImage(image 类型字段)。当用作单选或者多选是,placeHoder会以选项的第一个值作为占位值。
static NSString *const FXFormFieldDefaultValue = @"default";
注释:默认值。当字段的value为nil时,可以配置默认值。通过设置该字段,可以防止用户对value进行置空设置。默认值和占位置的区别在于,默认值是被form存储管理的,一旦设置了默认值,占位值就不会被显示。默认值只能作用于OC类型,尽管“0"是个很有意义的值(对于integer 或者float类型的字段来说),它不会被defultValue所取代。(这里的意思应该是说,OC中对于“O”的转换时无法区分该对象为nil还是该对象经转换为“0”这两种情况);
static NSString *const FXFormFieldOptions = @"options";
注释:对于任何的字段,你可以通过设置属性为NSArray类型,配置options来实现表单的选项处理,这些选项可以是NSString、NSValue等其他OC类型,你也可以提供一个FXFormFieldValueTransformer来控制实际的option value的值和需要显示在列表中的值的映射关系。相对的,你也可以通过实现- (NSString *)fieldDescription来控制自定义对象在列表中的显示。
static NSString *const FXFormFieldTemplate = @"template";
注释:该字段的了性为NSArray 或者 NSOrderSet,允许用户编辑、删除、移动编表单项、默认情况下集合类型中的表单项都是“FXFormFieldTypeText”类型的,当然你也可以改写成其他类型,template中的字段信息的配置和单个字段配置几乎一样。(实际demo中,FXFormController对表单数据源的控制是有问题的,经过移动、删除后,会经常崩溃)
static NSString *const FXFormFieldValueTransformer = @"valueTransformer";
注释:大多数情况下Form本身存储的value值和要显示给用户读取的值可能不一样,我们可以通过valueTransformer进行转换。可以通过提供block或者NSValueTransformer子类对象来处理映射,如果设置了该字段,那么-fieldDescription 方法就不会被调用了。通过设置NSValueTransformer来处理映射,该映射是可以逆向的,也就是说,可以在用户操作输入后经过转换,存储到field value中。
static NSString *const FXFormFieldAction = @"action";
注释:该字段配置是可选的。可以为方法的名字或者方法或者一个block,。当触发控件事件时就会被执行,如果action被制定为aciton,那么target可以通过响应链从cell追溯到appdelegate甚至是window中,直到找到响应。如果你的form是作为subForm,那么你也可以在parent form中对应的viewController中实现该方法。对于不可交互的字段,只有cell被选中时才会执行方法。该方法最多只能传递一个参数(通常是field cell,通过cell,你可以活着field model 和form)
static NSString *const FXFormFieldInline = @"inline";
注释:是否内联。如果字段为其他form,或者提供一个options array.通常会展示在一个viewController中,当点击cell时该viewController会被push到导航栈中。如果设置为"yes",那么这些信息都会展示在当前tableView中。
static NSString *const FXFormFieldViewController = @"controller";
注释:一些字段展示时可能是另一个view controller,当点击cell时就会push到导航栈只中,该字段制定要被push到导航栈中的viewController.
static NSString *const FXFormFieldHeader = @"header";
static NSString *const FXFormFieldFooter = @"footer";
注释:提供cell的header和footer,设置了该字段会影响dataSoure,该字段可以为字符串或者自定义view的实例
static NSString *const FXFormFieldSortable = @"sortable";
注释:字段类型为NSArray或者NSOrderSet时,设置是否可以进行排序(实际demo中进行会有bug)
字段type类型:
static NSString *const FXFormFieldTypeDefault = @"default";
默认类型,如果不指定type的话
static NSString *const FXFormFieldTypeLabel = @"label";
label类型的,自带label控件且不可交互
static NSString *const FXFormFieldTypeText = @"text";
自带UITextfield控件
static NSString *const FXFormFieldTypeLongText = @"longtext";
自带UITextView控件
static NSStringconst FXFormFieldTypeURL = @"url"; static NSStringconst FXFormFieldTypeEmail = @"email"; static NSStringconst FXFormFieldTypePassword = @"password"; static NSStringconst FXFormFieldTypeNumber = @"number"; static NSStringconst FXFormFieldTypeInteger = @"integer"; static NSStringconst FXFormFieldTypeUnsigned = @"unsigned"; static NSString *const FXFormFieldTypeFloat = @"float";
自带UITextField控件,调起键盘配置
static NSString *const FXFormFieldTypeBoolean = @"boolean";
自带UISwitch控件
static NSString *const FXFormFieldTypeOption = @"option";
右侧自带选中"check"图片
static NSStringconst FXFormFieldTypeDate = @"date"; static NSStringconst FXFormFieldTypeTime = @"time"; static NSString *const FXFormFieldTypeDateTime = @"datetime";
自带调起ImagePicker控件
static NSString *const FXFormFieldTypeImage = @"image"
自带UIImage控件
配置cell:
可以通过keyPath来进行设置,例如:
(NSDictionary *)emailField { return @{@"textLabel.color": [UIColor redColor]}; }
(NSDictionary *)nameField { return @{@"textField.autocapitalizationType": @(UITextAutocapitalizationTypeNone)}; }
自定义cell
FXForms已经提供了几种类型的cell,如果你需要添加自定义的cell来满足业务需求。或者替换掉所有的原始cell,你可以注入自定义的cell。
有两种程度的自定义:一种是直接继承FXFormCell,可以通过改写setup,update,didSelectWithTableView:controller等方法来实现自定义。setup方法会在cell创建的时候调用一次,update方法会在field value更新的时候调用。 另一种是继承自定义baseCell,那么需要采用FXFormFieldCell协议。自定义cell必须包含一个FXFormField(封装了filed的配置信息,也提供了获取和设置form value的方法,即设置field.value。)不能直接实例FXFormField,可以通过FXFormController来处理FXFormField。
可以通过以下方法自定义cell. * 可以通过FXFormFieldCell ,配置一个自定义的字段cell * 如果要关联一个type类型,可以通过formController的-registerCellClass:forFieldType:来配置 * 如果要配置指定类字段的cell,即字段是一个自定义的model类型,那么可以通过:-registerCellClass:forFieldClass:进行配置 * 如果要替换整个基类cell,那么可通过registerDefaultFieldCellClass: ,然后通过-registerCellClass:forFieldType: 进行配置。