iOS复杂UITableViewCell表单实现 - XLForm

XLForm

目的

XLForm是创建动态表格视图最灵活、最强大的iOS库。 本库的目的是像手动创建一样,实现表单功能,但是只用其1/10的时间。

XLForm提供了非常强大的DSL(域特定语言)以创建表单。 它在运行时遵从此规范,即时更新UI。

XLForm的功能

  • 根据表单定义的陈述,加载表单。
  • 在运行时跟踪表单定义更改,相应地更新表单界面。
  • 支持多值分组,允许我们创建,删除或对行排序。有关详细信息,请参考下面的“多值分组”部分。
  • 支持自定义行。
  • 支持自定义选择器。有关如何自定义选择器的更多细节,请查看“自定义选择器”部分。
  • 提供了几个内联选择器,比如日期选择器和选择器内联选择器,并提供创建自定义内联选择器的方法。
  • 基于表单定义的表单数据验证。
  • 能够轻松地在行之间导航,可高度定制。
  • 如果需要,可以显示inputAccessoryView。默认情况下,显示inputAccessoryView。
  • 可为特定行或整个表单提供只读模式。
  • 行可以根据其它行的值以隐藏或显示。可以使用NSPredicates声明性地完成。 (请参阅根据其他行值使行或分组不可见)

怎样创建表单

#import "XLFormViewController.h"

@interface CalendarEventFormViewController: XLFormViewController

@end
@interface ExamplesFormViewController ()

@end

@implementation ExamplesFormViewController

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self){
        [self initializeForm];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self){
        [self initializeForm];
    }
    return self;
}

- (void)initializeForm {
  // Implementation details covered in the next section.
}

@end

** 实现initializeForm方法 **

创建表单,我们需要通过XLFormDescriptor实例声明它,并将它指派给XLFormViewController实例。如我们所说XLForm基于DSL,它在隐藏了复杂的东西的同时,实现了手工表单的功能和灵活性。

我们使用三个类定义表单

  • XLFormDescriptor
  • XLFormSectionDescriptor
  • XLFormRowDescriptor

一个表单的定义就是一个XLFormDescriptor实例,这个实例包含一个或多个分组(XLFormSectionDescriptor实例),每个分组包含多行(XLFormRowDescriptor实例)。您可能已经注意到DSL结构类似于UITableView(表 - >>分组 - >>行)的结构。 生成的表格视图表单的结构(分组和行的顺序)反映了表单定义的结构。

** 看下面initializeForm实现以定义iOS日历事件表单的例子 **

- (void)initializeForm {
  XLFormDescriptor * form;
  XLFormSectionDescriptor * section;
  XLFormRowDescriptor * row;

  form = [XLFormDescriptor formDescriptorWithTitle:@"Add Event"];

  // First section
  section = [XLFormSectionDescriptor formSection];
  [form addFormSection:section];

  // Title
  row = [XLFormRowDescriptor formRowDescriptorWithTag:@"title" rowType:XLFormRowDescriptorTypeText];
  [row.cellConfigAtConfigure setObject:@"Title" forKey:@"textField.placeholder"];
  [section addFormRow:row];

  // Location
  row = [XLFormRowDescriptor formRowDescriptorWithTag:@"location" rowType:XLFormRowDescriptorTypeText];
  [row.cellConfigAtConfigure setObject:@"Location" forKey:@"textField.placeholder"];
  [section addFormRow:row];

  // Second Section
  section = [XLFormSectionDescriptor formSection];
  [form addFormSection:section];

  // All-day
  row = [XLFormRowDescriptor formRowDescriptorWithTag:@"all-day" rowType:XLFormRowDescriptorTypeBooleanSwitch title:@"All-day"];
  [section addFormRow:row];

  // Starts
  row = [XLFormRowDescriptor formRowDescriptorWithTag:@"starts" rowType:XLFormRowDescriptorTypeDateTimeInline title:@"Starts"];
  row.value = [NSDate dateWithTimeIntervalSinceNow:60*60*24];
  [section addFormRow:row];
  
  self.form = form;
}

