JTForm是一個能簡單快速的搭建流暢復(fù)雜表單的庫涝影,靈感來自于XLForm與Texture危号。JTForm能幫助你像html一樣創(chuàng)建表單隘马。不同于XLForm
是一個UIViewController
的子類逞姿,JTForm
是UIView
的子類乔妈,也就是說蝙云,你可以像使用UIView一樣使用JTForm,應(yīng)用范圍更廣路召,更方便勃刨。JTForm也可以用來創(chuàng)建列表,而不僅僅是表單优训。
JTForm使用Texture
完成視圖的布局與加載朵你,所以集成了Texture的優(yōu)點:異步渲染,極度流暢揣非。使用JTForm抡医,你可以忘記許多原生控件時需要注意的東西:高度設(shè)置,單元行復(fù)用等早敬。為了避免ASTableNode
重載時圖片閃爍的問題忌傻,自定義了JTNetworkImageNode
代替ASNetworkImageNode
。
下面是demo運行在公司老舊設(shè)備5s的截圖搞监,可以看到fps基本保持在60左右水孩。
安裝
- 使用cocoapods:
pod 'JTForm', '~> 0.0.1'
注意事項
- 如果庫自帶的單元行滿足不了需求,需要自定義單元行的時候琐驴,需要了解Texture的相關(guān)知識俘种。
- 如果你的項目中有類似
?IQKeyboardManager
的第三方,請在使用JTForm的時候禁用他們绝淡,不然會跟庫的鍵盤彈起相沖突宙刘。如果你想禁用JTForm的鍵盤彈起,你可以設(shè)置JTForm
的屬性showInputAccessoryView
為NO - 目前該庫依賴于
SDWebImage
(4.4.6),Texture
(2.8)牢酵,使用cocoapods安裝時悬包,如果與本地的版本發(fā)生沖突,請手動添加該庫馍乙,或者升級依賴庫
簡單使用
下面是構(gòu)建該表單一部分的代碼以及注釋
// 構(gòu)建表描述
JTFormDescriptor *formDescriptor = [JTFormDescriptor formDescriptor];
// 是否在必填行的title前面添加一個紅色的*
formDescriptor.addAsteriskToRequiredRowsTitle = YES;
JTSectionDescriptor *section = nil;
JTRowDescriptor *row = nil;
#pragma mark - float text
// 創(chuàng)建節(jié)描述
section = [JTSectionDescriptor formSection];
// 為section創(chuàng)建header title布近,目前需要手動輸入header view的height
section.headerAttributedString = [NSAttributedString attributedStringWithString:@"float text" font:nil color:nil firstWordColor:nil];
// 目前需要手動輸入header view的height,不然是默認(rèn)值丝格,可能會出現(xiàn)排版顯示問題
section.headerHeight = 30.;
// 將節(jié)描述添加到表描述中
[formDescriptor addFormSection:section];
// 創(chuàng)建行描述撑瞧,rowType為必填項,創(chuàng)建單元行時根據(jù)rowType來選擇創(chuàng)建不同的單元行
row = [JTRowDescriptor formRowDescriptorWithTag:JTFormRowTypeFloatText rowType:JTFormRowTypeFloatText title:@"測試"];
// 是否必填
row.required = YES;
// 將行描述添加到表描述中
[section addFormRow:row];
#pragma mark - formatter
row = [JTRowDescriptor formRowDescriptorWithTag:@"20" rowType:JTFormRowTypeNumber title:@"百分比"];
NSNumberFormatter *numberFormatter = [NSNumberFormatter new];
numberFormatter.numberStyle = NSNumberFormatterPercentStyle;
// 添加valueFormatter显蝌,是NSFormatter的子類季蚂,能將value轉(zhuǎn)換成不同的文本。常用的有nsdateformatter
// 這里valueFormatter的作用是將數(shù)字轉(zhuǎn)換成百分?jǐn)?shù),例如10->1000%
row.valueFormatter = numberFormatter;
row.value = @(100);
row.required = YES;
// 在title前面添加圖片
row.image = [UIImage imageNamed:@"jt_money"];
[section addFormRow:row];
row = [JTRowDescriptor formRowDescriptorWithTag:@"21" rowType:JTFormRowTypeNumber title:@"人民幣"];
NSNumberFormatter *numberFormatter1 = [NSNumberFormatter new];
numberFormatter1.numberStyle = NSNumberFormatterCurrencyStyle;
// 這里valueFormatter的作用是將數(shù)字轉(zhuǎn)換成貨幣扭屁,例如10->¥10
row.valueFormatter = numberFormatter1;
row.value = @(100);
row.required = YES;
row.image = [UIImage imageNamed:@"jt_money"];
[section addFormRow:row];
#pragma mark - common
row = [JTRowDescriptor formRowDescriptorWithTag:JTFormRowTypeName rowType:JTFormRowTypeName title:@"JTFormRowTypeName"];
// 占位符
row.placeHolder = @"請輸入姓名...";
// 賦值
row.value = @"djdjd";
row.required = YES;
[section addFormRow:row];
// 創(chuàng)建JTForm算谈,formDescriptor不能為空
JTForm *form = [[JTForm alloc] initWithFormDescriptor:formDescriptor];
form.frame = CGRectMake(0, 0, kJTScreenWidth, kJTScreenHeight-64.);
[self.view addSubview:form];
self.form = form;
行描述 JTRowDescriptor
行描述JTRowDescriptor
是單元行的數(shù)據(jù)源,我們通過修改行描述來控制著單元行的行為料滥,例如:是否顯示然眼,是否可編輯,高度葵腹。
下面是JTRowDescriptor的主要屬性和常用方法
configMode
配置模型高每。
- titleColor:標(biāo)題顏色
- contentColor:詳情顏色
- placeHolderColor:占位符顏色
- disabledTitleColor:禁用時標(biāo)題顏色
- disabledContentColor:禁用時詳情顏色
- bgColor:控件背景顏色
- titleFont:標(biāo)題字體
- contentFont:詳情字體
- placeHlderFont:占位符字體
- disabledTitleFont:禁用時標(biāo)題字體
- disabledContentFont:禁用時詳情字體
JTSectionDescriptor
和JTFormDescriptor
同樣具有這些屬性,作用也類似践宴。優(yōu)先級JTRowDescriptor > JTSectionDescriptor > JTFormDescriptor
image & imageUrl
用于加載圖片鲸匿,樣式類似于UITableViewCell的imageView。image應(yīng)用于靜態(tài)圖片阻肩,imageUrl用于加載網(wǎng)絡(luò)圖片带欢。
rowType
創(chuàng)建表單時,根據(jù)rowType
來創(chuàng)建不同類型的單元行烤惊。目前庫自帶的rowType
都已經(jīng)添加到了[JTForm cellClassesForRowTypes]
字典中乔煞,其中rowType
為key,單元行類型Class為value柒室。在創(chuàng)建時單元行時渡贾,你就可以通過字典根據(jù)rowType
得到相應(yīng)單元行的Class。
所以當(dāng)你自定義單元行時雄右,你需要在+ (void)load
中空骚,將相應(yīng)的rowType以及對應(yīng)的Class添加到[JTForm cellClassesForRowTypes]
字典中。
tag
nullable擂仍,若不為空囤屹,表單將其添加到字典中,其中key為tag防楷,value為JTRowDescriptor
實例。所以如果創(chuàng)建表單時有多個行描述tag值一樣的話则涯,字典中將只會保存最后添加進(jìn)去的JTRowDescriptor复局。
你可以在表單中,根據(jù)tag值找到相對應(yīng)的行描述粟判。且在獲取整個表單值的時候也會派上用場亿昏。
height
該屬性控制著單元行高度。默認(rèn)值為JTFormUnspecifiedCellHeight
档礁,即不指定高度(自動調(diào)節(jié)高度)角钩。
單元行高度的優(yōu)先級:
- JTRowDescriptor的height屬性
- JTBaseCellDelegate的方法
+ (CGFloat)formCellHeightForRowDescriptor:(JTRowDescriptor *)row;
- 自動調(diào)節(jié)高度
action
響應(yīng)事件,目前僅用于點擊單元行。如果單元行上有多個控件有響應(yīng)事件時递礼,建議使用- (JTBaseCell *)cellInForm;
得到當(dāng)前的單元行cell惨险,然后用[cell.button addTarget:self action:action forControlEvents:UIControlEvents]
添加響應(yīng)事件。
hidden & disabled
hidden:bool值脊髓,控制隱藏或者顯示當(dāng)前單元行
disabled:bool值辫愉,控制當(dāng)前單元行是否接受響應(yīng)事件
JTSectionDescriptor
和JTFormDescriptor
同樣具有這些屬性衙荐,作用也類似饺藤。優(yōu)先級JTRowDescriptor > JTSectionDescriptor > JTFormDescriptor
cellConfigAfterUpdate & cellConfigWhenDisabled & cellConfigAtConfigure & cellDataDictionary
- cellConfigAfterUpdate:配置cell,在‘update’方法后使用
- cellConfigWhenDisabled:配置cell哥攘,當(dāng)'update'方法后依疼,且disabled屬性為Yes時被使用
- cellConfigAtConfigure:配置cell痰腮,當(dāng)cell調(diào)用config之后,update方法之前調(diào)用
- cellDataDictionary:預(yù)留律罢,你可以選擇使用時機
text
文本方面的膀值,屬性比較多,統(tǒng)一放到這里講
- valueFormatter:文本格式轉(zhuǎn)換弟翘,可以將數(shù)據(jù)格式化為一種易讀的格式虫腋。‘NSFormatter’是一個抽象類稀余,我們只使用它的子類悦冀,類似'NSDateFormatter'和‘NSNumberFormatter’
- placeHolder:占位符,當(dāng)value為空時顯示該內(nèi)容
- maxNumberOfCharacters:文本類單元行能輸入最大字符數(shù)
-
- (nullable NSString *)displayContentValue;
:在未編輯狀態(tài)時睛琳,詳情的顯示內(nèi)容 -
- (nullable NSString *)editTextValue;
:在編輯狀態(tài)時盒蟆,詳情的顯示內(nèi)容
驗證器
你可以通過- (void)addValidator:(nonnull id<JTFormValidateProtocol>)validator;
添加一個或多個驗證器,驗證器的作用是對單元行的值進(jìn)行驗證师骗,來判斷是否符合你的要求历等,例如:身份證格式,密碼的復(fù)雜程度辟癌,字?jǐn)?shù)長度等寒屯。
當(dāng)然,除了庫自帶的驗證器外黍少,你可以自定義自己的驗證器寡夹,注意需要實現(xiàn)代理JTFormValidateProtocol
。
單元行類型
文本類
- JTFormRowTypeFloatText
- JTFormRowTypeText
- JTFormRowTypeName
- JTFormRowTypeEmail
- JTFormRowTypeNumber
- JTFormRowTypeInteger
- JTFormRowTypeDecimal
- JTFormRowTypePassword
- JTFormRowTypePhone
- JTFormRowTypeURL
- JTFormRowTypeTextView
- JTFormRowTypeInfo
主要的區(qū)別是鍵盤不同厂置,需要注意的是:JTFormRowTypeTextView
和JTFormRowTypeInfo
是textview
菩掏,而其它幾種是textfield
。
select類
- JTFormRowTypePushSelect
push到另一個vc中昵济,僅可選擇一個
- JTFormRowTypeMultipleSelect
push到另一個vc中智绸,可選擇多個
- JTFormRowTypeSheetSelect
UIAlertController野揪,樣式為UIAlertControllerStyleActionSheet
- JTFormRowTypeAlertSelect
UIAlertController,樣式為UIAlertControllerStyleAlert
- JTFormRowTypePickerSelect
類似于彈出鍵盤瞧栗,inputview為UIPickeraaa
選擇項通常會擁有一個展示文本斯稳,一個是代表value的id。例如你在選擇汽車型號的時候沼溜,展示給你的是不同汽車的型號的文本平挑,當(dāng)你選中之后傳給后臺的是代表該型號的文本。
在選擇類的單元行中系草,我們使用的選擇項類型是JTOptionObject
通熄,主要由兩個屬性formDisplayText
和formValue
,含義顧名思義找都。選擇項可以通過selectorOptions
賦值得到唇辨,在單元行選中之后,單元行的value也是JTOptionObject
類型(單選)或者為NSArray<JTOptionObject *> *
類型(多選)能耻,你可以使用NSObject類目方法- (id)cellValue;
得到value赏枚。
date類
- JTFormRowTypeDate
- JTFormRowTypeTime
- JTFormRowTypeDateTime
- JTFormRowTypeCountDownTimer
- JTFormRowTypeDateInline
除了JTFormRowTypeDateInline
,其余集中的區(qū)別只是UIDatePicker
中timeStyle
和timeStyle
的區(qū)別晓猛。JTFormRowTypeDateInline
的效果如下:
其它
- JTFormRowTypeSwitch
- JTFormRowTypeCheck
- JTFormRowTypeStepCounter
- JTFormRowTypeSegmentedControl
- JTFormRowTypeSlider
具體樣式可以看demo
JTBaseCell
單元行的基類饿幅,如果你需要自定義單元行的話需要繼承它。JTBaseCell
里面的屬性和方法都比較簡單戒职,需要注意的是JTBaseCellDelegate
栗恩,下面來我來說明一下它的幾個方法:
config
required。初始化控件洪燥,在這個方法里只需要創(chuàng)建需要的控件磕秤,但不需要為控件添加內(nèi)容,因為這個時候并沒有添加進(jìn)去數(shù)據(jù)源JTRowDescriptor
捧韵。在生命周期內(nèi)該方法只會被調(diào)用一次市咆,除非調(diào)用JTRowDescriptor
的方法reloadCell
,該方法會重新創(chuàng)建單元行再来。
子類中實現(xiàn)時需要調(diào)用[super config]
update
required蒙兰。更新視圖內(nèi)容,在生命周期中會被多次調(diào)用芒篷。在這個方法中搜变,我們可以為已經(jīng)創(chuàng)建好的內(nèi)容添加內(nèi)容。
子類中實現(xiàn)時需要調(diào)用[super update]
其它
剩下的幾個方法都是@optional
+ (CGFloat)formCellHeightForRowDescriptor:(JTRowDescriptor *)row
指定單元行的高度
- (BOOL)formCellCanBecomeFirstResponder
指示單元行是否能夠成為第一響應(yīng)者, 默認(rèn)返回NO
- (BOOL)formCellBecomeFirstResponder
單元行成為第一響應(yīng)者
- (BOOL)formCellResignFirstResponder
單元行放棄第一響應(yīng)者
- (void)formCellDidSelected
當(dāng)前的單元行被選中了
- (NSString *)formDescriptorHttpParameterName
為單元行設(shè)置一個參數(shù)名稱梭伐。若不為空痹雅,當(dāng)調(diào)用JTFormDescriptor
的方法httpParameters
返回的表單字典中仰担,key為該參數(shù)名稱糊识,value為JTRowDescriptor的value绩社。
- (void)formCellHighlight
單元行高亮
- (void)formCellUnhighlight
單元行不高亮
自定義單元行
以demo中我自定義的單元行IGCell
為例。
+ (void)load
首先赂苗,你需要一個rowType來代表該行愉耙。然后在+ (void)load
方法中[[JTForm cellClassesForRowTypes] setObject:self forKey:JTFormRowTypeIGCell];
將rowType與單元行關(guān)聯(lián)起來。
config
- (void)config
{
[super config];
// 你的代碼
}
在這里你可以創(chuàng)建好控件拌滋,但不需要為控件添加內(nèi)容朴沿。注意需要調(diào)用[super config];
。
update
- (void)update
{
[super update];
// 你的代碼
}
在這個方法中败砂,我們可以為已經(jīng)創(chuàng)建好的內(nèi)容添加內(nèi)容赌渣。。注意需要調(diào)用[super update];
layoutSpecThatFits
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
昌犹。在這個方法中坚芜,你需要創(chuàng)建好布局。對此斜姥,你需要額外學(xué)習(xí)Texture
(原AsyncDisplayKit)的布局系統(tǒng)鸿竖。
表單行為控制
hidden
當(dāng)表單完成之后,你可以通過改變JTRowDescriptor
,JTSectionDescriptor
,JTFormDescriptor
hidden的值來隱藏或者顯示相應(yīng)的單元行铸敏,單元節(jié)缚忧,表單。
disabled
你可以通過改變JTRowDescriptor
,JTSectionDescriptor
,JTFormDescriptor
disabled的值來決定相應(yīng)的單元行杈笔,單元節(jié)闪水,表單是否可以被編輯。
delete row
JTSectionDescriptor *section = [JTSectionDescriptor formSection];
section.sectionOptions = JTFormSectionOptionCanDelete;
你可以這樣創(chuàng)建節(jié)描述桩撮,就可以讓單元節(jié)具有刪除單元行功能敦第。
FAQ
如何給section自定義 header/footer
你也可以通過設(shè)置JTSectionDescriptor
的headerHieght
和headerView
或者footerHieght
和footerView
屬性來自定義header/footer。目前需要手動設(shè)置高度...
如何拿到表單的值
你可以通過JTForm
的- (NSDictionary *)formValues
獲取表單值店量。如果設(shè)置了驗證器或者有必填項芜果,可以先調(diào)用- (NSArray<NSError *> *)formValidationErrors
來獲取錯誤集合,再獲取表單值進(jìn)行其它操作融师。
如何給日期行設(shè)置最大右钾,最小日期
你可以通過下面的代碼這樣設(shè)置,雖然丑陋旱爆,但是能用...
[row.cellConfigAtConfigure setObject:[NSDate date] forKey:@"minimumDate"];
[row.cellConfigAtConfigure setObject:[NSDate dateWithTimeIntervalSinceNow:(60*60*24*3)] forKey:@"maximumDate"];
如何改變cell的高度
單元行高度的優(yōu)先級:
- JTRowDescriptor的height屬性
- JTBaseCellDelegate的方法
+ (CGFloat)formCellHeightForRowDescriptor:(JTRowDescriptor *)row;
- 根據(jù)布局來生成高度
如何自定義類似于JTFormRowTypeDateInline的內(nèi)聯(lián)行
如果你想要創(chuàng)建類似JTFormRowTypeDateInline
的內(nèi)聯(lián)行舀射,就意味著你需要自定義兩種單元行。拿JTFormRowTypeDateInline舉個例子怀伦,A:JTFormDateCell脆烟,B:JTFormDateInlineCell。當(dāng)你選中A時房待,B顯示出來邢羔,再選中A驼抹,B消失。
- 首先拜鹤,創(chuàng)建兩種單元行A, B
- B在
load
方法中框冀,還需要額外添加[[JTForm inlineRowTypesForRowTypes] setObject: A.rowType forKey:B.rowType]
- 剩下的操作為以下代碼,你可以照著寫敏簿。這里簡單說明以下明也,當(dāng)你選擇A時,會調(diào)用
formCellCanBecomeFirstResponder
和formCellBecomeFirstResponder
方法惯裕。隨后調(diào)用canBecomeFirstResponder
和becomeFirstResponder
温数,注意這里必須調(diào)用super的方法,不然當(dāng)前單元行無法成為第一響應(yīng)者蜻势。在becomeFirstResponder
中帆吻,我們創(chuàng)建B,并且添加到A后面咙边。
- (BOOL)formCellCanBecomeFirstResponder
{
return [self canBecomeFirstResponder];
}
- (BOOL)formCellBecomeFirstResponder
{
if ([self isFirstResponder]) {
return [self resignFirstResponder];
}
return [self becomeFirstResponder];
}
- (BOOL)canBecomeFirstResponder
{
[super canBecomeFirstResponder];
return !self.rowDescriptor.disabled;
}
- (BOOL)becomeFirstResponder
{
[super becomeFirstResponder];
NSIndexPath *currentIndexPath = [self.rowDescriptor.sectionDescriptor.formDescriptor indexPathForRowDescriptor:self.rowDescriptor];
JTSectionDescriptor *section = [self.rowDescriptor.sectionDescriptor.formDescriptor.formSections objectAtIndex:currentIndexPath.section];
JTRowDescriptor *inlineRow = [JTRowDescriptor formRowDescriptorWithTag:nil rowType:JTFormRowTypeInlineDatePicker title:nil];
JTFormDateInlineCell *inlineCell = (JTFormDateInlineCell *)[inlineRow cellInForm];
NSAssert([inlineCell conformsToProtocol:@protocol(JTFormInlineCellDelegate)], @"inline cell must conform to protocol 'JTFormInlineCellDelegate'");
inlineCell.connectedRowDescriptor = self.rowDescriptor;
[section addFormRow:inlineRow afterRow:self.rowDescriptor];
[self.findForm ensureRowIsVisible:inlineRow];
BOOL result = [super becomeFirstResponder];
if (result) {
[self.findForm beginEditing:self.rowDescriptor];
}
return result;
}
- (BOOL)canResignFirstResponder
{
BOOL result = [super canResignFirstResponder];
return result;
}
- (BOOL)resignFirstResponder
{
BOOL result = [super resignFirstResponder];
if ([self.rowDescriptor.rowType isEqualToString:JTFormRowTypeDateInline]) {
NSIndexPath *currentIndexPath = [self.rowDescriptor.sectionDescriptor.formDescriptor indexPathForRowDescriptor:self.rowDescriptor];
NSIndexPath *nextRowPath = [NSIndexPath indexPathForRow:currentIndexPath.row + 1 inSection:currentIndexPath.section];
JTRowDescriptor *inlineRow = [self.rowDescriptor.sectionDescriptor.formDescriptor formRowAtIndex:nextRowPath];
if ([inlineRow.rowType isEqualToString:JTFormRowTypeInlineDatePicker]) {
[self.rowDescriptor.sectionDescriptor removeFormRow:inlineRow];
}
}
return result;
}