寫一個(gè)iOS復(fù)雜表單的正確姿勢(shì)
前言
這幾天項(xiàng)目的新需求中有個(gè)復(fù)雜的表單界面,在做的過程中發(fā)現(xiàn)要比想象中復(fù)雜很多,有好多問題需要處理。有很多東西值得寫下來(lái)好好梳理下。
需求分析:
6創(chuàng)建網(wǎng)店1.png
上圖便是UI根據(jù)需求給的高保真, 我們先根據(jù)這張圖片來(lái)描述一下具體需求,明確一下我們都需要干些什么。
創(chuàng)建網(wǎng)店這個(gè)界面是一個(gè)復(fù)雜的表單,有“網(wǎng)店名稱”、“網(wǎng)店主標(biāo)簽”、“網(wǎng)店簡(jiǎn)介”、“網(wǎng)店地址”、“網(wǎng)店座機(jī)”、“email”、“網(wǎng)店LOGO”、“網(wǎng)店封面圖”這些項(xiàng)。大部分都是輸入框,但也有幾項(xiàng)有所不同。“網(wǎng)店地址”項(xiàng),當(dāng)被點(diǎn)擊后會(huì)彈出一個(gè)pickView來(lái)選擇“市&區(qū)”;“網(wǎng)店LOGO”和“網(wǎng)店封面圖”是一樣的,是選取圖片的控件,要求既可以通過相冊(cè)選取圖片,也可以現(xiàn)場(chǎng)拍照選擇。當(dāng)被點(diǎn)擊后,彈出一個(gè)ActionSheet來(lái)是以“拍照”或以“相冊(cè)”來(lái)選取圖片。當(dāng)選取成功后拍照的背景圖片變?yōu)楸贿x取的圖片,并在右上角出現(xiàn)一個(gè)刪除按鈕,可以刪除還原再次選取。
表單中除了“email”外所有的項(xiàng)目都是必填的,且“網(wǎng)店名稱”、“網(wǎng)店主標(biāo)簽”、“網(wǎng)店簡(jiǎn)介”和“網(wǎng)店座機(jī)”分別有30、20、500、15字的長(zhǎng)度限制。“email”雖然為選填,但若填寫了則會(huì)進(jìn)行郵箱格式校驗(yàn)。對(duì)字?jǐn)?shù)長(zhǎng)度的限制要在輸入過程中進(jìn)行監(jiān)聽,若輸入時(shí)超過限制,則輸入框出現(xiàn)紅色邊框并出現(xiàn)提示文字。等***點(diǎn)擊了“提交”按鈕后要進(jìn)行數(shù)據(jù)校驗(yàn),所有該填但未填,所有格式不正確的項(xiàng)都會(huì)出現(xiàn)紅框和提示文字,當(dāng)所有數(shù)據(jù)都合法后才可以提交給服務(wù)器。
需求大體就是如此。
這個(gè)界面我們還是以tableView來(lái)實(shí)現(xiàn),由cell視圖來(lái)表示圖中所需填寫的項(xiàng)目。那我們得先分析下這個(gè)界面需要寫哪幾種樣式的cell。
該界面總共有4種樣式的cell。4種樣式的cell樣式也有共同點(diǎn),每個(gè)cell左邊部分均為表示該行所要填寫的項(xiàng)目名稱,右邊部分則為填寫或者選取的內(nèi)容值,這些值的顯示形式有所不同。 CreateShopTFCell和CreateShopTVCell其實(shí)非常類似,右邊首先是一個(gè)灰色的背景視圖,只不過在灰色背景之上的前者是textField,而后者是textView;CreateShopPickCell右邊則是兩個(gè)灰色背景視圖,點(diǎn)擊之后便彈出一個(gè)pickView供你選取“市&區(qū)”;CreateShopUploadPicCell右邊則是一個(gè)UIImageView,無(wú)圖片被選取時(shí)默認(rèn)是一個(gè)相機(jī)的圖片,當(dāng)被點(diǎn)擊后彈出ActionSheet供你選擇拍照還是從相冊(cè)選取照片,選好照片后UIImageView的圖片被替換,并在右上角出現(xiàn)紅色的刪除按鈕。
如下圖所示:
6創(chuàng)建網(wǎng)店.png
正確地將視圖和數(shù)據(jù)綁定:
我們假設(shè)已經(jīng)寫好了上面4種樣式cell的代碼,現(xiàn)在我們?cè)诳刂破骼餅槠涮畛鋽?shù)據(jù)。
我們首先定義一個(gè)表示cell數(shù)據(jù)的CreateShopModel。該model是為了給cell填充數(shù)據(jù),可以看到它里面的屬性就是cell上對(duì)應(yīng)應(yīng)該顯示的數(shù)據(jù)項(xiàng)。
同時(shí),我們?cè)陂_頭也定義了一個(gè)枚舉CreateShopCellType來(lái)代表4種不同樣式的cell,用于在tableView返回cell的代理方法里根據(jù)枚舉值來(lái)返回相應(yīng)樣式的cell。
- #import
- typedef enum : NSUInteger {
- CreateShopCellType_TF = 0, // textfield
- CreateShopCellType_TV, // textView
- CreateShopCellType_PICK, // picker
- CreateShopCellType_PIC, // upload picture
- } CreateShopCellType;
- @interface CreateShopModel : NSObject
- @property (nonatomic, copy)NSString *title; // 所要填寫的項(xiàng)目名稱
- @property (nonatomic, copy)NSString *placeholder;
- @property (nonatomic, copy)NSString *key; // 表單對(duì)應(yīng)的字段
- @property (nonatomic, copy)NSString *errText; // 校驗(yàn)出錯(cuò)時(shí)的提示信息
- @property (nonatomic, strong)UIImage *image; // 所選取的圖片
- @property (nonatomic, assign)CreateShopCellType cellType; // cell的類型
- @property (nonatomic, assign)NSInteger maxInputLength; // ***輸入長(zhǎng)度限制
- @end
我們?cè)趯ableView創(chuàng)建并添加在控制器的view上后便可以初始化數(shù)據(jù)源了。該界面tableView的數(shù)據(jù)源是_tableViewData數(shù)組,數(shù)據(jù)的每項(xiàng)元素是代表cell顯示數(shù)據(jù)的CreateShopModel類型的model。準(zhǔn)確地來(lái)說(shuō),這些數(shù)據(jù)是表單未填寫之前的死數(shù)據(jù),所以需要我們手動(dòng)地給裝入數(shù)據(jù)源數(shù)組中。而在輸入框輸入或者選取而得的數(shù)據(jù)則需要我們?cè)谳斎胫髮⑵洳东@存儲(chǔ)下來(lái),以等到提交時(shí)提交給服務(wù)器,這個(gè)也有需要注意的坑點(diǎn),后面再說(shuō)。
現(xiàn)在我們的數(shù)據(jù)源準(zhǔn)備好了,但是tableView還沒做處理呢,要等tableView也配套完成后再刷新tableView就OK了。我們來(lái)看tableView代理方法。

