XLForm 4.3.0

XLForm 4.3.0

测试已测试
语言语言 Obj-CObjective C
许可证 MIT
发布时间最新发布2021年3月

Martin BarretoMathias Claassen维护。



XLForm 4.3.0

  • 作者:
  • Martin Barreto

XLForm

XMARTLABS 提供。

Build Status CocoaPods compatible

如果您正在使用 Swift 进行开发,那么您应该看看 Eureka,它是 XLForm 在 Swift 中的完整重设计版本,并且添加了更多功能。

我们不再为 XLForm 实现新功能。然而,如果出现关键问题,我们将修复它。

目的

XLForm 是用于创建动态表格视图表单的_most Flexible and powerful iOS_库。该库的目标是实现手动制作表单的同等功能,但只需花费十分之一的时间。

XLForm 提供了一个非常强大的 DSL(领域特定语言),用于创建表单。它在运行时跟踪此规范,并在实时更新 UI。

让我们看看使用 XLForm 创建的 iOS 日历事件表单

Screenshot of native Calendar Event Example

XLForm的作用

  • 根据声明式表单定义加载表单。
  • 监视运行时定义的变化,相应地更新表单界面。关于动态表单的更多信息请参阅本readme的动态表单部分。
  • 支持多值部分,允许我们创建、删除或重新排序行。有关详细信息,请参见下面的多值部分部分。
  • 支持自定义行定义
  • 支持自定义选择器。有关如何定义自己的选择器的详细信息,请查看自定义选择器部分。
  • 提供了一些内联选择器,如日期选择器和内联选择器,并带来创建自定义内联选择器的方法。
  • 根据表单定义进行表单数据验证。
  • 能够在行之间轻松导航,完全可自定义。
  • 如有需要,可以显示inputAccessoryView。默认情况下,显示导航输入辅助视图。
  • 特定行或整个表单的只读模式。
  • 行可以根据其他行的值隐藏或显示。这可以通过使用NSPredicates声明式完成。(请参阅根据其他行的值隐藏或显示行或部分

如何创建表单

创建XLFormViewController的实例

Swift
class CalendarEventFormViewController : XLFormViewController {

  required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    self.initializeForm()
  }


  override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    self.initializeForm()
  }

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

}
Objective-C
#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,隐藏了复杂的样板代码,同时又不失手动制作的表单的强大和灵活性。

要定义表单,我们使用 3 个类

  • XLFormDescriptor
  • XLFormSectionDescriptor
  • XLFormRowDescriptor

一个表单定义为包含一个或多个部分(XLFormSectionDescriptor 实例)的 XLFormDescriptor 实例,每个部分包含多个行(XLFormRowDescriptor 实例)。正如您可能注意到的,DSL 结构类似于 UITableView 的结构(表格 -->> 部分 -->> 行)。生成的 table-view 表单的结构(部分和行的顺序)反映了定义的结构。

让我们看看初始化 form 的示例实现以定义 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 会根据之前解释的定义加载 table-view 表单。最有意思的是,它将根据表单定义的修改来更新 table-view 表单。这意味着我们能够在运行时对表单定义进行更改,添加或删除部分定义和行定义到表单定义中,你将再也不需要关心 NSIndexPathUITableViewDelegateUITableViewDataSource 或其他复杂性。

要查看更多复杂的表单定义,请查看本存储库的 Examples 文件夹中的示例应用程序。如果您愿意,您也可以在自己的设备上运行这些示例。 XLForm 完全不依赖于 其他 pods,但是示例项目使用了某些 CocoaPods 来演示 XLForm 的先进功能。

在 Storyboard 中使用 XLForm

  • 按照 如何创建表单 中的步骤操作
  • 在 Interface Builder(IB)中,将一个 UIViewController 拖放到 Storyboard 中。
  • 使用《Identity Inspector》将自定义表单类关联到 UIViewController 上。