XLForm将从前面交待的定义加载表视图表单。 最有趣的部分是它会根据表单定义的修改来更新表格视图表单。 这意味着我们能够在运行时对表格视图表单添加或删除分组定义或行定义,并且您将永远不需要再关心NSIndexPath,UITableViewDelegate,UITableViewDataSource或其他复杂性。

要查看更复杂的表单定义,请查看此仓库中“ Examples”文件夹中的示例程序。 如果您愿意,也可以在自己的设备上运行。 XLForm不依赖其它pod,还有,这些示例项目使用一些Cocoapod来显示高级XLForm功能。

** 输入行 **

输入行允许用户输入文本值。通常使用UITextFieldUITextView。输入行类型之间最大的区别是keyboardType, autocorrectionType 和 autocapitalizationType 配置

static NSString *const XLFormRowDescriptorTypeText = @"text";

将由UITextAutocorrectionTypeDefaultUITextAutocapitalizationTypeSentencesUIKeyboardTypeDefaultUITextField表示。

static NSString *const XLFormRowDescriptorTypeName = @"name";

将由UITextAutocorrectionTypeNo, UITextAutocapitalizationTypeWordsUIKeyboardTypeDefault的UITextField表示。

static NSString *const XLFormRowDescriptorTypeURL = @"url";

将由UITextField with UITextAutocorrectionTypeNo, UITextAutocapitalizationTypeNoneUIKeyboardTypeURL的UITextField表示。

static NSString *const XLFormRowDescriptorTypeEmail = @"email";

将由UITextAutocorrectionTypeNo, UITextAutocapitalizationTypeNoneUIKeyboardTypeEmailAddress的UITextField表示。

static NSString *const XLFormRowDescriptorTypePassword = @"password";

将由UITextAutocorrectionTypeNo, UITextAutocapitalizationTypeNoneUIKeyboardTypeASCIICapable的UITextField表示。这个类型的行同时会设置secureTextEntry为YES,以隐藏用户输入的内容。

static NSString *const XLFormRowDescriptorTypeNumber = @"number";

将由UITextAutocorrectionTypeNo, UITextAutocapitalizationTypeNoneUIKeyboardTypeNumbersAndPunctuation的UITextField表示。

static NSString *const XLFormRowDescriptorTypePhone = @"phone";

将由UIKeyboardTypePhonePad的UITextField表示。

static NSString *const XLFormRowDescriptorTypeTwitter = @"twitter";

将由UITextAutocorrectionTypeNo, UITextAutocapitalizationTypeNoneUIKeyboardTypeTwitter的UITextField表示。

static NSString *const XLFormRowDescriptorTypeAccount = @"account";

将由UITextAutocorrectionTypeNo, UITextAutocapitalizationTypeNone and UIKeyboardTypeDefault的UITextField表示。

static NSString *const XLFormRowDescriptorTypeInteger = @"integer";

将由UIKeyboardTypeNumberPad的UITextField表示。

static NSString *const XLFormRowDescriptorTypeDecimal = @"decimal";

将由UIKeyboardTypeDecimalPad的UITextField表示。

static NSString *const XLFormRowDescriptorTypeTextView = @"textView";

将由UITextAutocorrectionTypeDefault, UITextAutocapitalizationTypeSentencesUIKeyboardTypeDefault的UITextField的UITextField表示。

Selector Rows

Selector Rows允许我们从列表中选择一个或多个值。XLForm支持8种直接可用的Selector类型

static NSString *const XLFormRowDescriptorTypeSelectorPush = @"selectorPush";
static NSString *const XLFormRowDescriptorTypeSelectorActionSheet = @"selectorActionSheet";
static NSString *const XLFormRowDescriptorTypeSelectorAlertView = @"selectorAlertView";
static NSString *const XLFormRowDescriptorTypeSelectorLeftRight = @"selectorLeftRight";
static NSString *const XLFormRowDescriptorTypeSelectorPickerView = @"selectorPickerView";
static NSString *const XLFormRowDescriptorTypeSelectorPickerViewInline = @"selectorPickerViewInline";
static NSString *const XLFormRowDescriptorTypeSelectorSegmentedControl = @"selectorSegmentedControl";
static NSString *const XLFormRowDescriptorTypeMultipleSelector = @"multipleSelector";