首先比較簡(jiǎn)單的,在設(shè)置行高的代理方法里,根據(jù)該行數(shù)據(jù)所表示的cellType類型來(lái)設(shè)置相應(yīng)的行高。
然后在返回cell的代理方法里,同樣以cellType來(lái)判斷返回相應(yīng)樣式的cell,并給該cell賦相應(yīng)的數(shù)據(jù)model。但是我們注意到,給cell賦值的方法,除了傳入我們前面說(shuō)定義的CreateShopModel類型的createModel外,還有個(gè)名叫_shopFormModel參數(shù)被傳入。_shopFormModel是什么,它代表什么意思?
_shopFormModel是CreateShopFormModel類型的一個(gè)實(shí)例對(duì)象,它用來(lái)表示這個(gè)表單需要提交的數(shù)據(jù),它里面的每個(gè)屬性基本上對(duì)應(yīng)著表單提交給服務(wù)器的字段。我們***不是要將表單數(shù)據(jù)作為參數(shù)去請(qǐng)求提交的接口嗎?表單數(shù)據(jù)從哪里來(lái),就從_shopFormModel中來(lái)。那_shopFormModel中的數(shù)據(jù)從哪里來(lái)?
- #import
- @interface CreateShopFormModel : NSObject
- @property (nonatomic, copy)NSString *groupId;
- @property (nonatomic, copy)NSString *groupName;
- @property (nonatomic, copy)NSString *tag;
- @property (nonatomic, copy)NSString *introduction;
- @property (nonatomic, copy)NSString *regionId;
- @property (nonatomic, copy)NSString *cityId;
- @property (nonatomic, copy)NSString *address;
- @property (nonatomic, copy)NSString *telephone;
- @property (nonatomic, copy)NSString *contactMail;
- @property (nonatomic, copy)NSString *coverUrl;
- @property (nonatomic, copy)NSString *logoUrl;
- @property (nonatomic, strong)UIImage *logo;
- @property (nonatomic, strong)UIImage *cover;
- @property (nonatomic, strong)NSIndexPath *indexPath;
- @property (nonatomic, strong)id indexPathObj;
- + (CreateShopFormModel *)formModelFromDict:(NSDictionary *)dict;
- -(BOOL)submitCheck:(NSArray*)dataArr;
- @end
以CreateShopTFCell為例,它所表示的字段的數(shù)據(jù)是我們?cè)谳斎肟蜉斎氲?,也就是說(shuō)數(shù)據(jù)來(lái)自textField,_shopFormModel對(duì)象在控制器被傳入cell的refreshContent:formModel:方法,在該方法內(nèi)部,將參數(shù)formModel賦給成員變量_formModel。需要格外注意的是,_shopFormModel、formModel和_ formModel是同一個(gè)對(duì)象,指向的是同一塊內(nèi)存地址。方法傳遞對(duì)象參數(shù)時(shí)只是“引用拷貝”,拷貝了一份對(duì)象的引用。既然這樣,我們可以預(yù)想到,我們?cè)赾ell內(nèi)部,將textField輸入的值賦給_formModel所指向的對(duì)象后,也即意味著控制器里的_shopFormModel也有數(shù)據(jù)了,因?yàn)樗鼈儽緛?lái)就是同一個(gè)對(duì)象嘛!
事實(shí)正是如此。
可以看到我們?cè)诮otextField添加的通知的回調(diào)方法textFiledEditChanged:里,將textField輸入的值以KVC的方式賦值給了_formModel。此時(shí)_formModel的某屬性,即該cell對(duì)應(yīng)的表單的字段已經(jīng)有了數(shù)據(jù)。同樣的,在控制器中與_formModel指向同一塊內(nèi)存地址的_shopFormModel也有了數(shù)據(jù)。