如何运行 XLForm 示例

  1. 克隆仓库 [隐私保护,请替换为DeSmILe5aMBVe8wbDObieXWHx Francis.57GS8Vx9bSeZbQbDBqXDa:xmartlabs/XLForm.git。您可以选择将仓库 Fork 并从您的 GitHub 账户克隆,如果您想贡献力量,这种方法会更好。
  2. 转到 Objective-c 或 Swift 示例文件夹
  3. 安装示例项目的 Cocoapod 依赖项。在 Objective-c 或 Swift 示例文件夹内部运行 pod install
  4. 使用 XCode 打开 XLForm 或 SwiftExample 工作空间并运行项目。祝您享受!

行(Rows)

输入行(Input Rows)

Screenshot of Input Examples

输入行允许用户输入文本值。基本上它们使用 UITextFieldUITextView 控件。输入行类型之间的主要区别在于 keyboardTypeautocorrectionTypeautocapitalizationType 设置。

static NSString *const XLFormRowDescriptorTypeText = @"text";

将表示为具有 UITextAutocorrectionTypeDefaultUITextAutocapitalizationTypeSentencesUIKeyboardTypeDefaultUITextField

static NSString *const XLFormRowDescriptorTypeName = @"name";

将表示为具有 UITextAutocorrectionTypeNoUITextAutocapitalizationTypeWordsUIKeyboardTypeDefaultUITextField

static NSString *const XLFormRowDescriptorTypeURL = @"url";

将表示为具有 UITextAutocorrectionTypeNoUITextAutocapitalizationTypeNoneUIKeyboardTypeURLUITextField

static NSString *const XLFormRowDescriptorTypeEmail = @"email";

将表示为具有 UITextAutocorrectionTypeNoUITextAutocapitalizationTypeNoneUIKeyboardTypeEmailAddressUITextField

static NSString *const XLFormRowDescriptorTypePassword = @"password";

将表示为具有 UITextAutocorrectionTypeNoUITextAutocapitalizationTypeNoneUIKeyboardTypeASCIICapableUITextField。此行类型还将 secureTextEntry 设置为 YES 以隐藏用户输入的内容。

static NSString *const XLFormRowDescriptorTypeNumber = @"number";

将表示为具有 UITextAutocorrectionTypeNoUITextAutocapitalizationTypeNoneUIKeyboardTypeNumbersAndPunctuationUITextField

static NSString *const XLFormRowDescriptorTypePhone = @"phone";

将表示为具有 UIKeyboardTypePhonePadUITextField

static NSString *const XLFormRowDescriptorTypeTwitter = @"twitter";

将表示为具有 UITextAutocorrectionTypeNoUITextAutocapitalizationTypeNoneUIKeyboardTypeTwitterUITextField

static NSString *const XLFormRowDescriptorTypeAccount = @"account";

将表示为具有 UITextAutocorrectionTypeNoUITextAutocapitalizationTypeNoneUIKeyboardTypeDefaultUITextField

static NSString *const XLFormRowDescriptorTypeInteger = @"integer";

将表示为具有 UIKeyboardTypeNumberPadUITextField

static NSString *const XLFormRowDescriptorTypeDecimal = @"decimal";

将使用带UIKeyboardTypeDecimalPadUITextField表示。

static NSString *const XLFormRowDescriptorTypeTextView = @"textView";

将使用具有UITextAutocorrectionTypeDefaultUITextAutocapitalizationTypeSentencesUIKeyboardTypeDefaultUITextView表示。

选择行

选择行允许我们从列表中选择值或多个值。XLForm支持8种内置的选择器。

Screenshot of native Calendar Event Example

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必须能够显示这些对象。

XLForm遵循以下规则来显示对象

  1. 如果XLFormRowDescriptor对象的值为nil,XLForm将使用noValueDisplayText属性作为显示文本。
  2. 如果XLFormRowDescriptor实例具有valueTransformer属性值,XLForm将使用NSValueTransformer将所选对象转换为NSString。
  3. 如果对象是NSStringNSNumber,它将使用对象的description属性。
  4. 如果对象符合协议XLFormOptionObject,XLForm将从formDisplayText方法获取显示值。
  5. 否则返回nil。这意味着您应该实现协议:)

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

这是协议声明

@protocol XLFormOptionObject <NSObject>

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

@end

日期和时间行

XLForms支持3种日期类型: DateDateTimeTimeCountdown Timer,并且能够以两种方式呈现UIDatePicker控件,内联和非内联。

Screenshot of native Calendar Event Example

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";