通常我们会选择一组对象(这些对象应该有一个字符串显示它们以及一个值序列化它们),XLForm必须能够显示这些对象。

XFForm在显示对象时遵循以下规则:

  1. 如果XLFormRowDescriptor对象为nil, XLForm使用行属性noValueDisplayText作为显示文本。
  2. 如果XLFormRowDescriptorvalueTransformer属性值,XLForm 使用NSValueTransformer将选择的对象转换成字符串。
  3. 如果对象是NSString或者NSNumber, XLForm使用其description属性。
  4. 如果对象遵从XLFormOptionObject协议,XLForm从formDisplayText获取显示值。
  5. 否则返回nil,意思是你应该遵从这个协议 :)

您可能有兴趣通过设置noValueDisplayText或valueTransformer属性或使选择器选项对象符合XLFormOptionObject协议来更改显示文本。

下面是这个协议声明:

@protocol XLFormOptionObject <NSObject>

@required
-(NSString *)formDisplayText;
-(id)formValue;

@end

日期和时间行

XLForm支持三种日期:Date, DateTime , Time and Countdown Timer,并且可以使用2中方式展示UIDatePicker,行内或者非行内。

static NSString *const XLFormRowDescriptorTypeDateInline = @"dateInline";
static NSString *const XLFormRowDescriptorTypeDateTimeInline = @"datetimeInline";
```
```
static NSString *const XLFormRowDescriptorTypeTimeInline = @"timeInline";
```
```
static NSString *const XLFormRowDescriptorTypeCountDownTimerInline = @"countDownTimerInline";
```
```
static NSString *const XLFormRowDescriptorTypeDate = @"date";
```
```
static NSString *const XLFormRowDescriptorTypeDateTime = @"datetime";
```
```
static NSString *const XLFormRowDescriptorTypeTime = @"time";
```
```
static NSString *const XLFormRowDescriptorTypeCountDownTimer = @"countDownTimer";
```

这里有怎样定义这些行类型的例子:
```
XLFormDescriptor * form;
XLFormSectionDescriptor * section;
XLFormRowDescriptor * row;

form = [XLFormDescriptor formDescriptorWithTitle:@"Dates"];

section = [XLFormSectionDescriptor formSectionWithTitle:@"Inline Dates"];
[form addFormSection:section];

// Date
row = [XLFormRowDescriptor formRowDescriptorWithTag:kDateInline rowType:XLFormRowDescriptorTypeDateInline title:@"Date"];
row.value = [NSDate new];
[section addFormRow:row];

// Time
row = [XLFormRowDescriptor formRowDescriptorWithTag:kTimeInline rowType:XLFormRowDescriptorTypeTimeInline title:@"Time"];
row.value = [NSDate new];
[section addFormRow:row];

// DateTime
row = [XLFormRowDescriptor formRowDescriptorWithTag:kDateTimeInline rowType:XLFormRowDescriptorTypeDateTimeInline title:@"Date Time"];
row.value = [NSDate new];
[section addFormRow:row];

