精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

寫一個iOS復雜表單的正確姿勢

移動開發 iOS
這幾天項目的新需求中有個復雜的表單界面,在做的過程中發現要比想象中復雜很多,有好多問題需要處理。有很多東西值得寫下來好好梳理下。

前言

這幾天項目的新需求中有個復雜的表單界面,在做的過程中發現要比想象中復雜很多,有好多問題需要處理。有很多東西值得寫下來好好梳理下。

需求分析: 

 

 

 

6創建網店1.png

上圖便是UI根據需求給的高保真, 我們先根據這張圖片來描述一下具體需求,明確一下我們都需要干些什么。

創建網店這個界面是一個復雜的表單,有“網店名稱”、“網店主標簽”、“網店簡介”、“網店地址”、“網店座機”、“email”、“網店LOGO”、“網店封面圖”這些項。大部分都是輸入框,但也有幾項有所不同。“網店地址”項,當被點擊后會彈出一個pickView來選擇“市&區”;“網店LOGO”和“網店封面圖”是一樣的,是選取圖片的控件,要求既可以通過相冊選取圖片,也可以現場拍照選擇。當被點擊后,彈出一個ActionSheet來是以“拍照”或以“相冊”來選取圖片。當選取成功后拍照的背景圖片變為被選取的圖片,并在右上角出現一個刪除按鈕,可以刪除還原再次選取。

表單中除了“email”外所有的項目都是必填的,且“網店名稱”、“網店主標簽”、“網店簡介”和“網店座機”分別有30、20、500、15字的長度限制。“email”雖然為選填,但若填寫了則會進行郵箱格式校驗。對字數長度的限制要在輸入過程中進行監聽,若輸入時超過限制,則輸入框出現紅色邊框并出現提示文字。等***點擊了“提交”按鈕后要進行數據校驗,所有該填但未填,所有格式不正確的項都會出現紅框和提示文字,當所有數據都合法后才可以提交給服務器。

需求大體就是如此。

這個界面我們還是以tableView來實現,由cell視圖來表示圖中所需填寫的項目。那我們得先分析下這個界面需要寫哪幾種樣式的cell。

該界面總共有4種樣式的cell。4種樣式的cell樣式也有共同點,每個cell左邊部分均為表示該行所要填寫的項目名稱,右邊部分則為填寫或者選取的內容值,這些值的顯示形式有所不同。 CreateShopTFCell和CreateShopTVCell其實非常類似,右邊首先是一個灰色的背景視圖,只不過在灰色背景之上的前者是textField,而后者是textView;CreateShopPickCell右邊則是兩個灰色背景視圖,點擊之后便彈出一個pickView供你選取“市&區”;CreateShopUploadPicCell右邊則是一個UIImageView,無圖片被選取時默認是一個相機的圖片,當被點擊后彈出ActionSheet供你選擇拍照還是從相冊選取照片,選好照片后UIImageView的圖片被替換,并在右上角出現紅色的刪除按鈕。

如下圖所示: 

 

 

 

6創建網店.png

正確地將視圖和數據綁定:

我們假設已經寫好了上面4種樣式cell的代碼,現在我們在控制器里為其填充數據。

我們首先定義一個表示cell數據的CreateShopModel。該model是為了給cell填充數據,可以看到它里面的屬性就是cell上對應應該顯示的數據項。

同時,我們在開頭也定義了一個枚舉CreateShopCellType來代表4種不同樣式的cell,用于在tableView返回cell的代理方法里根據枚舉值來返回相應樣式的cell。

  1. #import 
  2.  
  3.   
  4.  
  5. typedef enum : NSUInteger { 
  6.  
  7.     CreateShopCellType_TF = 0, // textfield 
  8.  
  9.     CreateShopCellType_TV, // textView 
  10.  
  11.     CreateShopCellType_PICK, // picker 
  12.  
  13.     CreateShopCellType_PIC, // upload picture 
  14.  
  15. } CreateShopCellType; 
  16.  
  17.   
  18.  
  19. @interface CreateShopModel : NSObject 
  20.  
  21.   
  22.  
  23. @property (nonatomic, copy)NSString                    *title;  // 所要填寫的項目名稱 
  24.  
  25. @property (nonatomic, copy)NSString                    *placeholder; 
  26.  
  27. @property (nonatomic, copy)NSString                    *key; // 表單對應的字段 
  28.  
  29. @property (nonatomic, copy)NSString                    *errText; // 校驗出錯時的提示信息 
  30.  
  31. @property (nonatomic, strong)UIImage                    *image;     // 所選取的圖片 
  32.  
  33. @property (nonatomic, assign)CreateShopCellType         cellType; // cell的類型 
  34.  
  35. @property (nonatomic, assign)NSInteger                 maxInputLength; // ***輸入長度限制 
  36.  
  37.   
  38.  
  39. @end  

我們在將tableView創建并添加在控制器的view上后便可以初始化數據源了。該界面tableView的數據源是_tableViewData數組,數據的每項元素是代表cell顯示數據的CreateShopModel類型的model。準確地來說,這些數據是表單未填寫之前的死數據,所以需要我們手動地給裝入數據源數組中。而在輸入框輸入或者選取而得的數據則需要我們在輸入之后將其捕獲存儲下來,以等到提交時提交給服務器,這個也有需要注意的坑點,后面再說。

 

 

 

  

 