以下是定义这些行类型的示例

Objective-C

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];

Swift

static let dateTime = "dateTime"
static let date = "date"
static let time = "time"

var form : XLFormDescriptor
var section : XLFormSectionDescriptor
var row : XLFormRowDescriptor

form = XLFormDescriptor(title: "Dates") as XLFormDescriptor

section = XLFormSectionDescriptor.formSectionWithTitle("Inline Dates") as XLFormSectionDescriptor
form.addFormSection(section)

// Date
row = XLFormRowDescriptor(tag: tag.date, rowType: XLFormRowDescriptorTypeDateInline, title:"Date")
row.value = NSDate()
section.addFormRow(row)

// Time
row = XLFormRowDescriptor(tag: tag.time, rowType: XLFormRowDescriptorTypeTimeInline, title: "Time")
row.value = NSDate()
section.addFormRow(row)

// DateTime
row = XLFormRowDescriptor(tag: tag.dateTime, rowType: XLFormRowDescriptorTypeDateTimeInline, title: "Date Time")
row.value = NSDate()
section.addFormRow(row)

self.form = form;

布尔值行

XLForms支持两种布尔值控件。

Screenshot of native Calendar Event Example

static NSString *const XLFormRowDescriptorTypeBooleanCheck = @"booleanCheck";
static NSString *const XLFormRowDescriptorTypeBooleanSwitch = @"booleanSwitch";

我们还可以使用选择行部分中引入的任何选择行类型来模拟其他类型的布尔行。

其他行

步进器

XLForms 支持使用 UIStepper 控件进行计数

Screenshot of native Calendar Event Example

static NSString *const XLFormRowDescriptorTypeStepCounter = @"stepCounter";

您可以轻松设置步进器的参数

	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"];
滑动条

XLForms 支持使用 UISlider 控件进行计数

static NSString *const XLFormRowDescriptorTypeSlider = @"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) 以禁用步进功能。

信息

有时我们的应用程序需要显示不可编辑的数据。XLForm 提供了 XLFormRowDescriptorTypeInfo 行类型来显示不可编辑的信息。一个用例示例是在应用程序设置的部件中显示应用程序版本。

按钮

除了数据输入行、不可编辑行和选择器之外,XLForm 还有一个按钮行 XLFormRowDescriptorTypeButton,允许我们在选择时执行任何操作。它可以配置为使用块(闭包)、选择器、导航标识符、导航类或指定要展示的视图控制器。视图控制器指定可以设置视图控制器类、视图控制器Storyboard ID或nib名称。nib名称必须与视图控制器类名称匹配。

多值部分(插入、删除、排序行)

任何 XLFormSectionDescriptor 对象都可以设置为支持行插入、删除或排序。可以仅启用这些模式中的一个、组合或者全部。多值部分只是一个支持这些模式之一的部分。

多值XLFormSectionDescriptor最有趣的部分在于它支持在Rows部分展示的所有类型行,以及自定义行。

Screenshot of Multivalued Section Example

如何设置多值部分

创建多值部分就像使用下面的方便的XLFormSectionDescriptor初始化器之一一样简单。

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

sectionOptions是一个位枚举参数,应该用于选择多值部分类型/(插入、删除、排序)。可用选项包括XLFormSectionOptionCanInsertXLFormSectionOptionCanDeleteXLFormSectionOptionCanReorderXLFormSectionOptionNone是默认值。

可以使用sectionInsertMode来选择插入模式的外观。XLform提供了2种不同的默认插入模式:XLFormSectionInsertModeLastRowXLFormSectionInsertModeButtonXLFormSectionInsertModeLastRow是默认值。

让我们看看如何创建一个多值部分。

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实例。

返回的NSDictionary是根据以下规则创建的:

如果某个XLFormSectionDescriptor中没有设置multivaluedTag值,XLForm将为属于该XLFormSectionDescriptor的每个XLFormRowDescriptor添加一个值。字典的键是XLFormRowDescriptor tag属性的值。

对于具有multivaluedTag值的每个部分,XLForm添加一个包含NSArray作为值的字典项,每个数组中的值是包含在部分中的每行的值,键是multivaluedTag