// CountDownTimer
row = [XLFormRowDescriptor formRowDescriptorWithTag:kCountDownTimerInline rowType:XLFormRowDescriptorTypeCountDownTimerInline title:@"Countdown Timer"];
row.value = [NSDate new];
[section addFormRow:row];
```

## Boolean Rows
XLForm支持两种开关控制
```
static NSString *const XLFormRowDescriptorTypeBooleanCheck = @"booleanCheck";
```
```
static NSString *const XLFormRowDescriptorTypeBooleanSwitch = @"booleanSwitch";
```
我们也能够使用在**Selector Rows **里介绍的任意Seector Row来模拟其他类型的Boolean Rows,。

## 其他行

**Stepper**

XLForms支持UIStepper计数
```
static NSString *const XLFormRowDescriptorTypeStepCounter = @"stepCounter";
```

你能够轻松的设置stepper参数:
```
row = [XLFormRowDescriptor formRowDescriptorWithTag:kStepCounter rowType:XLFormRowDescriptorTypeStepCounter title:@"Step counter"];
    row.value = @50;
    [row.cellConfigAtConfigure setObject:@YES forKey:@"stepControl.wraps"];
    [row.cellConfigAtConfigure setObject:@10 forKey:@"stepControl.stepValue"];
    [row.cellConfigAtConfigure setObject:@10 forKey:@"stepControl.minimumValue"];
    [row.cellConfigAtConfigure setObject:@100 forKey:@"stepControl.maximumValue"];
```

** Slider **
XLForms支持UISlider计数:
```
static NSString *const XLFormRowDescriptorTypeSlider = @"slider";
```
你能够依据个人喜好轻松的调整slider:
```
row = [XLFormRowDescriptor formRowDescriptorWithTag:kSlider rowType:XLFormRowDescriptorTypeSlider title:@"Slider"];
    row.value = @(30);
    [row.cellConfigAtConfigure setObject:@(100) forKey:@"slider.maximumValue"];
    [row.cellConfigAtConfigure setObject:@(10) forKey:@"slider.minimumValue"];
    [row.cellConfigAtConfigure setObject:@(4) forKey:@"steps"];
```
把`steps`设置成`@(0)`以禁用步进功能。

**info**
有时应用需要显示不可编辑的数据。XLForm提供了`XLFormRowDescriptorTypeInfo`行类型以显示不可编辑的信息。比如在应用的设置部分显示应用版本。

**Button**
除了数据输入行,不可编辑的行和选择器,XLForm有一个按钮行XLFormRowDescriptorTypeButton,允许我们在选择时执行任何操作。 它可以使用块(clousure),选择器,segue标识符,segue类或指定要呈现的视图控制器进行配置。 ViewController格式可以通过设置视图控制器类,视图控制器故事板Id或nib名称来完成。 Nib名称必须与视图控制器类名称匹配。

## Multivalued Sections (插入、删除行以及排序)
任意的`XLFormSectionDescriptor`实例都支持插入、删除行以及排序。可以使用其中一种或者混合或者同时使用所有的模式。

多值XLFormSectionDescriptor最有趣的部分是它支持在“行”分组上显示的所有行类型和自定义行。

** 怎样创建多值分组 **
创建多值分组和使用下面`XLFormSectionDescriptor`的initializer一样简单:

```
+(id)formSectionWithTitle:(NSString *)title
           sectionOptions:(XLFormSectionOptions)sectionOptions;
+(id)formSectionWithTitle:(NSString *)title
           sectionOptions:(XLFormSectionOptions)sectionOptions
        sectionInsertMode:(XLFormSectionInsertMode)sectionInsertMode;
```

`sectionOptions`是按位枚举参数,可以被用来选择多值类型。可选的类型有`XLFormSectionOptionCanInsert`, `XLFormSectionOptionCanDelete`, `XLFormSectionOptionCanReorder`。`XLFormSectionOptionNone `是默认值。

`sectionInsertMode`用来选择插入模式长什么样子。XLform有两种可以直接使用的插入模式`XLFormSectionInsertModeLastRow`和`XLFormSectionInsertModeButton`.`XLFormSectionInsertModeLastRow`是默认值。

** 一起来看看怎样创建多值分组 **
```
XLFormDescriptor * form;
XLFormSectionDescriptor * section;
XLFormRowDescriptor * row;

NSArray * nameList = @[@"family", @"male", @"female", @"client"];

form = [XLFormDescriptor formDescriptorWithTitle:@"Multivalued examples"];

// Enable Insertion, Deletion, Reordering
section = [XLFormSectionDescriptor formSectionWithTitle:@"MultiValued TextField"
                                          sectionOptions:XLFormSectionOptionCanReorder | XLFormSectionOptionCanInsert | XLFormSectionOptionCanDelete];
