XLFormPhotoView 3.3.0

XLFormPhotoView 3.3.0

测试验证
语言语言 Obj-CObjective C
许可 MIT
发布最后发布2017年10月

felixyin 维护。



  • 尹彬

XLForm

作者:XMARTLABS


如果您正在寻找 Swift 原生实现,我们最近创建了一个名为 Eureka 的项目,它是 XLForm 在 Swift 中的完全重新设计。不要担心,我们还将继续维护和改进 XLForm,obj-c 是无敌的!!

目的

XLForm 是创建动态表格视图表单的最灵活和强大的 iOS 库。库的目标是获得手写表单相同的强大功能,但只需花十分之一的时间。

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

#####以下是一个使用 XLForm 创建的 iOS 日历事件表单示例

Screenshot of native Calendar Event Example

XLForm 做什么

  • 基于声明性的 表单定义 加载表单。
  • 在运行时跟踪定义更改以便相应地更新表单界面。更多关于 动态表单 的信息请参阅本说明书的相应部分。
  • 支持多值部分,允许我们创建、删除或重新排序行。有关详细信息,请参阅下文的 多值部分 部分。
  • 支持 自定义行定义
  • 支持自定义选择器。有关如何定义自己的选择器的更多详细信息,请参阅下文的 自定义选择器 部分。
  • 提供了一些内置选择器,如日期选择器和选择器内联选择器,并提供了一种创建自定义内联选择器的方法。
  • 根据表单定义进行表单数据验证。
  • 能够在行之间轻松导航,完全可自定义。
  • 如果需要,可以显示 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(表格 -->> 部分 --> 行)。生成的表格视图表单的结构(部分和行顺序)与定义的结构相对应。

让我们看看 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将从先前解释的定义中加载表格视图表单。最有意思的部分是,它将根据表单定义的修改更新表格视图表单。
这意味着我们可以在运行时对表格视图表单做出更改,添加或删除部分定义或行定义到表单定义中,您再也不需要关心 NSIndexPathUITableViewDelegateUITableViewDataSource 或其他复杂性。

要查看更复杂的形式定义,请查看此存储库 Examples 目录中的示例应用程序。 如果您愿意,您还可以在自己的设备上运行示例。XLForm不依赖于其他 pods,但示例项目使用一些 Cocoapods 来展示 XLForm 的先进功能。

使用 Storyboards 与 XLForm

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

如何运行 XLForm 示例

  1. 克隆存储库 [email protected]:xmartlabs/XLForm.git。您可以选择将存储库分叉并从您自己的 GitHub 账户克隆它,如果您想要贡献,这种方法会更好。
  2. 移到 Objective-c 或 Swift 示例文件夹
  3. 安装示例项目的 Cocoapod 依赖关系。在 Objective-c 或 Swift 示例文件夹中运行 pod install
  4. 使用 XCode 打开 XLForm 或 SwiftExample 的工作空间,并运行项目。祝您玩得开心!

####输入行

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
此行类型还设置 secureTextEntryYES 以隐藏用户输入的内容。

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

将由具有 UITextViewUITextAutocorrectionTypeDefaultUITextAutocapitalizationTypeSentences 以及 UIKeyboardTypeDefaultUITextView 表示。

####选择器行

选择器行允许我们从列表中选择值或多个值。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,它允许在选中时执行任何操作。它可以通过块(闭包)、选择器、撤销标识符、撤销类或指定要呈现的视图控制器进行配置。可以基于视图控制器类、视图控制器的故事板ID或nib名称设置视图控制器 specification。nib名称必须与视图控制器类名称匹配。

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

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

多值XLFormSectionDescriptor最有趣的部分是它支持部分中显示的所有类型以及自定义行。

Screenshot of Multivalued Section Example

如何设置多值部分

创建多值部分非常简单,只需使用以下便利XLFormSectionDescriptor初始化器之一即可

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

sectionOptions 是一个位枚举参数,用于选择多值部分类型(插入、删除、重新排序)。可用选项包括 XLFormSectionOptionCanInsertXLFormSectionOptionCanDeleteXLFormSectionOptionCanReorder。默认使用的是 XLFormSectionOptionNone

sectionInsertMode 可以用来选择插入模式的外观。XLform 自带两种插入模式: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 是根据以下规则创建的:

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

对于具有 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 方法中的方式一样工作。

如何创建自定义行

要创建自定义单元格,您需要创建一个从 XLFormBaseCell 扩展的 UITableViewCellXLFormBaseCell 符合 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];