現在我們的數據源準備好了,但是tableView還沒做處理呢,要等tableView也配套完成后再刷新tableView就OK了。我們來看tableView代理方法。 

   

 

 

首先比較簡單的,在設置行高的代理方法里,根據該行數據所表示的cellType類型來設置相應的行高。

然后在返回cell的代理方法里,同樣以cellType來判斷返回相應樣式的cell,并給該cell賦相應的數據model。但是我們注意到,給cell賦值的方法,除了傳入我們前面說定義的CreateShopModel類型的createModel外,還有個名叫_shopFormModel參數被傳入。_shopFormModel是什么,它代表什么意思?

_shopFormModel是CreateShopFormModel類型的一個實例對象,它用來表示這個表單需要提交的數據,它里面的每個屬性基本上對應著表單提交給服務器的字段。我們***不是要將表單數據作為參數去請求提交的接口嗎?表單數據從哪里來,就從_shopFormModel中來。那_shopFormModel中的數據從哪里來?

  1. #import 
  2.  
  3.   
  4.  
  5. @interface CreateShopFormModel : NSObject 
  6.  
  7.   
  8.  
  9. @property (nonatomic, copy)NSString            *groupId; 
  10.  
  11. @property (nonatomic, copy)NSString            *groupName; 
  12.  
  13. @property (nonatomic, copy)NSString            *tag; 
  14.  
  15. @property (nonatomic, copy)NSString            *introduction; 
  16.  
  17. @property (nonatomic, copy)NSString            *regionId; 
  18.  
  19. @property (nonatomic, copy)NSString            *cityId; 
  20.  
  21. @property (nonatomic, copy)NSString            *address; 
  22.  
  23. @property (nonatomic, copy)NSString            *telephone; 
  24.  
  25. @property (nonatomic, copy)NSString            *contactMail; 
  26.  
  27. @property (nonatomic, copy)NSString            *coverUrl; 
  28.  
  29. @property (nonatomic, copy)NSString            *logoUrl; 
  30.  
  31. @property (nonatomic, strong)UIImage        *logo; 
  32.  
  33. @property (nonatomic, strong)UIImage        *cover; 
  34.  
  35. @property (nonatomic, strong)NSIndexPath    *indexPath; 
  36.  
  37. @property (nonatomic, strong)id             indexPathObj; 
  38.  
  39.   
  40.  
  41. + (CreateShopFormModel *)formModelFromDict:(NSDictionary *)dict; 
  42.  
  43. -(BOOL)submitCheck:(NSArray*)dataArr; 
  44.  
  45.   
  46.  
  47. @end  

以CreateShopTFCell為例,它所表示的字段的數據是我們在輸入框輸入的,也就是說數據來自textField,_shopFormModel對象在控制器被傳入cell的refreshContent:formModel:方法,在該方法內部,將參數formModel賦給成員變量_formModel。需要格外注意的是,_shopFormModel、formModel和_ formModel是同一個對象,指向的是同一塊內存地址。方法傳遞對象參數時只是“引用拷貝”,拷貝了一份對象的引用。既然這樣,我們可以預想到,我們在cell內部,將textField輸入的值賦給_formModel所指向的對象后,也即意味著控制器里的_shopFormModel也有數據了,因為它們本來就是同一個對象嘛!

事實正是如此。

可以看到我們在給textField添加的通知的回調方法textFiledEditChanged:里,將textField輸入的值以KVC的方式賦值給了_formModel。此時_formModel的某屬性,即該cell對應的表單的字段已經有了數據。同樣的,在控制器中與_formModel指向同一塊內存地址的_shopFormModel也有了數據。 

  

 

 

 

我們看到在refreshContent:formModel:方法中,cell上的死數據是被CreateShopModel的實例對象createModel賦值的,而在其后我們又以KVC的方式又將_shopFormModel的某屬性的值賦給了textField。這是因為我們為了防止cell在復用的過程中出現數據錯亂的問題,而在給cell賦值前先將每個視圖上的數據都清空了(即clearCellData方法),需要我們重新賦過。(不過,如果你沒清空數據的情況下,不再次給textField賦值好像也是沒問題的。不會出現數據錯亂和滑出屏幕再滑回來時從復用池取出cell后賦值時數據消失的問題。)

輸入長度的限制:

需求中要求“網店名稱”、“網店主標簽”、“網店簡介”、“網店座機”都有輸入長度的限制,分別為30、20、500、15字數的限制。其實我們在上面初始化數據源的時候已經為每行的數據源model設置過字數限制了,即maxInputLength屬性。

我們還是以CreateShopTFCell為例。

要在開始輸入的時候監聽輸入的長度,若字數超過***限制,則要出現紅框,并且顯示提示信息。那我們就得給textField開始輸入時添加valueChange的觀察,在textField輸入結束時移除觀察。   

 

另外,可以看到在textField開始輸入的回調方法里,調用了該cell的代理方法。該cell為什么要調用這個代理方法,它需要代理給別人來干什么?…其實這個和鍵盤遮擋的處理有關,下面我們慢慢解釋。