section.multivaluedTag = @"textFieldRow";
[form addFormSection:section];

for (NSString * tag in nameList) {
    // add a row to the section, each row will represent a name of the name list array.
    row = [XLFormRowDescriptor formRowDescriptorWithTag:nil rowType:XLFormRowDescriptorTypeText title:nil];
    [[row cellConfig] setObject:@"Add a new tag" forKey:@"textField.placeholder"];
    row.value = [tag copy];
    [section addFormRow:row];
}
// add an empty row to the section.
row = [XLFormRowDescriptor formRowDescriptorWithTag:nil rowType:XLFormRowDescriptorTypeText title:nil];
[[row cellConfig] setObject:@"Add a new tag" forKey:@"textField.placeholder"];
[section addFormRow:row];
```
## 表单值
** formValues **
你可以调用`-(NSDictionary *)formValues`获取所有的表单值;`XLFormViewController`实例或者`XLFormDescriptor`实例。

返回的字典遵循以下规则:

XLForm为每个属于没有设置multivaluedTag值的XLFormSectionDescriptor的XLFormRowDescriptor添加一个值。 字典键是XLFormRowDescriptor的tag属性的值。

有multivaluedTag值的每个分组,XLForm会添加用NSArray作为值的字典,数组中的每个值都是该分组中包含的每一行的值,multivaluedTag是其键。

例如,如果我们有一个multivaluedTag属性等于tags的分组,包含的行上是以下值:'family','male','female','client',生成的值将是tags:['family ','male','female','client']

** httpParameters**

在同样的情况下,我们需要的表单值可能与XLFormRowDescriptor实例的值不同。 这通常是选择器行的情况,当我们需要将表单值发送到某个终端时,所选择的值可以是Core Data对象或其他任意对象。 在这种情况下,XLForm需要知道如何获取所选对象的值和描述。

当使用 - (NSDictionary *)httpParameters方法时,XLForm遵循以下规则来获取XLFormRowDescriptor值:
1. 如果对象是NSString,NSNumber或NSDate,则该值是对象本身。
2. 如果对象符合协议XLFormOptionObject,则XLForm将从formValue方法获取值。
3. 否则返回nil。

multivaluedTag的工作方式与formValues方法相同。

**怎样创建自定义行**
创建自定义单元格,需要创建继承自`XLFormBaseCell`的`UITableViewCell`。`XLFormBaseCell`遵循`XLFormDescriptorCell`协议。

你可能对实现`XLFormDescriptorCell`协议以改变单元格的行为感兴趣:
```
@protocol XLFormDescriptorCell <NSObject>

@required

@property (nonatomic, weak) XLFormRowDescriptor * rowDescriptor;

// initialise all objects such as Arrays, UIControls etc...
-(void)configure;
// update cell when it about to be presented
-(void)update;

@optional

// height of the cell
+(CGFloat)formDescriptorCellHeightForRowDescriptor:(XLFormRowDescriptor *)rowDescriptor;
// called to check if cell can became first responder
-(BOOL)formDescriptorCellCanBecomeFirstResponder;
// called to ask cell to assign first responder to relevant UIView.
-(BOOL)formDescriptorCellBecomeFirstResponder;
// called when cell is selected
-(void)formDescriptorCellDidSelectedWithFormController:(XLFormViewController *)controller;
// http parameter name used for network request
-(NSString *)formDescriptorHttpParameterName;

// is invoked when cell becomes firstResponder, could be used for change how the cell looks like when it's the forst responder.
-(void)highlight;
// is invoked when cell resign firstResponder
-(void)unhighlight;