这样做后,XLForm 将在用到 kMyAppCustomCellType 行类型时实例化适当的单元格类。

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

基本选择器通常就足够了,它允许用户从推送的视图控制器中选择一项或多项,但有时我们需要更多灵活性,为用户提供更好的用户体验或执行一些默认情况下不支持的操作。

假设您的应用用户需要选择地图坐标或从服务器端点选择值,我们如何轻松地做到这一点?

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/CustomSelectorsExamples/Objective-C/Examples/Selectors/DynamicSelector

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

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

我们不需要处理 NSIndexPath 或添加、删除 UITableViewCell。随着时间推移,特定 UITableViewCellNSIndexPath 会发生变化,这使得难以追踪每个 UITableViewCellNSIndexPath

每个 XLForm XLFormRowDescriptor 行都有一个在构造函数中设置的 tag 属性。除了其他辅助工具外,XLFormDescriptor 还有一个特定于获取带有 tagXLFormRowDescriptor 的方法。
使用标签管理 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),使其在包含“hide”值的上一行(first)消失。

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

这将把first标签放在'$'之后,当然您也可以手动操作。当谓词被评估时,每个标签变量都会被相应的行描述符替换。

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

您也可以使用bool对象来设置这些属性,这意味着除非手动设置,否则属性值不会改变。

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

下面是一个更复杂的例子:

Screenshot of hiding rows

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

可以禁用行,这样用户就不能更改它们。默认情况下,禁用行的文字颜色为灰色。要禁用一行,要做的只是设置它的禁用属性

@property id disabled;

该属性预期包含一个BOOL的NSNumber或一个NSString或一个NSPredicate。布尔值将静态地禁用(或启用)行。其他两个与上文中解释的隐藏属性的工作方式相同。这意味着一个行可以根据其他行的值被禁用或启用。当设置NSString时,将使用字符串作为格式字符串生成NSPredicate,因此它必须为了这个目的保持一致。

与隐藏属性的区别是,检查行的禁用状态不会自动将此值反映在表单上。因此,应调用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的通用方面,例如:rowTypelabelvalue(默认值)、单元格是否是requiredhiddendisabled等。

您可能想要设置UITableViewCell的另外一些属性。要设置其他属性,XLForm使用键值编码,允许开发者通过keyPath设置单元格属性。

只需将属性添加到cellConfigcellConfigAtConfigure字典属性中,这些属性属于XLFormRowDescriptor
cellConfigcellConfigAtConfigure之间的主要区别在于属性设置的时间。每次单元格即将显示时都会设置cellConfig属性。而另一方面,cellConfigAtConfigure在单元格的init方法被调用后立即设置属性,并且只设置一次。

自版本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)

常见问题解答

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

将表单显示时设置第一个响应者与将属性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属性值用作字典键。只有具有非niltag值的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方法中可以使用。这就是该视图控制器应该设置的地方。

如何更改某些单元格的默认外观?

最好的办法是扩展该单元格的类并覆盖其update和/或configure方法。为了使这生效,您还应该更新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"];

安装

如何使用master分支

通常,master分支包含最新功能和最新修复。另一方面,这些功能尚未完全测试,并且master上的更改可能会随时发生。出于上述原因,我强烈建议您分发此存储库,并自行从master管理更新,根据需要进行适当的拉取操作。

要使用 xmartlabs master 分支......

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

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

如何在Swift文件中使用XLForm?

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

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

有关如何创建和配置桥接头文件的更多详细信息,请访问 将 Objective-C 导入 Swift

使用 git 子模块

  • 在您项目的根 git 文件夹中运行以下命令以将 XLForm 作为一个 git 子模块 拉取。
$ git submodule add https://github.com/xmartlabs/XLForm.git
  • 打开由上一个 git 子模块命令创建的 XLForm 文件夹,将 XLForm.xcodeproj 拖拽到您的应用程序 Xcode 项目的项目导航器中。

  • 在项目导航器中选择 XLForm.xcodeproj,并验证其部署目标与您的应用程序部署目标相匹配。

  • 在 Xcode 导航中选择您的项目,然后从侧边栏中选择您的应用程序目标。接下来,选择“通用”选项卡,并点击“嵌入式二进制文件”部分下的 + 按钮。

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

需求

  • ARC
  • iOS 7.0 及以上
  • XCode 6.3+

发行说明

请查看 CHANGELOG

作者

Martin Barreto (@mtnBarreto)

联系方式

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

xmartlabs.com (@xmartlabs)