處理鍵盤遮擋問題:

這個界面有很多行輸入框,在自然情況下,下面的幾個輸入框肯定是在鍵盤彈出后高度之下的,也即會被鍵盤遮擋住,我們沒法輸入。這時就一定處理鍵盤遮擋問題了。

關于鍵盤遮擋問題,其實我在以前的一篇筆記中就寫過了:UITextField一籮筐——輸入長度限制、自定義placeholder、鍵盤遮擋問題

我們要處理鍵盤遮擋問題,也就是要實現當鍵盤彈出時,被遮擋住的輸入框能上移到鍵盤高度之上;當鍵盤收回時,輸入框又能移回原來的位置。那么首先***步,我們得能獲取到鍵盤彈出或者收回這個動作的時機,在這個時機我們再按需要移動輸入框的位置。系統提供了表示鍵盤彈出和收回的兩個觀察的key,分別為UIKeyboardWillShowNotification和UIKeyboardWillHideNotification。注冊這兩個觀察者,然后在兩者的回調方法里實現輸入框位移就大功告成了。

因為鍵盤遮擋的處理有可能是比較普遍的需求,所以在公司的項目架構設計里是把上面兩個關于鍵盤的觀察是注冊在APPDelegate.m中的,并定義了一個有關鍵盤遮擋處理的協議,協議里定義了一個方法。具體需要具體處理,由需要處理鍵盤遮擋問題的控制器來實現該協議方法,具體實現怎么移動界面元素來使鍵盤不遮擋輸入框。這么說現在CreateShopViewController控制器需要處理鍵盤遮擋問題,那么就需要設置它為APPDelegate的代理,并由它實現所定義的協議嗎?其實不用,公司項目所有的控制器都是繼承于基類CommonViewController,在基類中實現了比較基本和普遍的功能,其實在基類中便定義了下面的方法來設置控制器為APPDelegate的代理,不過需要屬性isListensKeyboard為YES。下面這個方法在CommonViewController中是在viewWillAppear:方法中調用的。那我們在子類CreateShopViewController中需要做的僅僅只要在viewWillAppear之前設置isListensKeyboard屬性為YES,便會自動設置將自己設為APPDelegate的代理。然后在CreateShopViewController控制器里實現協議所定義的方法,實現具體的輸入框移動問題。

CommonViewController.m 

  1. -(void)initListensKeyboardNotificationDelegate 
  2.  
  3.  
  4.     if (!self.isListensKeyboard) { 
  5.  
  6.         return
  7.  
  8.     } 
  9.  
  10.   
  11.  
  12.     if (!self.appDelegate) { 
  13.  
  14.         self.appDelegate=(AppDelegate*)[[UIApplication sharedApplication] delegate]; 
  15.  
  16.     } 
  17.  
  18.   
  19.  
  20.     [self.appDelegate setKeyboardDelegate:self]; 
  21.  

CreateShopViewController.m 

 

 

 

可以看到在該代理方法的實現里。當鍵盤彈出時,我們首先將tableView的contentSize在原來的基礎上增加了鍵盤的高度keyBoard_h。然后將tableView的contentOffset值變為set_y,這個set_y的值是通過計算而來,但是計算它的_inputY這個變量代表什么意思?

我們可以回過頭去看看tableView返回cell的代理方法中,當為CreateShopTFCell時,我們設置了當前控制器為其cell的代理。

  1. cell.cellDelegate = self; 

并且我們的控制器CreateShopViewController也實現了該cell的協議CreateShopTFCellDelegate,并且也實現了協議定義的方法。

  1. #pragma mark - tfCell delegate 
  2.  
  3. - (void)cellBeginInputviewY:(CGFloat)orginY 
  4.  
  5.  
  6.     _inputY = orginY; 
  7.  
  8.  

原來上面的_intputY變量就是該協議方法從cell里的調用處傳遞而來的orginY參數值。我們回過頭看上面的代碼,該協議方法是在textField的開始輸入的回調方法里調用的,給協議方法傳入的參數是self.frame.origin.y,即被點擊的textField在手機屏幕內所在的Y坐標值。

可以看到,處理鍵盤遮擋問題,其實也不是改變輸入框的坐標位置,而是變動tableView的contentSize和contentOffset屬性。

選取地址的實現:

CreateShopPickCell實現里地址的選取和顯示。有左右兩個框框,點擊任何一個將會從屏幕下方彈出一個選取器,選取器有“市”和“區”兩列數據對應兩個框框,選取器左上方是“取消”按鈕,右上方是“確定”按鈕。點擊“取消”,選取器彈回,并不進行選取;點擊“確定”,選取器彈回,選取選擇的數據。 

 

 

 

WechatIMG1.png

CreateShopPickCell的界面元素布局沒什么可說的,值得一說的是彈出的pickView視圖,是在cell的填充數據的方法中創建的。 

 

 

 

這里只是創建了pickView的對象,并設置了數據源items,已經點擊之后的回調block,而并未將其添加在父視圖上。

要將選取的“市&區”的結果從CustomPickView中以block回調到cell來,將數據賦給_formModel。并且當有了數據后UILabel的文本顏色也有變化。 

 

 

 

