最近和另外一位同事負責公司登錄和用戶中心模塊的開發(fā)工作,開發(fā)周期計劃兩周,減去和產(chǎn)品和接口的協(xié)調(diào)時間,再減去由于原型圖和接口的問題,導(dǎo)致強迫癥糾結(jié)癥狀高發(fā),情緒不穩(wěn)定耗費的時間,能在兩周基本完成也算是個不小的奇跡了。本文就總結(jié)一下如何滿足產(chǎn)品需要的情況下,高效開發(fā)一個登錄注冊模塊。
1.利用繼承解決界面重復(fù)性功能。通常登錄注冊會有一個獨立的設(shè)計,而模塊內(nèi)部會有有相似的背景,相似的導(dǎo)航欄樣式,相似返回和退出行為,相似的輸入框,按鈕樣式等。
比如上面的的注冊和登錄模塊,就有相同的返回按鈕,相同的背景,相同的導(dǎo)航欄樣式,甚至相同的按鈕和輸入框樣式。所以為了加快我們的開發(fā),我們完全先定義一個父控制器,然后通過的繼承實現(xiàn)多態(tài),從而實現(xiàn)我們快速設(shè)計頁面和基本功能的實現(xiàn)。下圖是我的個人項目《丁丁印記》的登錄注冊模塊的目錄結(jié)構(gòu),其中hooentrybaseviewcontroller就定義了這個模塊通用的行為和樣式:
2.彈出鍵盤和退出鍵盤機制開發(fā)。
這點使我們開發(fā)者容易忽略的一點,我也因為看到一些app因為彈出鍵盤遮擋輸入,導(dǎo)致怒刪app的行為。這模塊的設(shè)計就根據(jù)產(chǎn)品的設(shè)計來決定采用什么代碼實現(xiàn)我們的目的了。
•單擊空白區(qū)域退出鍵盤代碼:
1
2
3
4
5
6
7
8
|
uitapgesturerecognizer *tap = [[uitapgesturerecognizer alloc] initwithtarget:self action:@selector(closekeyboard:)]; tap.numberoftapsrequired = 1; tap.numberoftouchesrequired = 1; [self.view addgesturerecognizer:tap]; - ( void )closekeyboard:(id)sender { [self.view endediting:yes]; } |
•避免鍵盤遮擋,登錄表單或按鈕上移代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
- ( void )textviewdidbeginediting:(uitextfield *)textview { cgrect frame = textview.frame; int offset = frame.origin.y + 120 - (self.view.frame.size.height - 216.0); //鍵盤高度216 nstimeinterval animationduration = 0.30f; [uiview beginanimations:@ "resizeforkeyboard" context:nil]; [uiview setanimationduration:animationduration]; float width = self.view.frame.size.width; float height = self.view.frame.size.height; if (offset > 0) { cgrect rect = cgrectmake(0.0f, -offset,width,height); self.view.frame = rect; } [uiview commitanimations]; } |
3.接入第三方登錄,必須要判斷用戶是否安裝該第三方客戶端,否則蘋果可能審核無法通過。血的教訓(xùn)。
比如我的app《丁丁印記》接入了qq登錄功能,程序會客戶端是否安裝了qq,如果未安裝則隱藏qq登錄圖標。
1
2
3
4
|
if (![qqapi isqqinstalled]) { self.qqloginbutton.hidden = yes; self.qqloginlabel.hidden = yes; } |
4.特殊情景處理。這容易是一個空白點,通常年輕的開發(fā)的者不會考慮到這一塊,而通常產(chǎn)品和ue也不太會記得定義清楚臨界點的行為。
• 加載狀態(tài)。當用戶發(fā)起登錄或者注冊請求時需要給用戶友好的提示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#pragma mark - 登錄按鈕點擊 - (ibaction)login:(uibutton *)sender { if ([self.usernametextfield.text isempty] || [self.passwordtextfield.text isempty]){ [svprogresshud showerrorwithstatus:@ "用戶名或密碼不能為空" ]; } else { __weak typeof(self) weakself = self; [[hoousermanager manager] loginwithusername:self.usernametextfield.text andpassword:self.passwordtextfield.text block:^(bmobuser *user, nserror *error) { __strong __typeof(weakself)strongself = weakself; if (error) { [svprogresshud showerrorwithstatus:@ "登錄失敗" ]; } else if (user){ [svprogresshud showsuccesswithstatus:@ "登錄成功" ]; [strongself loginsuccessdismiss]; } }]; } } |
• 賬號或者密碼各種錯誤判斷
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
nsstring *emailstr; nsstring *phonestr; nsstring *passwordstr = weakself.passwordview.inputtextfield.text; emailstr = weakself.accountview.inputtextfield.text; if (![nsstring validateemail:emailstr] || !emailstr.length) { [weakself showerrortipviewwithmessage:@ "郵箱格式錯誤" ]; return ; } } else { phonestr = weakself.accountview.inputtextfield.text; if (phonestr.length < 5) { [weakself showerrortipviewwithmessage:@ "手機長度錯誤" )]; return ; } if ([weakself.accountview.countrycode isequaltostring:@ "+86" ]) { if (![phonestr isvalidatemobilenumber]) { [weakself showerrortipviewwithmessage:@ "手機號碼格式錯誤" )]; return ; } } } if (passwordstr.length < kpasswordminlength) { [weakself showerrortipviewwithmessage:atlocalizedstring(@ "密碼長度超過少于6個字符" )]; return ; } if (passwordstr.length > kpasswordmaxlength) { [weakself showerrortipviewwithmessage:@ "密碼長度超過20個字符" )]; return ; } |
5.手機找回密碼,發(fā)送驗證碼按鈕的處理。這個行為也容易被產(chǎn)品忽略需要我們開發(fā)者主動想到,然后跟產(chǎn)品確定這個需求,然后確定按鈕的觸發(fā)后的行為,否則用戶可能多次點擊發(fā)送驗證碼,這會造成服務(wù)器負擔,并且可能返回給用戶多條短信,造成困擾。下面這段代碼可以實現(xiàn)單擊驗證碼按鈕,然后倒計時2分鐘后恢復(fù)按鈕的可點擊狀態(tài)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
- ( void )verifedcodebuttonwithtitle:(nsstring *)title andnewtitle:(nsstring *)newtitle { ws(weakself); __block int timeout = ktimeout; dispatch_queue_t queue = dispatch_get_global_queue(dispatch_queue_priority_default, 0); dispatch_source_t _timer = dispatch_source_create(dispatch_source_type_timer, 0, 0,queue); dispatch_source_set_timer(_timer,dispatch_walltime(null, 0),1.0*nsec_per_sec, 0); dispatch_source_set_event_handler(_timer, ^{ if (timeout<=0){ dispatch_source_cancel(_timer); dispatch_async(dispatch_get_main_queue(), ^{ [weakself settitle:title forstate:uicontrolstatenormal]; weakself.userinteractionenabled = yes; }); } else { int seconds = timeout; nsstring *strtime = [nsstring stringwithformat:@ "%.2d" , seconds]; dispatch_async(dispatch_get_main_queue(), ^{ [uiview beginanimations:nil context:nil]; [uiview setanimationduration:1]; [weakself settitle:[nsstring stringwithformat:@ "%@(%@)" ,newtitle,strtime] forstate:uicontrolstatenormal]; [uiview commitanimations]; weakself.userinteractionenabled = no; }); timeout--; } }); dispatch_resume(_timer); } |
5.用戶登錄信息和狀態(tài)持久化。我們通常會有業(yè)務(wù)層處理登錄的數(shù)據(jù)的持久,并且使用單例,但是不能依賴單例記錄用狀態(tài),因為用戶可能會退出,所以需要從沙盒去讀取用戶狀態(tài)的字段是否存在,如用戶的id,或者accesstoken。
下面這段代碼,用來持久化用戶信息
-
1
2
3
4
5
6
7
8
|
( void )saveuserinfowithdata:(nsdictionary *)dict { nsstring *userid = dict[kuserid]; nsstring *email = dict[kemail]; nsstring *mobile = dict[kmobile]; [hoonsuserdefaultserialzer setobject:memberid forkey:kuserid]; [hoonsuserdefaultserialzer setobject:email forkey:kemail]; [hoonsuserdefaultserialzer setobject:mobile forkey:kmobile]; } |
5.對外開發(fā)用戶信息的接口。封裝我們的模塊。對外提供我們的接口,通常其他頁面需要判斷用戶是否登錄,也可能需要用戶的唯一標示符來請求數(shù)據(jù)。這一塊如果我們做的混亂,則容易導(dǎo)致其他頁面獲取用戶信息的隨意性,比如給他們開發(fā)了讀取沙盒里讀取用戶信息的字段。我們應(yīng)該在登錄模塊統(tǒng)一其他頁面獲取這些用戶信息的行為。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
#import <foundation/foundation.h> #import "hoosingleton.h" @interface hoousermanager : nsobject @property (nonatomic, strong) nsstring *userid; singletonh(manager) /** * verify user if login or not * * @return if login in return yes ,otherwise return no */ - ( bool )isuserlogin; /** * login out */ - ( void )loginout; @end #import "hoousermanager.h" #import "hoonsuserdefaultserialzer.h" static nsstring * const kmobile = @ "mobile" ; static nsstring * const kemail = @ "email" ; static nsstring * const kuserid = @ "userid" ; @implementation hoousermanager singletonm(manager) #pragma mark - getter and setter - (nsstring *)userid { nsstring *userid = [hoonsuserdefaultserialzer objectforkey:kuserid]; return userid; } - ( bool )isuserlogin { nsstring *userid = [hoonsuserdefaultserialzer objectforkey:kuserid]; if (userid.length) { return yes; } return no; } - ( void )loginout { [hoonsuserdefaultserialzer removeobjectforkey:kmobile]; [hoonsuserdefaultserialzer removeobjectforkey:kemail]; [hoonsuserdefaultserialzer removeobjectforkey:kuseid]; } @end |
6.其他。
其實為了更好的用戶體驗,我們還會提供其他功能,如明文顯示密碼選擇按鈕、從服務(wù)器讀取郵箱格式提示、錯誤字符糾正、當然還有最重要的動畫效果。