例如,如果我们有一个具有等于tagsmultivaluedTag属性的缩放部分,包含行上的以下值:"family"、"male"、"female"、"client",生成的值将是tags: ['family', 'male', 'female', 'client']

httpParameters

在某些情况下,我们需要的数据表单值可能与XLFormRowDescriptor实例的值不同。这通常是选择器行的案例,或者当我们需要将表单值发送到某个端点时,选定的值可能是一个核心数据对象或任何其他对象。在这种情况下,XLForm需要知道如何获取选定对象的值和描述。

当使用-(NSDictionary *)httpParameters方法时,XLForm遵循以下规则来获取XLFormRowDescriptor

  1. 如果对象是NSStringNSNumberNSDate,其值就是对象本身。
  2. 如果对象符合XLFormOptionObject协议,XLForm将从formValue方法获取值。
  3. 否则返回nil。

multivaluedTagformValues方法中与上述方式相同。

如何创建自定义行

要创建自定义的单元格,需要创建一个继承自XLFormBaseCellUITableViewCellXLFormBaseCell符合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

创建自定义单元格后,需要通过将行定义添加到cellClassesForRowDescriptorTypes字典中让XLForm知道这个单元格。

[[XLFormViewController cellClassesForRowDescriptorTypes] setObject:[MYCustomCellClass class] forKey:kMyAppCustomCellType];

或者,在已使用nib文件定义XLBaseDescriptorCell的情况下

[[XLFormViewController cellClassesForRowDescriptorTypes] setObject:@"nibNameWithoutNibExtension" forKey:kMyAppCustomCellType];

这样做,当使用kMyAppCustomCellType行类型时,XLForm将实例化正确的单元格类。

自定义选择器 - 带自定义选择器视图控制器的选择行

几乎所有情况下,基本的允许用户从推送视图控制器中选择一个或多个项的选择器就已经足够满足我们的需求,但有时我们需要更多的灵活性,以带给用户更好的体验或完成默认不支持的操作。

假设应用程序用户需要选择一个地图坐标,或需要从一个端点获取值。我们如何轻松地完成这些操作呢?

Screenshot of Map Custom Selector

定义上述选择器行与以下操作相同:...

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协议。

在上面的示例中,MapViewController符合XLFormRowDescriptorViewController

@protocol XLFormRowDescriptorViewController <NSObject>

@required
@property (nonatomic) XLFormRowDescriptor * rowDescriptor;

@end

XLForm使用属于选择器行的XLFormRowDescriptor实例设置了rowDescriptor属性。

开发人员负责根据rowDescriptor的值更新视图,以及从自定义选择器视图控制器中将所选值设置到rowDescriptor中。

注意:属性viewControllerClassviewControllerNibNameviewControllerStoryboardId是互斥的,并且由XLFormButtonCellXLFormSelectorCell使用。如果您创建了一个自定义单元格,那么您有责任使用它们。

另一个示例

Screenshot of Dynamic Custom Selector

row = [XLFormRowDescriptor formRowDescriptorWithTag:kSelectorUser rowType:XLFormRowDescriptorTypeSelectorPush title:@"User"];
row.action.viewControllerClass = [UsersTableViewController class];

您可以在示例仓库文件夹中找到这些示例的详细信息,请参阅Examples/Objective-C/Examples/Selectors/CustomSelectors/Examples/Objective-C/Examples/Selectors/DynamicSelector

动态表单 - 如何在运行时动态更改表单

XLFormDescriptor的任何更改都会反映在XLFormViewController的tableView上。这意味着当添加或删除部分或行时,XLForm将相应地动画化部分或行。

我们不必处理NSIndexPaths或添加、删除UITableViewCell anymore。随着时间的推移,特定TableViewCellNSIndexPath会改变,这使得难以追踪每个UITableViewCellNSIndexPath

每个XLForm XLFormRowDescriptor行都有在构造函数中设置的tag属性。XLFormDescriptor具有其他辅助器中的一个特定于从tag获取XLFormRowDescriptor。使用标记管理XLFormRowDescriptor更容易,标记应该是唯一的,并且在表视图添加、修改或删除时不会改变。

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

通常,您可能希望在某些值更改或添加或删除行或部分时更改表单。为此,您可以设置行或部分的disabledhidden属性。有关更多详细信息,请参阅根据其他行值使行或部分不可见