@end
```

一旦创建了自定义cell,你需要添加这个行定义到`cellClassesForRowDescriptorTypes`字典中,使`XLForm`知道。
```
[[XLFormViewController cellClassesForRowDescriptorTypes] setObject:[MYCustomCellClass class] forKey:kMyAppCustomCellType]
```

或者加入我们使用xib定义`XLBaseDescriptorCell`:
```
[[XLFormViewController cellClassesForRowDescriptorTypes] setObject:@"nibNameWithoutNibExtension" forKey:kMyAppCustomCellType];
```
这样做,当使用到`kMyAppCustomCellType`行类型时,XLForm创建合适的cell类。

## Custom Selectors - Selector Row with a custom selector view controller

基本的Selector允许用户从pushed的控制器选择一个或多个items,已经能够满足需求,但是有时我们需要更多的灵活性以获取更好的用户体验,或者做一些默认不支持的事情。

假如用户需要选择一个地图坐标或者选择一个从服务器获取的值。我们怎样轻易做到?

```
row = [XLFormRowDescriptor formRowDescriptorWithTag:kSelectorMap rowType:XLFormRowDescriptorTypeSelectorPush title:@"Coordinate"];
// set up the selector controller class
row.action.viewControllerClass = [MapViewController class];
// or
//row.action.viewControllerStoryboardId = @"MapViewControllerStoryboardId";
// or
//row.action.viewControllerNibName = @"MapViewControllerNibName";

// Set up a NSValueTransformer to convert CLLocation to NSString, it's used to show the select value description (text).  
row.valueTransformer = [CLLocationValueTrasformer class];
// Set up the default value
row.value = [[CLLocation alloc] initWithLatitude:-33 longitude:-56];
```
`action.viewControllerClass`控制器类要遵循`XLFormRowDescriptorViewController`协议。

XLForm使用`XLFormRowDescriptor`实例设置`rowDescriptor`属性。

开发人员负责使用rowDescriptor值更新其视图,并将所选值设置给rowDescriptor。

> 注意:viewControllerClass, viewControllerNibName or viewControllerStoryboardId属性是相互独立的,均由XLFormButtonCell 和 XLFormSelectorCell使用. 如果你创建了自定义的cell,然后你负责使用它们。

**另一个例子**
```
row = [XLFormRowDescriptor formRowDescriptorWithTag:kSelectorUser rowType:XLFormRowDescriptorTypeSelectorPush title:@"User"];
row.action.viewControllerClass = [UsersTableViewController class];
```
你可以在实例仓库文件夹下找到这些示例细节: [Examples/Objective-C/Examples/Selectors/CustomSelectors/](https://github.com/xmartlabs/XLForm/tree/master/Examples/Objective-C/Examples/Selectors/CustomSelectors) 和 [Examples/Objective-C/Examples/Selectors/DynamicSelector](https://github.com/xmartlabs/XLForm/tree/master/Examples/Objective-C/Examples/Selectors/DynamicSelector).

## 动态表单 - 怎样在运行时动态修改表单

XLFormDescriptor所做的任何更改都将反映在XLFormViewController tableView中。这意味着当添加或删除分组或行时,XLForm将相应地对该分组或行进行动画处理。

我们不必再处理NSIndexPaths或添加,删除UITableViewCell了。特定TableViewCell的NSIndexPath随着时间的变化而变化,这使得很难跟踪每个UITableViewCell的NSIndexPath。

每个XLForm XLFormRowDescriptor行都有一个在其构造函数中设置的tag属性。 XLFormDescriptor还有一个特定的帮助器可以从tag中获取XLFormRowDescriptor。使用tags管理XLFormRowDescriptors要容易得多,tag应该是唯一的,并且在表视图添加修改或删除过程中不会更改。

重点记住,所有的UITableView表单修改必须使用描述符进行,而不是直接在UITableView上进行修改。

通常某些值更改、添加、删除某些行或分组时,你可能需要更改表单。为此,您可以设置行或分组的disabled和hidden属性。有关详细信息,请参阅"根据其它行值设置分组或行不可见"。

为了与表单描述符保持同步,XLFormViewController子类应该覆盖“XLFormViewController”的XLFormDescriptorDelegate方法。

> 注意:覆盖此代理方法时,始终调用[super ...]方法是非常重要的。

```
@protocol XLFormDescriptorDelegate <NSObject>

@required