pickView的對象已經創建好,但是還未到彈出顯示的時機。所謂時機,就是當左右兩個框框被點擊后。

可以看到pickView是被添加在window上的。并且調用了pickView的接口方法showPickerView方法,讓其從屏幕底部彈出來。

  1. - (void)cityGestureHandle:(UITapGestureRecognizer *)tapGesture 
  2.  
  3.  
  4.     [_superView endEditing:YES]; 
  5.  
  6.     [self showPicker]; 
  7.  
  8.  
  9.   
  10.  
  11. - (void)areaGestureHandle:(UITapGestureRecognizer *)tapGesture 
  12.  
  13.  
  14.     [_superView endEditing:YES]; 
  15.  
  16.     [self showPicker]; 
  17.  
  18.  
  19.   
  20.  
  21. -(void)showPicker 
  22.  
  23.  
  24.     [[PubicClassMethod getCurrentWindow] addSubview:_pickView]; 
  25.  
  26.     [_pickView showPickerView]; 
  27.  
  28.  

前面代碼中給pickView設置數據源時,它的數據源有點特別,調用了ShopAddressModel的類方法cityAddressArr來返回有關地址的數據源數組。這是因為這里的地址數據雖然是從服務器接口請求的,但是一般情況不會改變,***是從服務器拿到數據后緩存在本地,當請求失敗或者無網絡時仍不受影響。

ShopAddressModel類定義了如下幾個屬性和方法。

  1. @interface ShopAddressModel : NSObject 
  2.  
  3.   
  4.  
  5. @property (nonatomic, copy)NSString            *addresssId; 
  6.  
  7. @property (nonatomic, copy)NSString            *name
  8.  
  9. @property (nonatomic, strong)NSArray        *subArr; 
  10.  
  11. #pragma mark - 地址緩存 
  12.  
  13. + (void)saveAddressArr:(NSArray *)addressArr; 
  14.  
  15. +(NSArray*)cityAddressArr; 
  16.  
  17. +(NSArray*)addressArr; 
  18.  
  19. #pragma mark - 解析 
  20.  
  21. + (ShopAddressModel *)addressModelFromDict:(NSDictionary *)dict; 
  22.  
  23. @end  

當我們我們從服務器拿到返回而來的地址數據后,調用saveAddressArr:方法,將數據緩存在本地。

  1. + (void)saveAddressArr:(NSArray *)addressArr 
  2.  
  3.  
  4.     if (addressArr && addressArr.count > 0) { 
  5.  
  6.         NSData *data = [NSKeyedArchiver archivedDataWithRootObject:addressArr]; 
  7.  
  8.         [[NSUserDefaults standardUserDefaults] setObject:data forKey:@"saveAddressArr"]; 
  9.  
  10.   
  11.  
  12.     }else 
  13.  
  14.     { 
  15.  
  16.         [[NSUserDefaults standardUserDefaults]setObject:nil forKey:@"saveAddressArr"]; 
  17.  
  18.     } 
  19.  
  20.   
  21.  
  22.     [[NSUserDefaults standardUserDefaults] synchronize]; 
  23.  
  24.  

當創建好pickView后以下面方法將本地緩存數據讀出,賦給items作為數據源。 

 

 

 

 

 

 

 

注意:這也是為什么把創建pickView的代碼放在了填充cell數據的refreshContent:formModel:里,而不在創建cell界面元素時一氣創建pickView。因為那樣當用戶***次打開這個界面,有可能數據來的比較慢,當代碼執行到賦數據源items時,本地還沒有被緩存上數據呢!這樣用戶***次進入這個界面時彈出的pickView是空的,沒有數據。而放在refreshContent:formModel:中是安全穩妥的原因是,每次從接口拿到數據后我們會刷新tableView,便會執行refreshContent:formModel:方法。它能保證先拿到數據,再設置數據源的順序。

提交表單時校驗數據:

在將表單數據提交前,要先校驗所填寫的表單是否有問題,該填的是否都填了,已填的數據格式是否是對的。若有問題,則要出現紅框和提示信息提醒用戶完善,等數據無誤后才可以提交給服務器。

數據校驗代碼很繁長,寫在控制器里不太好。因為它是對表單數據的校驗,那我們就寫在CreateShopFormModel里,這樣既可以給控制器瘦身,也可以降低耦合度,數據的歸數據,邏輯的歸邏輯。