为了与表单描述符的修改保持同步,您的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允许您在行之间定义相互依赖关系,以便当一行值的更改时,另一行的行为会自动改变。例如,您可能有一个表单,其中询问用户是否有宠物。如果答案为“是”,您可能希望询问他们的名字。因此,您可以基于其他行的值使一行不可见和再次可见。与部分的作用相同。看看以下示例。

Screenshot of hiding rows

当然,您也可以通过观察某些行的值并相应地删除和添加行来手动完成此操作,但这将是一项大量工作,这些工作已经完成。

工作原理

为了实现行和部分的自动出现和消失,每个描述符中都有一个属性。

@property id hidden;

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

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

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

这将为first后的'$'插入标签,当然您也可以手动完成。当评估谓词时,每个标签变量都会被对应的行描述符替换。

当自变量是一个NSString时,除非标签后面跟着'.isHidden'或'.isDisabled',否则会在每个标签后面附加'.value'。这意味着一个行(或部分)可能依赖于另一个行的valuehiddendisabled属性。当直接使用NSPredicate设置属性时,其格式字符串不会更改(因此,如果您想引用其值,则必须在每个变量后附加'.value')。设置NSString是最简单的方法,但对于一些复杂的谓词可能不起作用。对于这些谓词,您应直接设置NSPredicate。

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

要获取评估后的布尔值,应调用isHidden方法。它不会每次调用时都重新评估谓词,但只是当所依赖的行的值(或隐藏/禁用状态)发生变化时。当这种情况发生且返回值更改时,它将自动反映该变化在表单上,因此不需要调用其他方法。

以下是一个更复杂的示例。

Screenshot of hiding rows

禁用行(设置为只读模式)

可以通过禁用行来防止用户更改它们。默认情况下,禁用行的文字颜色为灰色。要禁用一行,只需设置其disabled属性即可。

@property id disabled;

该属性期望一个包含BOOL、NSString或NSPredicate的NSNumber。布尔值会静态地禁用(或启用)行。其他两个则像上面章节中解释的hidden属性一样工作。这意味着行可以根据其他行的值禁用或启用。当设置NSString时,将生成一个NSPredicate,该字符串用作格式字符串,以便于在此目的上保持一致性。

与hidden属性的不同之处在于,检查行的禁用状态并不会自动将该值反映到表单上。因此,应调用XLFormViewController的updateFormRow方法。

验证

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

每个XLFormRowDescriptor实例都包含一个验证器列表。我们可以添加验证器、删除验证器,并使用这些方法验证特定的行。

-(void)addValidator:(id<XLFormValidatorProtocol>)validator;
-(void)removeValidator:(id<XLFormValidatorProtocol>)validator;
-(XLFormValidationStatus *)doValidation;

我们可以定义自己的自定义验证器,只需定义一个符合XLFormValidatorProtocol的对象。

@protocol XLFormValidatorProtocol <NSObject>

@required

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

@end

XLFormRegexValidator是我们可以创建的验证器的一个示例。

一个非常常见的验证是确保值不为空或nil。XLFom通过required XLFormRowDescriptor属性公开来指定必填项。

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

-(NSArray *)formValidationErrors;

行的附加配置

XLFormRowDescriptor允许我们配置UITableViewCell的通用方面,例如:rowType(行类型)、label(标签)、value(默认值)、单元格是否required(必填)、hidden(隐藏)或disabled(禁用)等。

你可能还想设置另一组UITableViewCell属性。为了设置其他属性,XLForm使用键值编码允许开发人员通过keyPath设置单元格属性。

只需将属性添加到 cellConfigcellConfigAtConfigure 字典属性中,即可修改 XLFormRowDescriptorcellConfigcellConfigAtConfigure 之间的主要区别在于设置属性的时间。 cellConfig 属性在每次单元格即将显示时设置。另一方面,cellConfigAtConfigure 在单元格初始化方法调用后立即设置,且只设置一次。

从版本 3.3.0 开始,您还可以使用 cellConfigForSelector 来配置在显示选择行时 XLFormOptionsViewController 的单元格外观。

例如,如果您想设置占位符,可以按照以下步骤操作:

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