我們看到在refreshContent:formModel:方法中,cell上的死數(shù)據(jù)是被CreateShopModel的實(shí)例對(duì)象createModel賦值的,而在其后我們又以KVC的方式又將_shopFormModel的某屬性的值賦給了textField。這是因?yàn)槲覀優(yōu)榱朔乐筩ell在復(fù)用的過程中出現(xiàn)數(shù)據(jù)錯(cuò)亂的問題,而在給cell賦值前先將每個(gè)視圖上的數(shù)據(jù)都清空了(即clearCellData方法),需要我們重新賦過。(不過,如果你沒清空數(shù)據(jù)的情況下,不再次給textField賦值好像也是沒問題的。不會(huì)出現(xiàn)數(shù)據(jù)錯(cuò)亂和滑出屏幕再滑回來(lái)時(shí)從復(fù)用池取出cell后賦值時(shí)數(shù)據(jù)消失的問題。)
輸入長(zhǎng)度的限制:
需求中要求“網(wǎng)店名稱”、“網(wǎng)店主標(biāo)簽”、“網(wǎng)店簡(jiǎn)介”、“網(wǎng)店座機(jī)”都有輸入長(zhǎng)度的限制,分別為30、20、500、15字?jǐn)?shù)的限制。其實(shí)我們?cè)谏厦娉跏蓟瘮?shù)據(jù)源的時(shí)候已經(jīng)為每行的數(shù)據(jù)源model設(shè)置過字?jǐn)?shù)限制了,即maxInputLength屬性。
我們還是以CreateShopTFCell為例。
要在開始輸入的時(shí)候監(jiān)聽輸入的長(zhǎng)度,若字?jǐn)?shù)超過***限制,則要出現(xiàn)紅框,并且顯示提示信息。那我們就得給textField開始輸入時(shí)添加valueChange的觀察,在textField輸入結(jié)束時(shí)移除觀察。
另外,可以看到在textField開始輸入的回調(diào)方法里,調(diào)用了該cell的代理方法。該cell為什么要調(diào)用這個(gè)代理方法,它需要代理給別人來(lái)干什么?…其實(shí)這個(gè)和鍵盤遮擋的處理有關(guān),下面我們慢慢解釋。
處理鍵盤遮擋問題:
這個(gè)界面有很多行輸入框,在自然情況下,下面的幾個(gè)輸入框肯定是在鍵盤彈出后高度之下的,也即會(huì)被鍵盤遮擋住,我們沒法輸入。這時(shí)就一定處理鍵盤遮擋問題了。
關(guān)于鍵盤遮擋問題,其實(shí)我在以前的一篇筆記中就寫過了:UITextField一籮筐——輸入長(zhǎng)度限制、自定義placeholder、鍵盤遮擋問題
我們要處理鍵盤遮擋問題,也就是要實(shí)現(xiàn)當(dāng)鍵盤彈出時(shí),被遮擋住的輸入框能上移到鍵盤高度之上;當(dāng)鍵盤收回時(shí),輸入框又能移回原來(lái)的位置。那么首先***步,我們得能獲取到鍵盤彈出或者收回這個(gè)動(dòng)作的時(shí)機(jī),在這個(gè)時(shí)機(jī)我們?cè)侔葱枰苿?dòng)輸入框的位置。系統(tǒng)提供了表示鍵盤彈出和收回的兩個(gè)觀察的key,分別為UIKeyboardWillShowNotification和UIKeyboardWillHideNotification。注冊(cè)這兩個(gè)觀察者,然后在兩者的回調(diào)方法里實(shí)現(xiàn)輸入框位移就大功告成了。
因?yàn)殒I盤遮擋的處理有可能是比較普遍的需求,所以在公司的項(xiàng)目架構(gòu)設(shè)計(jì)里是把上面兩個(gè)關(guān)于鍵盤的觀察是注冊(cè)在APPDelegate.m中的,并定義了一個(gè)有關(guān)鍵盤遮擋處理的協(xié)議,協(xié)議里定義了一個(gè)方法。具體需要具體處理,由需要處理鍵盤遮擋問題的控制器來(lái)實(shí)現(xiàn)該協(xié)議方法,具體實(shí)現(xiàn)怎么移動(dòng)界面元素來(lái)使鍵盤不遮擋輸入框。這么說(shuō)現(xiàn)在CreateShopViewController控制器需要處理鍵盤遮擋問題,那么就需要設(shè)置它為APPDelegate的代理,并由它實(shí)現(xiàn)所定義的協(xié)議嗎?其實(shí)不用,公司項(xiàng)目所有的控制器都是繼承于基類CommonViewController,在基類中實(shí)現(xiàn)了比較基本和普遍的功能,其實(shí)在基類中便定義了下面的方法來(lái)設(shè)置控制器為APPDelegate的代理,不過需要屬性isListensKeyboard為YES。下面這個(gè)方法在CommonViewController中是在viewWillAppear:方法中調(diào)用的。那我們?cè)谧宇怌reateShopViewController中需要做的僅僅只要在viewWillAppear之前設(shè)置isListensKeyboard屬性為YES,便會(huì)自動(dòng)設(shè)置將自己設(shè)為APPDelegate的代理。然后在CreateShopViewController控制器里實(shí)現(xiàn)協(xié)議所定義的方法,實(shí)現(xiàn)具體的輸入框移動(dòng)問題。
CommonViewController.m
- -(void)initListensKeyboardNotificationDelegate
- {
- if (!self.isListensKeyboard) {
- return;
- }
- if (!self.appDelegate) {
- self.appDelegate=(AppDelegate*)[[UIApplication sharedApplication] delegate];
- }
- [self.appDelegate setKeyboardDelegate:self];
- }
CreateShopViewController.m
可以看到在該代理方法的實(shí)現(xiàn)里。當(dāng)鍵盤彈出時(shí),我們首先將tableView的contentSize在原來(lái)的基礎(chǔ)上增加了鍵盤的高度keyBoard_h。然后將tableView的contentOffset值變?yōu)閟et_y,這個(gè)set_y的值是通過計(jì)算而來(lái),但是計(jì)算它的_inputY這個(gè)變量代表什么意思?
我們可以回過頭去看看tableView返回cell的代理方法中,當(dāng)為CreateShopTFCell時(shí),我們?cè)O(shè)置了當(dāng)前控制器為其cell的代理。
- cell.cellDelegate = self;
并且我們的控制器CreateShopViewController也實(shí)現(xiàn)了該cell的協(xié)議CreateShopTFCellDelegate,并且也實(shí)現(xiàn)了協(xié)議定義的方法。
- #pragma mark - tfCell delegate
- - (void)cellBeginInputviewY:(CGFloat)orginY
- {
- _inputY = orginY;
- }
原來(lái)上面的_intputY變量就是該協(xié)議方法從cell里的調(diào)用處傳遞而來(lái)的orginY參數(shù)值。我們回過頭看上面的代碼,該協(xié)議方法是在textField的開始輸入的回調(diào)方法里調(diào)用的,給協(xié)議方法傳入的參數(shù)是self.frame.origin.y,即被點(diǎn)擊的textField在手機(jī)屏幕內(nèi)所在的Y坐標(biāo)值。
可以看到,處理鍵盤遮擋問題,其實(shí)也不是改變輸入框的坐標(biāo)位置,而是變動(dòng)tableView的contentSize和contentOffset屬性。
選取地址的實(shí)現(xiàn):
CreateShopPickCell實(shí)現(xiàn)里地址的選取和顯示。有左右兩個(gè)框框,點(diǎn)擊任何一個(gè)將會(huì)從屏幕下方彈出一個(gè)選取器,選取器有“市”和“區(qū)”兩列數(shù)據(jù)對(duì)應(yīng)兩個(gè)框框,選取器左上方是“取消”按鈕,右上方是“確定”按鈕。點(diǎn)擊“取消”,選取器彈回,并不進(jìn)行選取;點(diǎn)擊“確定”,選取器彈回,選取選擇的數(shù)據(jù)。
WechatIMG1.png
CreateShopPickCell的界面元素布局沒什么可說(shuō)的,值得一說(shuō)的是彈出的pickView視圖,是在cell的填充數(shù)據(jù)的方法中創(chuàng)建的。
這里只是創(chuàng)建了pickView的對(duì)象,并設(shè)置了數(shù)據(jù)源items,已經(jīng)點(diǎn)擊之后的回調(diào)block,而并未將其添加在父視圖上。
要將選取的“市&區(qū)”的結(jié)果從CustomPickView中以block回調(diào)到cell來(lái),將數(shù)據(jù)賦給_formModel。并且當(dāng)有了數(shù)據(jù)后UILabel的文本顏色也有變化。
pickView的對(duì)象已經(jīng)創(chuàng)建好,但是還未到彈出顯示的時(shí)機(jī)。所謂時(shí)機(jī),就是當(dāng)左右兩個(gè)框框被點(diǎn)擊后。
可以看到pickView是被添加在window上的。并且調(diào)用了pickView的接口方法showPickerView方法,讓其從屏幕底部彈出來(lái)。
- - (void)cityGestureHandle:(UITapGestureRecognizer *)tapGesture
- {
- [_superView endEditing:YES];
- [self showPicker];
- }
- - (void)areaGestureHandle:(UITapGestureRecognizer *)tapGesture
- {
- [_superView endEditing:YES];
- [self showPicker];
- }
- -(void)showPicker
- {
- [[PubicClassMethod getCurrentWindow] addSubview:_pickView];
- [_pickView showPickerView];
- }
前面代碼中給pickView設(shè)置數(shù)據(jù)源時(shí),它的數(shù)據(jù)源有點(diǎn)特別,調(diào)用了ShopAddressModel的類方法cityAddressArr來(lái)返回有關(guān)地址的數(shù)據(jù)源數(shù)組。這是因?yàn)檫@里的地址數(shù)據(jù)雖然是從服務(wù)器接口請(qǐng)求的,但是一般情況不會(huì)改變,***是從服務(wù)器拿到數(shù)據(jù)后緩存在本地,當(dāng)請(qǐng)求失敗或者無(wú)網(wǎng)絡(luò)時(shí)仍不受影響。
ShopAddressModel類定義了如下幾個(gè)屬性和方法。
- @interface ShopAddressModel : NSObject
- @property (nonatomic, copy)NSString *addresssId;
- @property (nonatomic, copy)NSString *name;
- @property (nonatomic, strong)NSArray *subArr;
- #pragma mark - 地址緩存
- + (void)saveAddressArr:(NSArray *)addressArr;
- +(NSArray*)cityAddressArr;
- +(NSArray*)addressArr;
- #pragma mark - 解析
- + (ShopAddressModel *)addressModelFromDict:(NSDictionary *)dict;
- @end
當(dāng)我們我們從服務(wù)器拿到返回而來(lái)的地址數(shù)據(jù)后,調(diào)用saveAddressArr:方法,將數(shù)據(jù)緩存在本地。
- + (void)saveAddressArr:(NSArray *)addressArr
- {
- if (addressArr && addressArr.count > 0) {
- NSData *data = [NSKeyedArchiver archivedDataWithRootObject:addressArr];
- [[NSUserDefaults standardUserDefaults] setObject:data forKey:@"saveAddressArr"];
- }else
- {
- [[NSUserDefaults standardUserDefaults]setObject:nil forKey:@"saveAddressArr"];
- }
- [[NSUserDefaults standardUserDefaults] synchronize];
- }
當(dāng)創(chuàng)建好pickView后以下面方法將本地緩存數(shù)據(jù)讀出,賦給items作為數(shù)據(jù)源。
注意:這也是為什么把創(chuàng)建pickView的代碼放在了填充cell數(shù)據(jù)的refreshContent:formModel:里,而不在創(chuàng)建cell界面元素時(shí)一氣創(chuàng)建pickView。因?yàn)槟菢赢?dāng)用戶***次打開這個(gè)界面,有可能數(shù)據(jù)來(lái)的比較慢,當(dāng)代碼執(zhí)行到賦數(shù)據(jù)源items時(shí),本地還沒有被緩存上數(shù)據(jù)呢!這樣用戶***次進(jìn)入這個(gè)界面時(shí)彈出的pickView是空的,沒有數(shù)據(jù)。而放在refreshContent:formModel:中是安全穩(wěn)妥的原因是,每次從接口拿到數(shù)據(jù)后我們會(huì)刷新tableView,便會(huì)執(zhí)行refreshContent:formModel:方法。它能保證先拿到數(shù)據(jù),再設(shè)置數(shù)據(jù)源的順序。
提交表單時(shí)校驗(yàn)數(shù)據(jù):
在將表單數(shù)據(jù)提交前,要先校驗(yàn)所填寫的表單是否有問題,該填的是否都填了,已填的數(shù)據(jù)格式是否是對(duì)的。若有問題,則要出現(xiàn)紅框和提示信息提醒用戶完善,等數(shù)據(jù)無(wú)誤后才可以提交給服務(wù)器。
數(shù)據(jù)校驗(yàn)代碼很繁長(zhǎng),寫在控制器里不太好。因?yàn)樗菍?duì)表單數(shù)據(jù)的校驗(yàn),那我們就寫在CreateShopFormModel里,這樣既可以給控制器瘦身,也可以降低耦合度,數(shù)據(jù)的歸數(shù)據(jù),邏輯的歸邏輯。
從前面CreateShopFormModel.h的代碼里我們其實(shí)已經(jīng)看到了這個(gè)校驗(yàn)方法:submitCheck:。若某條CreateShopFormModel實(shí)例的數(shù)據(jù)不達(dá)要求,則在相應(yīng)的CreateShopModel數(shù)據(jù)源對(duì)象的errText屬性賦值,意為提示信息。該方法的返回值類型為BOOL值,有數(shù)據(jù)不合格則返回NO。此時(shí),在調(diào)用該方法的外部,應(yīng)該將tableView重新加載,因?yàn)榇藭r(shí)在該方法內(nèi)部,已將數(shù)據(jù)格式不合格的提示信息賦值給了相應(yīng)的數(shù)據(jù)源model。
- - (BOOL)submitCheck:(NSArray*)dataArr
- {
- BOOL isSubmit=YES;
- if(self.groupName.length==0){
- if (dataArr.count>0) {
- CreateShopModel *cellObj=dataArr[0];
- cellObj.errText=@"網(wǎng)店名不能為空";
- }
- isSubmit=NO;
- }
- if(self.groupName.length>0){
- if(dataArr.count>0){
- if(self.groupName.length>30){
- CreateShopModel *cellObj=dataArr[0];
- cellObj.errText=@"最多30個(gè)字";
- isSubmit=NO;
- }
- }
- }
- if(self.tag.length==0){
- if (dataArr.count>1) {
- CreateShopModel *cellObj=dataArr[1];
- cellObj.errText=@"標(biāo)簽不能為空";
- }
- isSubmit=NO;
- }
- if(self.introduction.length==0){
- if (dataArr.count>2) {
- CreateShopModel *cellObj=dataArr[2];
- cellObj.errText=@"簡(jiǎn)介不能為空";
- }
- isSubmit=NO;
- }
- if(self.introduction.length>0){
- if(dataArr.count>2){
- if(self.introduction.length>30){
- CreateShopModel *cellObj=dataArr[2];
- cellObj.errText=@"最多500個(gè)字";
- isSubmit=NO;
- }
- }
- }
- if(self.regionId.length==0){
- if (dataArr.count>3) {
- CreateShopModel *cellObj=dataArr[3];
- cellObj.errText=@"市區(qū)不能為空";
- }
- isSubmit=NO;
- }
- if(self.address.length==0){
- if (dataArr.count>4) {
- CreateShopModel *cellObj=dataArr[4];
- cellObj.errText=@"地址不能為空";
- }
- isSubmit=NO;
- }
- if(self.telephone.length==0){
- if (dataArr.count>5) {
- CreateShopModel *cellObj=dataArr[5];
- cellObj.errText=@"電話不能為空";
- }
- isSubmit=NO;
- }
- if (self.contactMail.length>0) {
- if (dataArr.count>6) {
- CreateShopModel *cellObj=dataArr[6];
- if(![PubicClassMethod isValidateEmail:self.contactMail]){
- cellObj.errText=@"郵箱格式不合法";
- isSubmit=NO;
- }
- }
- }
- if(self.logoUrl.length==0&&!self.logo){
- if (dataArr.count>7) {
- CreateShopModel *cellObj=dataArr[7];
- cellObj.errText=@"logo不能為空";
- }
- isSubmit=NO;
- }
- if(self.coverUrl.length==0&&!self.cover){
- if (dataArr.count>8) {
- CreateShopModel *cellObj=dataArr[8];
- cellObj.errText=@"封面圖不能為空";
- }
- isSubmit=NO;
- }
- return isSubmit;
- }
上傳圖片到七牛:
當(dāng)點(diǎn)擊了“提交”按鈕后,先校驗(yàn)數(shù)據(jù),若所填寫的數(shù)據(jù)不合格,則給出提示信息,讓用戶繼續(xù)完善數(shù)據(jù);若數(shù)據(jù)無(wú)問題,校驗(yàn)通過,則開始提交表單。但是,這里有圖片,圖片我們是上傳到七牛服務(wù)器的,提交表單是圖片項(xiàng)提交的應(yīng)該是圖片在七牛的一個(gè)url。這個(gè)邏輯我在以前的這篇筆記已經(jīng)捋過了APP上傳圖片至七牛的邏輯梳理。
但是當(dāng)時(shí)所有的邏輯都是寫在控制器里的。我們這個(gè)“創(chuàng)建網(wǎng)店”的控制器已經(jīng)很龐大了,寫在控制器里不太好。所以在這里我將上傳圖片的邏輯拆分了出去,新建了一個(gè)類`QNUploadPicManager。只暴露一個(gè)允許傳入U(xiǎn)IImage參數(shù)的接口方法,便可以通過successBlock來(lái)返回上傳到七牛成功后的url。以及通過failureBlock來(lái)返回上傳失敗后的error信息。而將所有的邏輯封裝在QNUploadPicManager內(nèi)部,這樣控制器里便精簡(jiǎn)了不少代碼,清爽了許多。
QNUploadPicManager.h
- @interface QNUploadPicManager : NSObject
- - (void)uploadImage:(UIImage *)image successBlock:(void(^)(NSString *urlStr))successBlock failureBlock:(void(^)(NSError *error))failureBlock;
- @end
QNUploadPicManager.m
總結(jié):
這個(gè)界面比較核心的一個(gè)問題就是:要在控制器里提交表單,那怎樣把在UITableViewCell里的textField輸入的數(shù)據(jù)傳遞給控制器? 另外一個(gè)問題是一個(gè)邏輯比較復(fù)雜的界面,控制器勢(shì)必會(huì)很龐大,應(yīng)該有意的給控制器瘦身,不能把所有的邏輯都寫在控制器里。有關(guān)視圖顯示的就考慮放入U(xiǎn)ITableViewCell,有關(guān)數(shù)據(jù)的就考慮放入model。這樣既為控制器瘦身,也使代碼職責(zé)變清晰,耦合度降低。