從前面CreateShopFormModel.h的代碼里我們其實已經看到了這個校驗方法:submitCheck:。若某條CreateShopFormModel實例的數據不達要求,則在相應的CreateShopModel數據源對象的errText屬性賦值,意為提示信息。該方法的返回值類型為BOOL值,有數據不合格則返回NO。此時,在調用該方法的外部,應該將tableView重新加載,因為此時在該方法內部,已將數據格式不合格的提示信息賦值給了相應的數據源model。

  1. - (BOOL)submitCheck:(NSArray*)dataArr 
  2.  
  3.  
  4.     BOOL isSubmit=YES; 
  5.  
  6.   
  7.  
  8.     if(self.groupName.length==0){ 
  9.  
  10.         if (dataArr.count>0) { 
  11.  
  12.             CreateShopModel *cellObj=dataArr[0]; 
  13.  
  14.             cellObj.errText=@"網店名不能為空"
  15.  
  16.         } 
  17.  
  18.         isSubmit=NO
  19.  
  20.     } 
  21.  
  22.   
  23.  
  24.     if(self.groupName.length>0){ 
  25.  
  26.         if(dataArr.count>0){ 
  27.  
  28.             if(self.groupName.length>30){ 
  29.  
  30.                 CreateShopModel *cellObj=dataArr[0]; 
  31.  
  32.                 cellObj.errText=@"最多30個字"
  33.  
  34.                 isSubmit=NO
  35.  
  36.             } 
  37.  
  38.         } 
  39.  
  40.     } 
  41.  
  42.   
  43.  
  44.     if(self.tag.length==0){ 
  45.  
  46.         if (dataArr.count>1) { 
  47.  
  48.             CreateShopModel *cellObj=dataArr[1]; 
  49.  
  50.             cellObj.errText=@"標簽不能為空"
  51.  
  52.         } 
  53.  
  54.         isSubmit=NO
  55.  
  56.     } 
  57.  
  58.   
  59.  
  60.     if(self.introduction.length==0){ 
  61.  
  62.         if (dataArr.count>2) { 
  63.  
  64.             CreateShopModel *cellObj=dataArr[2]; 
  65.  
  66.             cellObj.errText=@"簡介不能為空"
  67.  
  68.         } 
  69.  
  70.         isSubmit=NO
  71.  
  72.     } 
  73.  
  74.   
  75.  
  76.     if(self.introduction.length>0){ 
  77.  
  78.         if(dataArr.count>2){ 
  79.  
  80.             if(self.introduction.length>30){ 
  81.  
  82.                 CreateShopModel *cellObj=dataArr[2]; 
  83.  
  84.                 cellObj.errText=@"最多500個字"
  85.  
  86.                 isSubmit=NO
  87.  
  88.             } 
  89.  
  90.         } 
  91.  
  92.     } 
  93.  
  94.   
  95.  
  96.     if(self.regionId.length==0){ 
  97.  
  98.         if (dataArr.count>3) { 
  99.  
  100.             CreateShopModel *cellObj=dataArr[3]; 
  101.  
  102.             cellObj.errText=@"市區不能為空"
  103.  
  104.         } 
  105.  
  106.         isSubmit=NO
  107.  
  108.     } 
  109.  
  110.   
  111.  
  112.     if(self.address.length==0){ 
  113.  
  114.         if (dataArr.count>4) { 
  115.  
  116.             CreateShopModel *cellObj=dataArr[4]; 
  117.  
  118.             cellObj.errText=@"地址不能為空"
  119.  
  120.         } 
  121.  
  122.         isSubmit=NO
  123.  
  124.     } 
  125.  
  126.   
  127.  
  128.     if(self.telephone.length==0){ 
  129.  
  130.         if (dataArr.count>5) { 
  131.  
  132.             CreateShopModel *cellObj=dataArr[5]; 
  133.  
  134.             cellObj.errText=@"電話不能為空"
  135.  
  136.         } 
  137.  
  138.         isSubmit=NO
  139.  
  140.     } 
  141.  
  142.   
  143.  
  144.   
  145.  
  146.     if (self.contactMail.length>0) { 
  147.  
  148.   
  149.  
  150.         if (dataArr.count>6) { 
  151.  
  152.             CreateShopModel *cellObj=dataArr[6]; 
  153.  
  154.             if(![PubicClassMethod isValidateEmail:self.contactMail]){ 
  155.  
  156.                 cellObj.errText=@"郵箱格式不合法"
  157.  
  158.                 isSubmit=NO
  159.  
  160.             } 
  161.  
  162.         } 
  163.  
  164.     } 
  165.  
  166.   
  167.  
  168.     if(self.logoUrl.length==0&&!self.logo){ 
  169.  
  170.         if (dataArr.count>7) { 
  171.  
  172.             CreateShopModel *cellObj=dataArr[7]; 
  173.  
  174.             cellObj.errText=@"logo不能為空"
  175.  
  176.         } 
  177.  
  178.         isSubmit=NO
  179.  
  180.     } 
  181.  
  182.   
  183.  
  184.     if(self.coverUrl.length==0&&!self.cover){ 
  185.  
  186.         if (dataArr.count>8) { 
  187.  
  188.             CreateShopModel *cellObj=dataArr[8]; 
  189.  
  190.             cellObj.errText=@"封面圖不能為空"
  191.  
  192.         } 
  193.  
  194.         isSubmit=NO
  195.  
  196.     } 
  197.  
  198.   
  199.  
  200.     return isSubmit; 
  201.  
  202.  

上傳圖片到七牛:

當點擊了“提交”按鈕后,先校驗數據,若所填寫的數據不合格,則給出提示信息,讓用戶繼續完善數據;若數據無問題,校驗通過,則開始提交表單。但是,這里有圖片,圖片我們是上傳到七牛服務器的,提交表單是圖片項提交的應該是圖片在七牛的一個url。這個邏輯我在以前的這篇筆記已經捋過了APP上傳圖片至七牛的邏輯梳理。