让我们看看如何更改单元格标签的颜色

Objective-C

row = [XLFormRowDescriptor formRowDescriptorWithTag:@"title" rowType:XLFormRowDescriptorTypeText];
[row.cellConfig setObject:[UIColor redColor] forKey:@"textLabel.textColor"];
[section addFormRow:row];

Swift

row = XLFormRowDescriptor(tag: "title", rowType: XLFormRowDescriptorTypeText, title: "title")
row.cellConfig.setObject(UIColor.blackColor(), forKey: "backgroundColor")
row.cellConfig.setObject(UIColor.whiteColor(), forKey: "textLabel.textColor")
section.addFormRow(row)

FAQ

如何定制部分的首部或尾部

为此,您应该在您的 XLFormViewController 中使用 UITableViewDelegate 方法。这意味着您应该实现一个或多个以下方法:

-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section

-(UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section

您可能还希望实现以下方法以指定这些视图的尺寸

-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section

-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section

如何在表单出现时分配第一个响应者

当表单显示时分配第一个响应者,只需将属性 assignFirstResponderOnShow 设置为 YES 即可。默认情况下,该属性的值为 NO

@property (nonatomic) BOOL assignFirstResponderOnShow;

如何为行设置值。

您应该设置相关的 XLFormRowDescriptor 实例的 value 属性。

@property (nonatomic) id value;

您可能已经注意到 value 属性的类型是 id,并且您负责使用正确类型的值来设置。例如,您应该将 NSString 值设置到 XLFormRowDescriptorTypeTextXLFormRowDescriptor 实例中。

如果行已经显示,您可能需要更新单元格以查看 UI 变化。XLFormViewController 提供了 -(void)reloadFormRow:(XLFormRowDescriptor *)formRow 方法来执行此操作。

如何设置行的默认值。

你应与如何设置行的值相同。

如何设置选择行的选项。

XLForm有两种类型的选项行。几乎所有这些都需要知道哪些是要选择的值。对于特定的XLFormRowDescriptor实例,你将这些值设置到selectorOptions属性中,通过设置一个NSArray实例。

@property NSArray * selectorOptions;

如何获取表单值

如果你想获取原始表单值,你应该调用XLFormDescriptorformValues方法。这样做你将得到一个包含所有表单值的字典。每行的tag属性值用作字典键。只有非nil的tag值的XLFormRowDescriptor值被添加到字典中。

你可能对表单值感兴趣,想将其用作端点参数。在这种情况下,httpParameters将很有用。

如果你需要其他东西,你可以遍历每一行...

Objective-C

 NSMutableDictionary * result = [NSMutableDictionary dictionary];
 for (XLFormSectionDescriptor * section in self.form.formSections) {
     if (!section.isMultivaluedSection){
         for (XLFormRowDescriptor * row in section.formRows) {
             if (row.tag && ![row.tag isEqualToString:@""]){
                 [result setObject:(row.value ?: [NSNull null]) forKey:row.tag];
             }
         }
     }
     else{
         NSMutableArray * multiValuedValuesArray = [NSMutableArray new];
         for (XLFormRowDescriptor * row in section.formRows) {
             if (row.value){
                 [multiValuedValuesArray addObject:row.value];
             }
         }
         [result setObject:multiValuedValuesArray forKey:section.multivaluedTag];
     }
 }
 return result;

Swift

var results = [String:String]()
if let fullName = form.formRowWithTag(tag.fullName).value as? String {
    results[tag.fullName] = fullName
}

如何更改UITextField长度

你可以使用cellConfigAtConfigure字典属性来更改UITextField的长度。这个值是指与表格视图单元格相关的百分比。

Objective-C

[row.cellConfigAtConfigure setObject:[NSNumber numberWithFloat:0.7] forKey:XLFormTextFieldLengthPercentage];

Swift

row.cellConfigAtConfigure.setObject(0.7, forKey:XLFormTextFieldLengthPercentage)

**注意:**使用XLFormRowDescriptorTypeTextView时,你也可以对UITextView实现同样的效果;只需将你的百分比设置为键XLFormTextViewLengthPercentage

如何更改 UITableViewCell 的字体

您可以使用 cellConfig 字典属性来更改字体或其他表格单元格属性。当表格单元格即将显示时,XLForm 将设置 cellConfig 字典值。

Objective-C

[row.cellConfig setObject:[UIColor greenColor] forKey:@"textLabel.textColor"];
[row.cellConfig setObject:[UIFont fontWithName:FONT_LATO_REGULAR size:12.0] forKey:@"textLabel.font"];
[row.cellConfig setObject:[UIFont fontWithName:FONT_LATO_REGULAR size:12.0] forKey:@"detailTextLabel.font"];

Swift

row.cellConfig.setObject(UIColor.whiteColor(), forKey: "self.tintColor")
row.cellConfig.setObject(UIFont(name: "AppleSDGothicNeo-Regular", size: 17)!, forKey: "textLabel.font")
row.cellConfig.setObject(UIColor.whiteColor(), forKey: "textField.textColor")
row.cellConfig.setObject(UIFont(name: "AppleSDGothicNeo-Regular", size: 17)!, forKey: "textField.font")

有关详细信息,请参阅 UICustomizationFormViewController.m 示例。

如何为日期单元格设置最小/最大值?

每个 XLFormDateCell 都具有 minimumDatemaximumDate 属性。要将日期行设置为下三天的值,您将按以下方式操作

Objective-C

[row.cellConfigAtConfigure setObject:[NSDate new] forKey:@"minimumDate"];
[row.cellConfigAtConfigure setObject:[NSDate dateWithTimeIntervalSinceNow:(60*60*24*3)] forKey:@"maximumDate"];

Swift

row.cellConfig.setObject(NSDate(), forKey: "maximumDate")

如何禁用整个表单(只读模式)。

可以使用 disable XLFormDescriptor 属性来禁用整个表单。要使显示的单元格生效,我们应该重新加载数据([self.tableView reloadData])。设置 disable 属性为 YES 之后添加到表单的其他行将自动反映禁用模式(无需重新加载数据表)。

如何在不同行值变化时隐藏行或部分。

要隐藏一个行或部分,您应设置其 hidden 属性。最简单的方法是为其设置一个 NSString。例如,如果您想要一个部分在先前的布尔开关行设置为 1(或 YES)时隐藏,您可以这样做

section.hidden = [NSString stringWithFormat:@"$%@ == 1", previousRow];

就是这样!

如何从版本2.2.0迁移到3.0.0?

与旧版本不兼容的唯一一点是,现在XLFormRowDescriptordisabled属性是id。所以你只需在设置的值前面加上@,就像这样:

row.disabled = @YES; // before: row.disabled = YES;

如何更改输入辅助视图(导航视图)

重写inputAccessoryViewForRowDescriptor: XLFormViewController方法。如果完全禁用它,则可以返回nil。但是,您也可以在此处自定义其整体外观。

- (UIView *)inputAccessoryViewForRowDescriptor:(XLFormRowDescriptor *)rowDescriptor
{
      return nil; //will hide it completely
      // You can use the rowDescriptor parameter to hide/customize the accessory view for a particular rowDescriptor type.
}

如何设置推送视图控制器?

将要推送的视图控制器必须遵循XLFormRowDescriptorViewController协议,该协议如下属性组成:

@property (nonatomic) XLFormRowDescriptor * rowDescriptor;

这个rowDescriptor指的是前一个视图控制器的选定行,在切换到新控制器之前将设置,以便在例如其viewDidLoad方法中使用它。这就是在那里应该设置视图控制器的地方。

如何更改特定单元格的默认外观?

最好的方法是为该单元格扩展类并重写其更新和/或配置方法。要使其生效,还应更新您XLFormViewController子类中的cellClassesForRowDescriptorTypes字典,将您的自定义类设置为要更改的单元格的类而不是更改的单元格类。

如何更改单元格的returnkeytype

要更改单元格的returnkeytype,您可以设置returnKeyTypenextReturnKeyType属性。前者在没有导航启用或在此行之后没有行的情况下使用。在其他情况下,将使用后者。如果您创建一个自定义单元格并想使用这些属性,应遵循XLFormReturnKeyProtocol协议。设置方法如下

[row.cellConfigAtConfigure setObject:@(UIReturnKeyGo) forKey:@"nextReturnKeyType"];

如何更改单个单元格的高度

如果您想更改一个类中所有单元格的高度,应子类化该单元格并重写类方法formDescriptorCellHeightForRowDescriptor。如果您想更改单个单元格的高度,可以像这样将其设置到XLFormRowDescripto的height属性中

XLFormRowDescriptor* row = ...
row.height = 55;

如何更改选择器视图控制器(XLFormOptionsViewController)中单元格的外观

要更改XLFormOptionsViewController中单元格的外观,你可以使用行描述符中的cellConfigForSelector属性。例如

[row.cellConfigForSelector setObject:[UIColor redColor] forKey:@"textLabel.textColor"];

如何限制XLFormTextFieldCell或XLFormTextViewCell的字符数

您可以使用textFieldMaxNumberOfCharacterstextViewMaxNumberOfCharacters分别实现这一点。

[row.cellConfigAtConfigure setObject:@(20) forKey:@"textViewMaxNumberOfCharacters"];

安装

Swift Package Manager

从 Xcode 11 开始,Swift Package Manager 是推荐和首选在 Xcode 项目中安装依赖项的方式。通过 SwiftPM 安装依赖项不需要应用程序或依赖项是用 Swift 编写的。

要使用 SwiftPM 将 XLForm 添加到您的项目中,请按照以下步骤操作

  1. 在 Xcode 中打开您的项目
  2. 在主菜单中,选择文件 -> Swift 包 -> 添加包依赖...
  3. 在窗口中,输入包 URL https://github.com/xmartlabs/XLForm
  4. 配置要使用的版本

要使用 XLForm 在您的代码中,根据需要导入模块或头文件

#import "XLForm.h"  // Obj-c
import XLForm       // Swift

CocoaPods

  1. 在项目的 Podfile 文件中添加以下行: pod 'XLForm', '~> 4.2'
  2. 从 Podfile 文件夹目录运行 pod install 命令。

XLForm 不依赖于 其他 pods。

如何使用主分支

通常主分支包含最新的功能和最新的修复。另一方面,这些功能还没有得到充分测试,主分支上的变化可能随时发生。出于以上原因,我强烈建议您分叉仓库,并自行从主分支管理更新,确保按需适当提取。

要使用 xmartlabs 的主分支......

pod 'XLForm', :git => 'https://github.com/xmartlabs/XLForm.git'

如果您愿意,您可以替换仓库 URL 为您的分叉版本 URL。

如何在 Swift 文件中使用 XLForm

如果您使用 cocoapods 安装了 XLForm,并且在您的 Podfile 中设置了 use_frameworks!,您可以将 import XLForm 添加到任何 Swift 文件中。

如果您使用 Cocoapods 但是没有在 Podfile 中设置 use_frameworks!,请将 #import <XLForm/XLForm.h> 添加到您的 bridging header 文件中。

有关如何创建和配置 bridging header 文件的更多信息,请访问 将 Objective-C 导入 Swift

Carthage

在您的 Cartfile 中添加以下内容:

github "xmartlabs/XLForm" ~> 4.2

使用 git 子模块

  • 从您的项目的 git 目录根目录中运行以下命令,将 XLForm 作为 git 子模块 克隆。
$ git submodule add https://github.com/xmartlabs/XLForm.git
  • 打开前面 git 子模块命令创建的 XLForm 文件夹,并将 XLForm.xcodeproj 拖到您应用程序 Xcode 项目的 Project Navigator 中。

  • 在 Project Navigator 中选择 XLForm.xcodeproj,并验证其部署目标是否与您的应用程序部署目标匹配。

  • 在 Xcode 导航中选择您的项目,然后在侧边栏中选择您的应用程序目标。接下来选择 "General" 选项卡,并在 "Embedded Binaries" 部分的 + 按钮下单击。

  • 选择 XLForm.framework,我们已完成操作!

要求

  • ARC
  • iOS 9.0 以上
  • Xcode 9.0+(通过 Swift Package Manager 安装为 11.0+)

发布说明

查看变更日志

作者

Martin Barreto (@mtnBarreto)

联系方式

有任何建议或疑问?请创建一个Github问题或联系我们。

xmartlabs.com (@xmartlabs)