-(void)formSectionHasBeenRemoved:(XLFormSectionDescriptor *)formSection atIndex:(NSUInteger)index;
-(void)formSectionHasBeenAdded:(XLFormSectionDescriptor *)formSection atIndex:(NSUInteger)index;
-(void)formRowHasBeenAdded:(XLFormRowDescriptor *)formRow atIndexPath:(NSIndexPath *)indexPath;
-(void)formRowHasBeenRemoved:(XLFormRowDescriptor *)formRow atIndexPath:(NSIndexPath *)indexPath;
-(void)formRowDescriptorValueHasChanged:(XLFormRowDescriptor *)formRow oldValue:(id)oldValue newValue:(id)newValue;
-(void)formRowDescriptorPredicateHasChanged:(XLFormRowDescriptor *)formRow
                                   oldValue:(id)oldValue
                                   newValue:(id)newValue
                              predicateType:(XLPredicateType)predicateType;

@end
```

比如我们想根据另一行的值显示或者隐藏当前行时:

```
-(void)formRowDescriptorValueHasChanged:(XLFormRowDescriptor *)rowDescriptor oldValue:(id)oldValue newValue:(id)newValue
{
    // super implmentation MUST be called
    [super formRowDescriptorValueHasChanged:rowDescriptor oldValue:oldValue newValue:newValue];
    if ([rowDescriptor.tag isEqualToString:@"alert"]){
        if ([[rowDescriptor.value valueData] isEqualToNumber:@(0)] == NO && [[oldValue valueData] isEqualToNumber:@(0)]){
            XLFormRowDescriptor * newRow = [rowDescriptor copy];
            [newRow setTag:@"secondAlert"];
            newRow.title = @"Second Alert";
            [self.form addFormRow:newRow afterRow:rowDescriptor];
        }
        else if ([[oldValue valueData] isEqualToNumber:@(0)] == NO && [[newValue valueData] isEqualToNumber:@(0)]){
            [self.form removeFormRowWithTag:@"secondAlert"];
        }
    }
```

## 根据其它行值设置分组或行不可见

** 概述 **
XLForm允许您定义行之间的依赖关系,以便如果一行的值更改,另一行的行为也跟着自动更改。 例如,您可能有一个表单向用户询问他/她是否拥有宠物。 如果答案是“yes”,你可能想问问他们的名字。 因此,您可以根据其他行的值,决定此行是否可见。 

当然,您也可以通过观察某些行的值并相应地删除和添加行来手动执行此操作,但是很多工作XLForm已经完成了。

** 工作原理 **
为了使行和分组自动出现和消失,每个descriptor中都有一个属性:

```
@property id hidden;
```

该id对象通常是NSPredicate或包含BOOL的NSNumber。它可以使用其中任何一个或NSString设置,最终从中创建NSPredicate。为了使其正常工作,字符串必须在语法上正确。

例如,当first行包含值“hide”时,您可以将下面的字符串设置给second行使其消失。

```
second.hidden = [NSString stringWithFormat:@“$%@ contains [c]'hide'”,first];
```

这将在'$'之后插入first的tag,当然也可以手工执行。当判断predicate时,每个tag变量都将被相应的 row descriptor替代。

当参数是NSString时,'.value'将附加到每个标签中,除非标签后跟'.isHidden'或'.isDisabled'。这意味着行(或分组)可能取决于另一行的值或hidden或disabled属性。当属性直接设置为NSPredicate时,它的formatString不会被更改(因此如果要引用其值,则必须在每个变量之后附加一个'.value')。设置NSString是最简单的方法,但是一些复杂的predicates可能不起作用,因此您应该直接设置NSPredicate。

您还可以使用bool对象设置此属性,这意味着除非手动设置,否则属性的值不会更改。

要获取评估的布尔值,应该调用isHidden方法。它不会在每次调用时重新评估predicate,只有在它依赖的行的值(或hidden/disabled状态)更改时。当这种情况发生并且返回值发生变化时,它会自动表单上反映其更改,因此不能调用其他方法。

## 禁用行(设置为只读模式)
可以禁用行,使用户无法修改它们。 默认情况下,禁用的行是灰色的。 要禁用行,唯一要做的是设置其diabled属性:
```
@property id disabled;
```
此属性是包含BOOL的NSNumber、NSString或NSPredicate。 布尔将静态禁用(或启用该行)。 另外两个工作就像上面介绍的hidden属性一样。 这意味着可以根据其他行的值禁用和启用当前航。 当设置了NSString时,将生成一个NSPredicate,将该字符串作为格式化字符串,以便和目标保持一致。

与hidden属性的区别在于检查行的disabled状态不会自动影响表单上的值。 因此,应该调用XLFormViewController的updateFormRow方法。

## 验证

我们可以使用XLForm的验证支持验证表单数据。

每一个`XLFormRowDescriptor`实例都包含了一组验证器(validators)。可以使用以下方法添加、移除验证器并验证指定行:

```
-(void)addValidator:(id<XLFormValidatorProtocol>)validator;
-(void)removeValidator:(id<XLFormValidatorProtocol>)validator;
-(XLFormValidationStatus *)doValidation;
```
只要定义一个对象遵循`XLFormValidatorProtocol`就能够自定义验证器:
```
@protocol XLFormValidatorProtocol <NSObject>

@required

-(XLFormValidationStatus *)isValid:(XLFormRowDescriptor *)row;

@end
```
[XLFormRegexValidator](https://github.com/xmartlabs/XLForm/blob/master/XLForm/XL/Validation/XLFormRegexValidator.h)是我们能够自定义验证器的示例。

一个常见的验证是值是否为空,为nil。

XLFom公开所需的XLFormRowDescriptor属性以指定所需的行。

要获取所有行验证错误,我们可以调用以下XLFormViewController方法:

```
-(NSArray *)formValidationErrors;
```

## 额外的行配置

XLFormRowDescriptor允许我们配置UITableViewCell的通用部分,例如:rowType,label,value(默认值),如果单元格`required`, `hidden` 或者`disabled`,等等。

您可能需要设置`UITableViewCell`的另一个属性。要设置另一个属性XLForm使用键值编码,允许开发人员通过keyPath设置单元格属性。

您只需要将属性添加到`XLFormRowDescriptor`的`cellConfig`或`cellConfigAtConfigure`的字典属性中即可。 `cellConfig`和`cellConfigAtConfigure`之间的主要区别是属性设置的时机。每次单元格即将显示时,都会设置`cellConfig`属性。另一方面,`cellConfigAtConfigure`属性只在调用单元的init方法后设置一次。

自3.3.0版起,您还可以使用`cellConfigForSelector`来配置`XLFormOptionsViewController`的单元格如何显示为选择器行时的样式。

例如,如果要设置占位符,可以执行以下操作:
```
row = [XLFormRowDescriptor formRowDescriptorWithTag:@“title”rowType:XLFormRowDescriptorTypeText];
[row.cellConfigAtConfigure setObject:@“Title”forKey:@“textField.placeholder”];
[section addFormRow:row];
```
让我们看看如何改变单元格标签的颜色:
```
row = [XLFormRowDescriptor formRowDescriptorWithTag:@"title" rowType:XLFormRowDescriptorTypeText];
[row.cellConfig setObject:[UIColor redColor] forKey:@"textLabel.textColor"];
[section addFormRow:row];
```
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,137评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,824评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,465评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,131评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,140评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,895评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,535评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,435评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,952评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,081评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,210评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,896评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,552评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,089评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,198评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,531评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,209评论 2 357

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,675评论 18 139
  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,754评论 1 92
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,833评论 6 342
  • 凌寒静夜绽无声,顾盼自怜香气盈。 不向朱门争宠幸,长留清白慰生平。
    widoms阅读 143评论 0 0
  • 作业1:口红 解决问题:她,8小时持久显色,不沾杯。 防备问题:她,让你精神满满,不再面黄肌瘦。 不完全满足:她,...
    加菲小懒阅读 366评论 1 1