但是當時所有的邏輯都是寫在控制器里的。我們這個“創建網店”的控制器已經很龐大了,寫在控制器里不太好。所以在這里我將上傳圖片的邏輯拆分了出去,新建了一個類`QNUploadPicManager。只暴露一個允許傳入UIImage參數的接口方法,便可以通過successBlock來返回上傳到七牛成功后的url。以及通過failureBlock來返回上傳失敗后的error信息。而將所有的邏輯封裝在QNUploadPicManager內部,這樣控制器里便精簡了不少代碼,清爽了許多。

QNUploadPicManager.h 

  1. @interface QNUploadPicManager : NSObject  
  2.   
  3.  
  4. - (void)uploadImage:(UIImage *)image successBlock:(void(^)(NSString *urlStr))successBlock failureBlock:(void(^)(NSError *error))failureBlock;  
  5.   
  6.  
  7. @end  

QNUploadPicManager.m 

 

 

  

 

 

  

 

 

  

 

 

  

 

 

 

總結:

這個界面比較核心的一個問題就是:要在控制器里提交表單,那怎樣把在UITableViewCell里的textField輸入的數據傳遞給控制器? 另外一個問題是一個邏輯比較復雜的界面,控制器勢必會很龐大,應該有意的給控制器瘦身,不能把所有的邏輯都寫在控制器里。有關視圖顯示的就考慮放入UITableViewCell,有關數據的就考慮放入model。這樣既為控制器瘦身,也使代碼職責變清晰,耦合度降低。 

責任編輯:龐桂玉 來源: iOS大全
相關推薦

2018-01-11 15:31:39

命令Linux關機

2017-02-23 15:37:44

OptionObject容器

2016-05-09 10:41:03

算法分析開發

2025-02-24 08:20:00

AI代碼生成

2025-06-26 00:40:13

2017-07-10 13:09:45

前端Flexbox

2017-03-16 11:39:33

Openstack源碼姿勢

2023-01-30 07:41:43

2016-09-28 17:34:27

JavaScriptvueWeb

2021-07-12 11:35:13

Go協程Goroutine

2024-08-01 09:10:03

2020-08-31 06:54:37

注解脫敏ELK

2016-09-30 09:49:05

2021-01-08 08:10:34

MySQL表空間回收

2017-10-12 11:30:34

Spark代碼PR

2019-12-27 15:58:57

大數據IT互聯網

2024-09-25 08:22:06

2016-12-12 08:48:24

2021-09-15 16:20:02

Spring BootFilterJava

2019-10-30 17:06:50

AWS物聯網IoT
點贊
收藏

51CTO技術棧公眾號

国产综合中文字幕| 国产欧美日韩亚洲| 亚洲欧美精品aaaaaa片| 日韩视频在线直播| 欧美日韩在线免费| 伊人狠狠色丁香综合尤物| 亚洲高清视频网站| 日韩精品一卡二卡三卡四卡无卡| 久久久国产精品视频| 丰满岳乱妇一区二区| 电影一区二区| 亚洲永久免费av| 日韩欧美一区二区在线观看| av手机免费看| 久久久青草婷婷精品综合日韩| 久久精品国产亚洲一区二区| 亚洲国产无码精品| 国产在线不卡一区二区三区| 日韩欧美国产免费播放| 国产精品一二三在线观看| 欧洲毛片在线| 国产成人综合在线播放| 国产精品久久久久久五月尺| 日本少妇做爰全过程毛片| 日韩在线精品| 亚洲欧美日韩一区在线| 日本黄色一级网站| 香蕉视频亚洲一级| 五月婷婷另类国产| 中文字幕欧美日韩一区二区三区| 免费在线观看一级毛片| 国产精品一区在线| 国产在线精品播放| 中文在线a天堂| 午夜一级在线看亚洲| 欧美激情精品久久久| 无码黑人精品一区二区| 日韩精品欧美| 在线日韩欧美视频| 性少妇bbw张开| 亚洲精品推荐| 日韩精品小视频| 欧美xxxxx少妇| 91精品国产乱码久久久竹菊| 欧美猛男超大videosgay| 国产精品99久久免费黑人人妻| 国产乱妇乱子在线播视频播放网站| 国产精品不卡在线观看| 亚洲精品无人区| 粉嫩一区二区三区国产精品| 久久久久久久久久久黄色| 久久亚洲综合网| 日本午夜在线视频| 91免费观看国产| 欧美二区在线| 黄色在线播放| 中文字幕欧美区| 色之综合天天综合色天天棕色 | 美女视频在线免费| 疯狂做受xxxx高潮欧美日本| 六月激情综合网| 天天综合网站| 欧美日韩成人在线| 亚洲黄色av片| 91精品尤物| 亚洲精品v欧美精品v日韩精品| 人妻丰满熟妇av无码久久洗澡| 猫咪成人在线观看| 亚洲午夜色婷婷在线| 国产中文字幕久久| 欧美三区不卡| 97视频在线观看亚洲| 三级视频在线观看| 日韩激情av在线| 国产在线一区二区三区| 亚洲大尺度视频| 91在线porny国产在线看| 欧美日韩系列| 婷婷在线视频观看| 亚洲在线观看免费视频| 久久久久久人妻一区二区三区| 亚洲黄色网址| 欧美男女性生活在线直播观看| 樱花草www在线| 精品三级av在线导航| 亚洲嫩模很污视频| 国产盗摄一区二区三区在线| 亚洲小说欧美另类社区| 奇米4444一区二区三区| 91丨porny丨在线中文| 成人综合婷婷国产精品久久| 日本一区二区精品视频| h片在线免费| 色综合夜色一区| 一级片免费在线观看视频| 日韩欧美黄色| zzjj国产精品一区二区| 日韩不卡视频在线| 久久精品国产999大香线蕉| 国产伦精品一区二区三区高清| 极品白浆推特女神在线观看| 国产午夜亚洲精品理论片色戒 | 99久精品视频在线观看视频| 久久久久久久色| 中文字幕二区三区| 97精品视频在线观看自产线路二| 亚洲日本精品| 黄色在线观看www| 欧美高清激情brazzers| 久久精品成人av| 亚洲视屏一区| 国产主播精品在线| 久草福利在线| 亚洲成人一区二区在线观看| 手机av在线网| 免费视频一区三区| 国内精品久久久久久久| 国产女同91疯狂高潮互磨| 久久亚洲精华国产精华液| 91传媒免费视频| 国产亚洲人成a在线v网站| 亚洲精品一区二区三区四区高清| 波多野结衣欲乱| 先锋影音久久久| 国产精品免费一区二区三区在线观看 | 国产精品一区二区6| 国产精品99久| 国产成人精品免费看在线播放 | 亚洲伊人一本大道中文字幕| 免费人成在线观看网站| 狠狠色噜噜狠狠狠狠97| 国产a级片视频| 欧美日一区二区三区在线观看国产免| 国产日韩欧美91| 97视频在线观看网站| 一本在线高清不卡dvd| www.88av| 一本色道久久综合亚洲精品高清 | 夜夜春成人影院| 欧美精品久久久久| www.亚洲天堂.com| 一区二区三区在线免费播放| 992kp免费看片| 91tv官网精品成人亚洲| 国产精品稀缺呦系列在线| 高清av在线| 欧美专区日韩专区| 欧美人与性囗牲恔配| 久久亚洲风情| 日韩欧美一区二区视频在线播放| 澳门av一区二区三区| 亚洲网址你懂得| 狠狠躁夜夜躁人人爽视频| 欧美国产综合色视频| av在线无限看| 久久精品国产大片免费观看| 成人黄色短视频在线观看 | 欧美精品video| 国产 欧美 自拍| 五月天网站亚洲| aa一级黄色片| 日韩av一级电影| 一区二区不卡在线| 日本伊人久久| 性金发美女69hd大尺寸| 可以免费看污视频的网站在线| 日本乱人伦一区| 亚洲图片第一页| 国产一区二区在线影院| 国产免费裸体视频| 欧美人妖在线观看| 国产精品高潮呻吟视频| 精品美女在线观看视频在线观看 | 国产欧美日韩综合精品二区| 色是在线视频| 中文日韩在线视频| 99精品视频免费看| 天天综合网天天综合色| 国产1区2区在线观看| 国产一区不卡在线| 亚洲熟妇无码一区二区三区| 精品国精品国产自在久国产应用| 成人春色激情网| 98色花堂精品视频在线观看| 亚洲一区二区福利| 国产女人高潮的av毛片| 欧美日韩一二三四五区| 国产wwwwxxxx| 99久久99久久精品国产片果冻| 一区二区三区国产免费| 欧美天天在线| 日产精品久久久一区二区| 久久久精品区| 日韩av手机在线观看| 2024最新电影免费在线观看| 亚洲精品资源美女情侣酒店| 国产三级按摩推拿按摩| 日韩欧美在线一区| 午夜免费激情视频| 欧美激情一区二区三区| 中国xxxx性xxxx产国| 九一九一国产精品| 国产精品后入内射日本在线观看| 国产精品毛片久久| 欧美极品视频一区二区三区| 欧美高清hd| 国产精品美女免费看| 国产不卡人人| 不卡av在线播放| 福利小视频在线观看| 亚洲第一天堂无码专区| 国产婷婷在线视频| 欧美影视一区二区三区| 午夜影院在线看| 亚洲一区二区精品视频| 亚洲AV成人无码精电影在线| 久久众筹精品私拍模特| zjzjzjzjzj亚洲女人| 国产在线一区观看| 不卡的av中文字幕| 老牛影视一区二区三区| 黄色av网址在线播放| 国产一区视频在线观看免费| 自拍另类欧美| 日韩久久视频| 日本一区视频在线| 日韩有码av| 韩日午夜在线资源一区二区| 亚洲无线观看| 7777精品久久久大香线蕉小说| 亚洲a成人v| 国产精品嫩草视频| 美女网站视频一区| 国产www精品| 成人av免费电影网站| 97视频在线观看视频免费视频 | 亚洲专区一区二区三区| 国产日韩av网站| 韩日视频一区| 久久这里只有精品18| 好吊日精品视频| 性欧美18一19内谢| 五月天久久久| 亚洲av综合色区| 欧美另类女人| 激情五月婷婷六月| 国产在线不卡| 国产人妻777人伦精品hd| 激情综合激情| 久久久久久久久久久视频| 日韩视频一区二区三区在线播放免费观看 | 涩视频在线观看| 国产99久久久久| 成人免费看片载| proumb性欧美在线观看| 亚洲男人在线天堂| 91丨porny丨蝌蚪视频| 波多野结衣办公室33分钟| 久久婷婷国产综合国色天香| 亚洲性猛交xxxx乱大交| 国产精品久久夜| 唐朝av高清盛宴| 亚洲国产wwwccc36天堂| 天码人妻一区二区三区在线看| 色999日韩国产欧美一区二区| 天堂av免费在线观看| 欧美人与z0zoxxxx视频| 国产黄色免费大片| 亚洲精品美女免费| 国产乱视频在线观看| 精品国产一区二区三区在线观看| a级片国产精品自在拍在线播放| 欧美第一黄网免费网站| 一二三四视频在线中文| 国产精品高清网站| 日韩在线精品强乱中文字幕| 国产精品国模大尺度私拍| 蜜乳av综合| 日本免费在线视频观看| 尹人成人综合网| 欧美日韩亚洲自拍| 国产精品亚洲а∨天堂免在线| 51调教丨国产调教视频| 国产精品美女久久久久久久 | 色综合夜色一区| 国产三级小视频| 亚洲精品国产精品国产自| 国产三级视频在线| 欧美激情精品久久久久久免费印度| 三妻四妾完整版在线观看电视剧 | 91国产高清在线| 国产91在线播放精品| 成人黄动漫网站免费| 精品国产精品| 久久久久久久香蕉| 日本中文一区二区三区| 亚洲高清无码久久| 国产精品国产三级国产aⅴ入口 | 日韩欧美一区中文| 免费黄色在线视频网站| 欧美国产日本在线| 四虎影视国产精品| 欧美日韩亚洲在线| 亚洲性视频h| 91小视频在线播放| 久久天堂av综合合色蜜桃网| 青草草在线视频| 欧美日韩一卡二卡三卡 | 中文字幕精品无码亚| 亚洲大胆人体av| 国产在线二区| 国产精品99久久久久久人| 国产精品99久久免费观看| 中文字幕一区二区三区5566| 日韩一区精品视频| 日本少妇色视频| 亚洲成人免费电影| a级片免费视频| 日日狠狠久久偷偷四色综合免费| 自拍偷拍欧美视频| 国产一区二区中文字幕免费看| 香蕉视频官网在线观看日本一区二区| 北条麻妃视频在线| 久久蜜桃av一区精品变态类天堂| 国产一卡二卡在线| 日韩欧美一区在线| caoporm免费视频在线| 国产在线播放不卡| 久久美女精品| 黄色手机在线视频| 欧美极品美女视频| 久久久蜜桃一区二区| 亚洲欧美日韩直播| 成人免费网站视频| 久久综合久久久| 在线亚洲一区| 醉酒壮男gay强迫野外xx| 亚洲www啪成人一区二区麻豆| 亚洲国产精品视频在线| 久久91亚洲人成电影网站| 麻豆精品国产| 欧美一级爱爱视频| 粉嫩欧美一区二区三区高清影视| 国产精品九九九九九九| 日韩一区二区三区观看| 中文字幕在线播放网址| 97久草视频| 亚洲高清自拍| 水蜜桃av无码| 欧美性色xo影院| 美女欧美视频在线观看免费| 国产精品wwwwww| 日韩国产在线| ass极品水嫩小美女ass| 亚洲黄色尤物视频| 男人天堂网在线视频| 亚洲97在线观看| 欧美精品momsxxx| 少妇一级淫免费放| 亚洲欧美日韩在线不卡| 成人精品在线播放| 国产91精品久久久久久久| 国产精品密蕾丝视频下载| 亚洲欧美视频二区| 亚洲尤物视频在线| 色猫av在线| 国产日韩精品综合网站| 国产精品www994| 国产激情在线免费观看| 欧美少妇xxx| 丝袜综合欧美| 任我爽在线视频精品一| 九色porny丨国产精品| 精品少妇一二三区| 亚洲毛片在线免费观看| 色8久久影院午夜场| 蜜桃视频成人在线观看| 不卡视频一二三四| 亚洲 小说区 图片区| 欧美成人精品一区二区| 亚洲盗摄视频| 亚洲欧美日本一区二区三区| 亚洲成a人片在线不卡一二三区| 美州a亚洲一视本频v色道| 51国偷自产一区二区三区的来源 | 91中文字幕精品永久在线| 日本人妻一区二区三区| 在线观看日韩一区| 免费在线看污片| 色阁综合av| 成人av资源网站| 在线视频1卡二卡三卡| 欧美激情日韩图片| 日韩国产一区二区三区| 亚洲制服丝袜在线播放| 欧美日韩国产高清一区二区三区| 91制片在线观看| 欧美 另类 交| 国产视频在线观看一区二区三区| 国产成人精品无码高潮|