TLFormView 是 又一个 真正通用的表单视图。这意味着相同的组件可以使用机制通过 Auto Layout 可视格式 来调整布局以适应运行中的设备。
因为它不扩展 UITableView
,所以您可以完全自由地创建任何作为表单字段的内容,只要它扩展了基本 TLFormField
类。它还有一些很酷的功能,比如:使用 NSPredicate
进行条件可见性,每个字段都带有 UIPopoverControler
位置的辅助,以及即时编辑/只读模式切换等功能。
有两个基本组件:TLFormView
和 TLFormField
。TLFormView
继承自 UIScrollView 并添加了另一个数据源和代理以创建和处理表单事件。要使表单实用,您需要实现 TLFormViewDataSource
协议的三个方法,这些方法是
- (NSArray *)fieldNamesToShowInFormView:(TLFormView *)form;
- (TLFormField *)formView:(TLFormView *)form fieldForName:(NSString *)fieldName;
- (NSArray *)constraintsFormatForFieldsInForm:(TLFormView *)form;
fieldNamesToShowInFormView:
返回一个包含用于识别表单字段(或 id)的字符串数组的数组。 formView:fieldForName:
为表单中的每个字段名创建一个字段。字段是任何扩展 TLFormField
(即扩展 UIView
的子类),有一组默认字段可以用于适合 80% 的用例。这些是
TLFormFieldImage
:用于显示从 url 或原始图像获取的图像TLFormFieldList
:用于显示事物列表TLFormFieldMultiLine
:用于显示长文本TLFormFieldSingleLine
:用于显示短文本、数字和布尔值TLFormFieldTitle
:用于显示格式为标题的短文本该项目在 TLFormField
上有一个类别,带有用于自定义字段外观的基本方法。它有一些方法来配置标题,并提供对内部组件的 UIAppearance
代理的访问。
一旦您为表单中的每个字段提供一个)TLFormField
,就需要定义如何布局这些字段。要做到这一点,您需要实现数据源的第三个方法 constraintsFormatForFieldsInForm:
,并返回一个包含放置字段在屏幕上的规则的字符串数组的数组。在编写规则时,您需要引用在 fieldNamesToShowInFormView:
实现中返回的字段名称。在这里,您有机会检查设备的功能,并根据需要更改规则来调整布局。
以下是 TLFormViewDataSource
中三个必要方法的示例实现:
- (NSArray *)fieldNamesToShowInFormView:(TLFormView *)form {
return @[
@"user_name",
@"avatar",
@"age",
];
}
- (TLFormField *)formView:(TLFormView *)form fieldForName:(NSString *)fieldName {
Class fieldClass;
NSString *title;
id value;
if ([fieldName isEqualToString:@"user_name"]) {
fieldClass = [TLFormFieldSingleLine class];
title = @"User Name";
value = userModel.name;
}
else if ([fieldName isEqualToString:@"avatar"]) {
fieldClass = [TLFormFieldImage class];
title = @"Avatar";
value = userModel.avatarUrl;
}
else {
fieldClass = [TLFormFieldSingleLine class];
title = @"Age";
value = userModel.age;
}
return [fieldClass formFieldWithName:fieldName title:title andDefaultValue:value];
}
- (NSArray *)constraintsFormatForFieldsInForm:(TLFormView *)form {
//For iPhone we want a vertical layout like we get on a UITableView
if (isIPhone) {
return @[
//Place the avatar on the top
@"V:|-[avatar(==230)]-",
@"H:|-[avatar(==420)]-|",
//Now place all the fields to the bottom
@"V:[avatar]-[user_name(>=44)]-",
@"H:|-[user_name]-|",
@"V:[age(==user_name)]-|",
@"H:|-[age]-|"
];
//For anything else we will place the image on the top left and the rest of the fields to the right
} else {
return @[
//Place the avatar on the top left
@"V:|-[avatar(==230)]",
@"H:|-[avatar]",
//Now place all the fields to the right
@"V:|-[user_name(>=44)]",
@"H:|-[avatar]-[user_name]-|",
@"V:[user_name]-[age(==user_name)]-|",
@"H:|-[avatar(==420)]-[age]-|"
];
}
}
以下是iPhone上的示例:
以下是iPad上的示例:
有些情况下,您需要仅当其他字段具有特定值时才显示特定字段。为此,TLFormField
具有一个属性 visibilityPredicate
,您可以在创建字段时将其设置为 NSPredicate
。该谓词可以通过NSPredicate变量替换 访问表单中所有字段的值。这意味着您可以使用语法 $[字段名称].value
访问表单中的任何字段值。
假设我们要在“年龄”大于12岁的情况下显示“用户名”字段。我们可以编写一个谓词,当“年龄”字段大于12时返回true,并将其作为“user_name”字段的 visibilityPredicate
。以下是对应的代码:
- (TLFormField *)formView:(TLFormView *)form fieldForName:(NSString *)fieldName {
...
//Asume that 'field' is an instance of TLFormField created before somehow
if ([fieldName isEqualToString:@"user_name"])
field.visibilityPredicate = [NSPredicate predicateWithFormat:@"$age.value > 12"];
...
}
假设我们想要简明扼要地解释上一个示例中的奇怪行为。为此,我们可以使用TLFormField
提供的原地支持。我们只需要在字段的helpText
属性中设置帮助文本字符串。当存在该属性的值时,字段将在字段标题旁边显示一个问号图标,当用户点击该图标时,会在弹出窗口中显示帮助文本。以下是对应的代码:
- (TLFormField *)formView:(TLFormView *)form fieldForName:(NSString *)fieldName {
...
if ([fieldName isEqualToString:@"age"])
field.helpText = @"This is the age of the user. Users with less than 12 years are not allowed to enter his name.";
...
}
单击图标后的表现如下:
TLFormModel
为了帮助您设置表单,存在一个名为 TLFormModel
的类,它可以自动推断出从自己的分类中实现的TLFormViewDataSource
。您只需要扩展它并添加一个属性,对于您想在表单中显示的每个字段,该属性的类型必须在文件 TLFormModel.h 中声明的类型之一。每个类型对应于提供的标准TLFormField
之一。
要读取表单的值,就像访问任何对象一样访问属性。要写入值,您需要将值包装在为每个类型提供的值构造函数中。一旦模型中的值更新,您需要手动执行一个重新加载,使用TLFormView
的 reloadValues
方法来显示新的值(这是一个便宜的更新,不涉及视图销毁)。
以下是TLFormModel
支持的类型声明:
TLFormSeparator
:扩展NSObject。在表单中表现为分隔符,允许将字段分组到部分。没有值或标题。TLFormText
:扩展NSString。TLFormLongText
:扩展NSString。TLFormTitle
:扩展NSString。TLFormNumber
:扩展NSNumber。TLFormBoolean
:扩展NSNumber。TLFormList
:扩展NSArray。TLFormEnumerated
:扩展NSDictionary。TLFormImage
:扩展NSObject。这些类除了从其超类继承的逻辑外没有其他任何逻辑,它们就像一个覆盖在属性上的注释。构建此类类型的值的方式是以在 TLFormModel.h 中声明的普通 C 函数,这些函数检查参数的类型并复制给定的参数值。
使用其属性的类型和值,TLFormModel
可以推断出应该为每个属性使用哪种表单字段。对于字段标题,使用属性名称。如果属性名称使用蛇形命名约定,则每个下划线(_)转换为空格,所有单词均首字母大写,因此属性 "user_name" 将具有标题 "User Name"。字段的顺序是声明属性的顺序。例如
@interface UserModel : TLFormModel
@property (nonatomic, strong) TLFormImage *avatar;
@property (nonatomic, strong) TLFormText *user_name;
@end
将头像字段置于顶部,用户名在其下方。
我们的先前示例可以用TLFormModel
这样编写
@interface UserModel : TLFormModel
@property (nonatomic, strong) TLFormImage *avatar;
@property (nonatomic, strong) TLFormText *user_name;
@property (nonatomic, strong) TLFormNumber *age;
@end
@implementation UserModel @end
就是这样。这将生成类似于我们在所有平台上在iPhone上获得的垂直布局。现在要连接TLFormModel
到表单,我们需要使用我们使用的TLFormView
上的setFormModel:
方法。
...
//At some point in some place...
FormUserModel *formUserModel = [FormUserModel new];
//This copy the values of our user model to the form model constructing the correct types using plain C functions
formUserModel.avatar = TLFormImage(userModel.avatar);
formUserModel.user_name = TLFormText(userModel.userName);
formUserModel.age = TLFormNumber(userModel.age);
TLFormView *form = ...
[form setFormModel:formUserModel];
要为其他设备家族调整布局,我们需要重写由TLFormModel
提供的constraintsFormatForFieldsInForm:
的实现,如下所示
@implementation UserModel
- (NSArray *)constraintsFormatForFieldsInForm:(TLFormView *)form {
//For iPhone we want the default implementation provided so just return the 'super' version
if (isIPhone)
return [super constraintsFormatForFieldsInForm:form];
//For anything else use our custom layout
else {
return @[
//Place the avatar on the top left
@"V:|-[avatar(==230)]",
@"H:|-[avatar]",
//Now place all the fields to the right
@"V:|-[user_name(>=44)]",
@"H:|-[avatar]-[user_name]-|",
@"V:[user_name]-[age(==user_name)]-|",
@"H:|-[avatar(==420)]-[age]-|"
];
}
}
@end
现在假设我们想编辑此用户信息并获取结果。我们需要切换表单的编辑状态并从我们使用的TLFormModel
实例中读取更新的值。我们可以这样做
- (IBAction)toggleEditionAction:(id)sender {
//Set the form on edit mode
self.form.editing = !self.form.editing;
//update the fields to reflect it
[self.form setupFields];
}
- (IBAction)saveUserAction:(id)sender {
//Read the values entered by the user and save it to disc.
NSDictionary values = @{
@"avatar": formUserModel.avatar,
@"user_name": formUserModel.name,
@"age": formUserModel.age
}
[values writeToURL:[self saveUrl] atomically:YES];
}
关于"编辑"图片。默认的TLFormFieldImage
在此点不提供选择图片的方法。您需要处理字段选择并显示某种类型的图片选择器。
TLFormView
通过TLFormViewDelegate
初出茅庐时报告两个事件:didSelectField:
和 didChangeValueForField:
。事件由通知表单的TLFormField
实现触发,然后表单传播事件。根据字段的实现方式,它可能会有更多的报告事件。例如,TLFormFieldList
使用UITableView
来呈现值列表,因此它有自己的委托,可以自定义行是否可以重新排列或选择单行。
iOS >= 8.0
或克隆存储库,并在Pod/Classes
下查看代码。
有许多需要改进的地方,以下是一些
TLFormField+UIAppearance.h
中的方法集中样式如果您想为该项目做出贡献,请考虑选择以下项之一。
BrunoBerisso, [email protected]
TLFormView
可在MIT许可下获得。有关更多信息,请参阅LICENSE文件。