iOS+PHP實現登錄功能

  近期在做app開發的時候,因爲要用到app登錄功能,就自己寫了個簡單的iOS+PHP實現登錄功能的demo,經過運行能夠通過登錄測試。

  在開發過程中,也是碰到了各種各樣的問題,經過不斷的調試和改變方法,終於將所有的坑都基本上填滿了,因此,將最終完整版的代碼及相關流程記錄在此,供自己及其它需要的人查閱使用。

一、一些約定條件

  Mac OS真的是一個太太太封閉的系統環境了,封閉到我已經測試了N中辦法,都沒辦法成功搭建後臺服務器——不管是使用集成軟件(如MAMP或者XAMPP),還是自行下載MySQL和MyAdmin客戶端安裝。有的時候Apache無法正常啓動,有時候MySQL又故障掉了,更悲哀的是,真機測試時,客戶端上輸入內容後,無法正常與服務器通信!逼不得已,就只能放棄了,最終採用Windows的WIN7系統的電腦做後臺服務器,然後與測試用的手機、編程用的Mac電腦處於同一無線局域網下。==如果哪位同仁能告知如何在MacBook上搭建後臺服務器且能正常工作,歡迎不吝賜教,鄙人萬分感激!==

  當在裝有WIN 7系統的電腦上配置服務器時,我使用的是WAMP集成軟件,數據庫和表的編輯操作使用的是SQLyog軟件,這樣可以有效的創建、修改表的內容。==注意,在WIN7的電腦上搭建完後臺並創建好數據庫之後,還需要進行局域網的配置工作,這樣才能讓處於同一局域網下的設備(如手機)連接到這臺電腦及後臺==。這個方法我也忘了,所以需要您和其他做PHP後臺開發的同仁諮詢。==如果您已經知道怎麼做了,也歡迎不吝賜教,我好記錄在本文章中,供更多人的來學習==。

  一些約定條件如下

  • [x] 手機客戶端能成功連接後臺服務器,並與後臺服務器進行數據交互
  • [x] 密碼爲原始輸入的字符串,不經過MD5等加密方式的加密(正常開發時,請務必進行加密處理)
  • [x] 傳輸方式選擇GET傳輸(爲了安全起見,最好使用POST傳輸方式)
  • [x] 登錄賬號只選擇手機號(正常開發時,登錄的賬號可能還有email或者用戶名)

二、數據庫和表的創建及初始化

  使用SQLyog或者phpMyAdmin創建一個名爲testAppDatabase的數據庫,“基字符集”選擇“utf8”,“數據庫排序規則”選擇“utf8_general_ci”,如下圖所示(圖像截取的是使用SQLyog軟件創建數據庫的情況,使用phpMyAdmin類似):
使用SQLyog創建新數據庫.PNG

  然後,在testAppDatabase數據庫下,新建一個名爲userInformationTable的表,“引擎”選擇“InnoDB”,“字符集”選擇“utf8”,“覈對”選擇“utf8_general_ci”,最後創建列名及每一列對應的數據類型以及是否可以爲空等,並設置userID爲主鍵、正數、自增,如下圖所示(圖像截取的是使用SQLyog軟件創建表的情況,使用phpMyAdmin類似):
使用SQLyog創建新表.PNG

  正常情況下,每一列都最好設置爲“非空”,如果用戶沒有輸入,那麼可以默認使用“N/A”等填充,等用戶輸入了當前列對應的內容了,再替換掉“N/A”即可。

  因爲我們是做登錄模塊的驗證,沒有經過註冊,因此,數據庫中是沒有信息的。我們可以手動先填寫一些信息,供測試使用。填寫好的內容如下圖所示(使用的phpMyAdmin客戶端插入的數據)
插入數據.PNG

  ==注意,此時的密碼是完全的明文密碼,未進行任何加密,這主要是爲了測試方便使用,正常開發時,請務必將保存到數據庫中的密碼進行加密處理。==

  至此,數據庫相關的“配置”就處理完了,下面是php代碼相關的內容。

三、php代碼

  在php代碼中,我們主要完成的是接收客戶端傳輸過來的數據,並將數據與數據庫進行匹配驗證,一般驗證的內容有兩點:

  • [x] 用戶輸入的賬號是否存在
  • [x] 用戶輸入的賬號存在的情況下,賬號和密碼是否與數據庫中的一一匹配

  因此,我們的php代碼主要就是圍繞這兩個邏輯來編寫。

  1. 首先,編寫數據庫連接代碼,並保存到其它用戶讀取不到的位置。

  對php有一些瞭解的人應該知道,保存在htddoc路徑(對於使用WAMP集成的環境來說,就是www文件夾下,如下圖)下的文件,是可以被瀏覽器通過輸入網址的方式讀取到的,如果將登錄數據庫使用的賬戶和密碼信息放到這個文件夾下,那麼數據庫是非常不安全的。
www目錄.png

  因此,我們通常將連接數據庫需要的php代碼單獨編寫並保存爲“.php”格式的文件,然後將這個文件放置在與“www”同級的位置,如下圖所示的“connectionToDB.php”文件。
連接數據庫文件存放位置.PNG

  使用php編輯器編輯“connectionToDB.php”文件,寫入的代碼如下:

connectionToDB.php

<?php

    $dbc = mysqli_connect('192.168.1.101', 'root', '你設置的登錄數據庫的密碼', 'testAppDatabase') or die("連接失敗:".mysql_error());  

  //連接數據庫的格式通常爲
    //$dbc = mysqli_connect(hostname, 登錄賬號, 登錄密碼, 數據庫的名稱) or die("連接失敗:".mysql_error());  
    //hostname:一般是localhost,也常設置爲作爲後臺的電腦的IP地址,查詢的方法是“運行->cmd->ipconfig /all”,在控制檯中找到IPv4地址。
    //對於局域網,這個IP地址可能會不斷的變化,因此,如果沒有做IP固化的操作,每次使用後臺服務器時,最好都加納差一下這個IP地址,然後將“connectionToDB.php”中的IP地址換爲正在使用的地址
    //登錄賬號:一般是根用戶root。如果不使用根用戶,就使用數據庫擁有者爲你開闢的用戶名和密碼
    //登錄密碼:對應登錄賬號的密碼
    //數據庫名稱:要連接的數據庫的名稱。一般一個產品只有一個數據庫,該數據庫中有很多的表  
?>

  ==注意:php代碼的編寫,一定要使用utf-8的編碼格式,這點要切記。下面提到的php文件均採用這種編碼格式,將不再贅述。==

  1. 接着,編寫和登錄驗證相關的php代碼,將其保存爲“login.php”文件並保存到www目錄下,如下圖所示:

登錄的php文件路徑.png

  “www”目錄就想到於在瀏覽器中輸入的localhost或者192.168.1.101這個IP地址,所以能看到,我們要編寫的“login.php”在下兩級目錄下,知道這點這對於我們編寫“login.php”文件中的某些代碼是有必要的。

login.php

<?php

    header('Content-type:text/html;charset=utf-8');  //代碼的方式設置編碼方式

    require_once('../../../connectionToDB.php');  
    //一個"../"代表一級目錄,
    //因爲我們的“connectionToDB.php”文件與“www”文件夾在同一級目錄下
    //從“login.php”追溯“connectionToDB.php”需要進過三級目錄,所以需要三個"../"

    $postedData = $_REQUEST;  //$_REQUEST既可以獲取到通過POST方式傳輸的數據,也可以獲取到通過GET方式傳輸的數據

    //獲取用戶輸入的賬號的形式:手機號、郵箱地址還是一般用戶名
    $userAccountType = $postedData['Account_Type'];

    //獲取用戶輸入的賬號和密碼  
    $userAccount = $postedData['User_Account'];

    $userPassword = $postedData['User_Password'];

    //根據賬戶形式獲取對應的賬號內容,用於後面的比對

    //是否賬號是否存在的標籤以及是否登錄成功的標籤
    $accountBeingOrNotFlag = "0";  //0代表賬號不存在,1代表賬號存在

    $loginOKOrNotFlag = "0";  //0代表登錄失敗,1代表登錄成功

    switch ($userAccountType) {

        case "Telephone":  //賬號是手機號

            $q = "SELECT * FROM userinformationtable WHERE UserTelephoneNumber = $userAccount";  //查詢數據庫有沒有這個手機號

            $r = @mysqli_query($dbc, $q);

            $rows = @mysqli_num_rows($r);  //查詢到的信息的行數,如果行數不是0,說明查詢到了信息

            if($rows) {

                //行數不是0,說明有這個手機號,設置標籤的值爲1
                $accountBeingOrNotFlag = "1";  //賬號存在

                //查詢賬號和密碼是否匹配
                $qA = "SELECT * FROM userinformationtable WHERE UserTelephoneNumber = '$userAccount' and UserPassword = '$userPassword'";

                $rA = @mysqli_query($dbc, $qA);

                $rowsA = @mysqli_num_rows($rA);

                if($rowsA) {

                    //行數不是0,說明賬號和密碼匹配,設置標籤值爲1,登錄成功
                    $loginOKOrNotFlag = "1";  

                }else {

                    //行數是0,說明賬號和密碼不匹配,設置標籤值爲0,登錄失敗
                    $loginOKOrNotFlag = "0";  

                }

            }else {

                //行數是0,說明賬號不存在,設置標籤值爲0
                $accountBeingOrNotFlag = "0";  

            }

            //將標籤值保存到數組中,然後將其傳遞給客戶端,客戶端根據標籤值判斷對應的操作邏輯
            $returnArr = array("accountBeingOrNotFlag" => $accountBeingOrNotFlag, "loginOKOrNotFlag" => $loginOKOrNotFlag);

            //下面的兩行代碼是方便測試使用,即將我們測試的一些內容保存到一個.log文件中,然後通過查看這個文件,看結果是否是我們想要的
            $dccc = print_r($returnArr, true);
            file_put_contents('C://Users/Administrator/Desktop/zj.log', $dccc);

            //關閉數據庫連接
            mysqli_close($dbc);

            //將要傳遞給客戶端的結果信息通過json編碼的形式輸出
            echo json_encode($returnArr);  

            break;

            //下面的代碼註釋和上面的這個case裏面的類似,不再贅述
        case "EmailAddress":

            $q = "SELECT * FROM userinformationtable WHERE UserEmailAddress = $userAccount";

            $r = @mysqli_query($dbc, $q);

            @$rows = mysql_num_rows($r);

            if($rows) {

                $accountBeingOrNotFlag = "1";  //賬號存在

                $qA = "SELECT * FROM userinformationtable WHERE UserEmailAddress = '$userAccount' and UserPassword = '$userPassword'";

                //$qA = "SELECT * FROM userinformationtable WHERE UserTelephoneNumber = 13240132824 and UserPassword = l19880226";

                $rA = @mysqli_query($dbc, $qA);

                $rowsA = @mysqli_num_rows($rA);

                if($rowsA) {

                    $loginOKOrNotFlag = "1";  //登錄成功

                }else {

                    $loginOKOrNotFlag = "0";  //登錄失敗

                }

            }else {

                $accountBeingOrNotFlag = "0";  //賬號不存在
            }

            $returnArr = array("accountBeingOrNotFlag" => $accountBeingOrNotFlag, "loginOKOrNotFlag" => $loginOKOrNotFlag);

            mysqli_close($dbc);

            echo json_encode($returnArr);  //輸出json格式

            break;

        case "NormalName":

            $q = "SELECT * FROM userinformationtable WHERE UserNormalName = $userAccount";

            $r = @mysqli_query($dbc, $q);

            @$rows = mysql_num_rows($r);

            if($rows) {

                $accountBeingOrNotFlag = "1";  //賬號存在

                $qA = "SELECT * FROM userinformationtable WHERE UserNormalName = '$userAccount' and UserPassword = '$userPassword'";

                $rA = @mysqli_query($dbc, $qA);

                $rowsA = @mysqli_num_rows($rA);

                if($rowsA) {

                    $loginOKOrNotFlag = "1";  //登錄成功                

                }else {

                    $loginOKOrNotFlag = "0";  //登錄失敗
                }

            }else {

                $accountBeingOrNotFlag = "0";  //賬號不存在
            }

            $returnArr = array("accountBeingOrNotFlag" => $accountBeingOrNotFlag, "loginOKOrNotFlag" => $loginOKOrNotFlag);

            mysqli_close($dbc);

            echo json_encode($returnArr);  //輸出json格式

            break;

    }

    ?>

  好了,和登錄有關的php代碼已經編寫完成了,下面就開始編寫iOS客戶端的代碼。

四、iOS客戶端

  iOS客戶端的代碼,我們將採用MVC的架構來編寫。

  可能有人會問,只是一個demo,爲什麼不將M也合併到V中一起寫呢?這個就和我在文章開頭提到的坑有關了。

  我們先來看一個將MVC寫在一個viewController中的例子

  1. 將MVC寫在一個viewController中的例子

  我們隨便新建一個基於單視圖的工程,然後在ViewController.m文件中編寫如下代碼:

ViewController.m的viewDidLoad方法中

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    NSURL *url = [NSURL URLWithString:@"http://192.168.1.101/testApp/Login/login.php"];

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];

    //設置請求方式 POST
    request.HTTPMethod = @"POST";

    //設置請求的超時時間
    request.timeoutInterval = 60;

    request.HTTPBody = [[NSString stringWithFormat:@"User_Account=%@&User_Password=%@&Account_Type=%@",@"13542138562",@"testApp123456", @"Telephone"] dataUsingEncoding:NSUTF8StringEncoding];

    NSURLSession *session = [NSURLSession sharedSession];

    //4 創建網絡任務 NSURLSessionTask
    //通過網絡會話 來創建數據任務
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

        NSLog(@"網絡請求完成");

        NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

        NSLog(@"data = %@", data);

        NSLog(@"result = %@", result);

        _str = result;

        NSLog(@"1.2_str = %@", _str);

//        dispatch_async(dispatch_get_main_queue(), ^{
//
//            // do something
//
//
//            _str = result;
//
//            NSLog(@"1.2_str = %@", _str);
//
//
//        });

    }];
    //5 發起網絡任務

    [dataTask resume];

    NSLog(@"_str = %@", _str);
}

  這段代碼本來是想完成的工作是:將登陸的信息傳遞給後臺之後,後臺進行驗證,並將驗證的結果(沒有賬號、賬號密碼不匹配、賬號密碼匹配)傳回給客戶端,然後由客戶端根據返回回來的標籤值做響應的操作。但是運行這段代碼之後,通過斷點調試,會發現,dataTaskWithRequest:completionHandler:並沒有按照順序執行,而是直接跳過,然後執行了[dataTask resume];方法,接着就是NSLog函數輸出_str的值,會發現值是空的。當viewDidLoad代碼塊全部執行完畢後(即執行到最後一個右大括號}),纔會執行dataTaskWithRequest:completionHandler:代碼塊中的內容。雖然此後會更新_str的值,但此時其實客戶端已經接收了第一次的_str的值了,如果不做其它的工作,我們是很難得到想要的結果了。

  後來經過多次的調試、驗證,最終才發現,使用通知可以解決這個問題。這也就是爲啥我要把M單獨寫的原因:我們可以在M裏面發送通知,然後在view裏面註冊通知和實現通知的方法。

  我們分別創建一個繼承於NSObject的RegisterAndLoginModel文件,一個繼承於UIViewController的LoginViewController文件,以及一個繼承於UIView的LoginView文件。

  1. 編寫RegisterAndLoginModel文件

RegisterAndLoginModel.h

#import <Foundation/Foundation.h>

@interface RegisterAndLoginModel : NSObject

- (void)checkTheUserAccount : (NSString*)userAccount andPassword : (NSString*)userPassword withAccountType : (NSString*)accountType;

@end

RegisterAndLoginModel.m

#import "RegisterAndLoginModel.h"

@implementation RegisterAndLoginModel

//GET方式提交數據
- (void)checkTheUserAccount : (NSString*)userAccount andPassword : (NSString*)userPassword withAccountType : (NSString*)accountType {

    NSMutableDictionary *returnDictionary = [[NSMutableDictionary alloc]initWithCapacity:2];

    NSLog(@"userAccount = %@, userPassword = %@, accountType = %@", userAccount, userPassword, accountType);

    //1.構造URL網絡地址
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://192.168.1.101/testApp/Login/login.php?User_Account=%@&User_Password=%@&Account_Type=%@",userAccount,userPassword, accountType]];

    //2.構造網絡請求對象  NSURLRequest
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];

    NSLog(@"request = %@", url);

    //3.設置請求方式 GET
    request.HTTPMethod = @"GET";
    //設置請求的超時時間
    request.timeoutInterval = 60;

    NSURLSession *session = [NSURLSession sharedSession];

    //4 創建網絡任務 NSURLSessionTask。通過網絡會話 來創建數據任務
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

        NSLog(@"網絡請求完成");

        NSDictionary *jsonDic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&error];

        NSLog(@"接收到的數據爲%@",jsonDic);

        [returnDictionary setObject:jsonDic forKey:@"returnDictionaryKey"];

        [[NSNotificationCenter defaultCenter] postNotificationName:@"loginStatusInformationDictionary" object:returnDictionary];

    }];

    //5.發起網絡任務
    [dataTask resume];
}
  1. 編寫LoginViewController文件

LoginViewController.h

#import <UIKit/UIKit.h>
#import "LoginView.h"
#import "RegisterAndLoginModel.h"

@protocol LoginViewControllerDelegate <NSObject>

@optional

- (void)goToRegisterViewController;

- (void)loginSucceed;

@end

@interface LoginViewController : UIViewController<LoginViewDelegate>
{

    NSString *accountTypeString;
}

@property (assign, nonatomic) id<LoginViewControllerDelegate>loginViewControllerDelegate;
@property (strong, nonatomic) RegisterAndLoginModel *registerAndLoginModel;
@property (strong, nonatomic) LoginView *loginView;

@end

LoginViewController.m

#import "LoginViewController.h"

@interface LoginViewController ()

@end

@implementation LoginViewController

int accountIsNotNULL = 0;  //賬號是否爲空
int loginPasswordIsOK = 0;  //密碼格式是否正確
int loginBtnPressedNumbers = 0;  //登錄按鈕累計點擊次數

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    //添加通知,監測後臺服務器返回的標籤值
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getTheLoginStatusDiecitonary:) name:@"loginStatusInformationDictionary" object:nil];

    _loginView = [[LoginView alloc]initTheLoginViewWithFrame:CGRectMake(0, 0, deviceScreenWidth, deviceScreenHeight)];

    _loginView.loginViewDelegate = self;

    [_loginView.goToRegisterButton addTarget:self action:@selector(goToRegisterButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
    [_loginView.findPasswordButton addTarget:self action:@selector(findPasswordButtonPressed:) forControlEvents:UIControlEventTouchUpInside];

    [self.view addSubview:_loginView];

    _registerAndLoginModel = [[RegisterAndLoginModel alloc]init];

}

- (void)loginButtonPressed : (UIButton*)sender {

    NSLog(@"點擊了登錄");

    NSLog(@"loginBtnPressedNumbers = %i", loginBtnPressedNumbers);

    //首先判斷用戶輸入的賬號的類型

    if(![self checkPhoneNumInputWithString:_loginView.loginAccountTextField.text]) {

        //不是手機號
        if(![self isEmailAddress:_loginView.loginAccountTextField.text]) {

            //也不是郵箱地址
            accountTypeString = @"NormalName";

        }else {

            //是郵箱地址
            accountTypeString = @"EmailAddress";
        }

    }else {

        //是手機號

        accountTypeString = @"Telephone";

    }

    [_registerAndLoginModel checkTheUserAccount:_loginView.loginAccountTextField.text andPassword:_loginView.loginPasswordTextField.text withAccountType:accountTypeString];

    loginBtnPressedNumbers += 1;

}

- (void)goToRegisterButtonPressed : (UIButton*)sender {

    NSLog(@"去註冊");

    [_loginViewControllerDelegate goToRegisterViewController];

}

- (void)findPasswordButtonPressed : (UIButton*)sender {

    NSLog(@"找回密碼");
}

#pragma mark - 實現LoginViewDelegate中的方法

- (void)getTheInputStringInLoginViewFromTheTextField : (NSString*)inputString withTextFieldTag : (NSInteger)tag {

    if(tag == 20001) {

        if (inputString.length > 0) {

            accountIsNotNULL = 1;

        }else {

            accountIsNotNULL = 0;

        }

    }else {

        if((inputString.length >= 8) && (inputString.length <= 20)) {

            loginPasswordIsOK = 1;

        }else {

            loginPasswordIsOK = 0;
        }
    }

    if(accountIsNotNULL == 1 && loginPasswordIsOK == 1) {

        [_loginView.loginButton addTarget:self action:@selector(loginButtonPressed:) forControlEvents:UIControlEventTouchUpInside];

        _loginView.loginButton.alpha = 1.0;

        [_loginView.loginButton setBackgroundColor:[UIColor redColor]];

        [_loginView.loginButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];

        [_loginView.loginButton setUserInteractionEnabled:YES];

    }else {

        [_loginView.loginButton setBackgroundColor:[UIColor colorWithRed:211/255.0 green:211/255.0 blue:211/255.0 alpha:1.0]];

        [_loginView.loginButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];

        [_loginView.loginButton setUserInteractionEnabled:NO];

    }

}

#pragma mark - 使用正則表達式判斷手機號格式是否正確

-(BOOL)checkPhoneNumInputWithString : (NSString*)telephoneString {

    NSString * MOBILE = @"^1(3[0-9]|5[0-35-9]|8[025-9])\\d{8}$";

    NSString * CM = @"^1(34[0-8]|(3[5-9]|5[017-9]|8[278])\\d)\\d{7}$";

    NSString * CU = @"^1(3[0-2]|5[256]|8[56])\\d{8}$";

    NSString * CT = @"^1((33|53|8[09])[0-9]|349)\\d{7}$";

    // NSString * PHS = @"^0(10|2[0-5789]|\\d{3})\\d{7,8}$";

    NSPredicate *regextestmobile = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", MOBILE];
    NSPredicate *regextestcm = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", CM];
    NSPredicate *regextestcu = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", CU];
    NSPredicate *regextestct = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", CT];
    BOOL res1 = [regextestmobile evaluateWithObject:telephoneString];
    BOOL res2 = [regextestcm evaluateWithObject:telephoneString];
    BOOL res3 = [regextestcu evaluateWithObject:telephoneString];
    BOOL res4 = [regextestct evaluateWithObject:telephoneString];

    if (res1 || res2 || res3 || res4 ) {

        return YES;

    }else {

        return NO;
    }
}

#pragma mark - 正則表達式判斷郵箱格式是否正確

- (BOOL)isEmailAddress:(NSString*)inputEmailAddress
{
    NSString* emailRegex = @"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}";
    NSPredicate* emailTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", emailRegex];
    return [emailTest evaluateWithObject:inputEmailAddress];

}

#pragma mark 實現通知方法

- (void)getTheLoginStatusDiecitonary :(NSNotification*) notification {

    NSMutableDictionary *resultDictionary = [notification object];

    NSLog(@"resultDictionary = %@", resultDictionary);

    NSDictionary *judgmentDictionary = [resultDictionary objectForKey:@"returnDictionaryKey"];

    NSLog(@"judgmentDictionary = %@", judgmentDictionary);

    if([[judgmentDictionary objectForKey:@"accountBeingOrNotFlag"] isEqualToString:@"0"]) {

        //賬號不存在,提示用戶是否去註冊

        NSLog(@"對不起,賬號不存在");

        //此處的操作一定要回到主線程操作,否則程序會崩潰,警告框彈不出來
        dispatch_async(dispatch_get_main_queue(), ^{

            // do something

            UIAlertController *accountNotBeingAlert = [UIAlertController alertControllerWithTitle:@"賬號不存在" message:@"對不起,您輸入的賬號不存在,是否前去註冊?" preferredStyle:UIAlertControllerStyleAlert];

            UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];

            UIAlertAction *goToRegisterAction = [UIAlertAction actionWithTitle:@"去註冊" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * action) {

                //進入註冊界面

                [_loginViewControllerDelegate goToRegisterViewController];

            }];

            [accountNotBeingAlert addAction:cancelAction];
            [accountNotBeingAlert addAction:goToRegisterAction];

            [self presentViewController:accountNotBeingAlert animated:YES completion:nil];

        });

    }else {

        //賬號存在

        //判斷賬號和密碼是否匹配

        if([[judgmentDictionary objectForKey:@"loginOKOrNotFlag"] isEqualToString:@"0"]) {

            //賬號和密碼不匹配

            NSLog(@"賬號和密碼不匹配,請重新輸入");

            if(loginBtnPressedNumbers > 2) {

                if([accountTypeString isEqualToString:@"Telephone"]) {

                    //用戶輸入的賬號是手機號,顯示獲取短信驗證碼
                    //短信驗證碼一段時間內只能獲取三次,如果超過三次,那麼顯示圖形驗證碼

                    //更新界面元素的時候,也需要回到主線程,否則程序就崩潰或者界面UI更新錯位
                    dispatch_async(dispatch_get_main_queue(), ^{

                        // do something

                        _loginView.verificationCodeTextField.hidden = NO;

                        _loginView.loginButton.frame = CGRectMake(20, 345, deviceScreenWidth - 40, 50);

                        _loginView.goToRegisterButton.frame = CGRectMake(20, 405, deviceScreenWidth /2 - 20, 25);

                        _loginView.findPasswordButton.frame = CGRectMake(deviceScreenWidth / 2, 405, deviceScreenWidth /2 - 20, 25);

                    });

                }else {

                    //賬號是郵箱地址或者一般用戶名,顯示圖形驗證碼
                }

            }

        }else {

            //賬號和密碼匹配,登錄成功

            //登錄成功後將登錄狀態信息保存到NSUserDefaults中,供程序調用

            NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
            [defaults setObject:_loginView.loginAccountTextField.text forKey:@"accountStr"];
            [defaults setObject:_loginView.loginPasswordTextField.text forKey:@"passwordStr"];
            [defaults setObject:@"isLogin" forKey:@"isLoginStr"];
            [defaults synchronize];

            [_loginViewControllerDelegate loginSucceed];
        }

    }

}

@end
  1. 編寫LoginView文件

LoginView.h

#import <UIKit/UIKit.h>

@protocol LoginViewDelegate <NSObject>

- (void)getTheInputStringInLoginViewFromTheTextField : (NSString*)inputString withTextFieldTag : (NSInteger)tag;

@end

@interface LoginView : UIView

@property (assign, nonatomic) id<LoginViewDelegate>loginViewDelegate;

@property (strong, nonatomic) UITextField *loginAccountTextField;
@property (strong, nonatomic) UITextField *loginPasswordTextField;
@property (strong, nonatomic) UITextField *verificationCodeTextField;

@property (strong, nonatomic) UIButton *getVerificationCodeButton;

@property (strong, nonatomic) UIButton *loginButton;

@property (strong, nonatomic) UIButton *goToRegisterButton;

@property (strong, nonatomic) UIButton *findPasswordButton;

- (id)initTheLoginViewWithFrame : (CGRect)frame;

@end

LoginView.m

#import "LoginView.h"

#import "UIImage+CircleImageView.h"  //圓形頭像

@implementation LoginView

- (id)initTheLoginViewWithFrame : (CGRect)frame {

    self = [super initWithFrame:frame];

    if(self) {

        //賬號輸入框
        _loginAccountTextField = [[UITextField alloc]initWithFrame:CGRectMake(20, 120, deviceScreenWidth - 40, 55)];
        [_loginAccountTextField setClearButtonMode:UITextFieldViewModeWhileEditing];
        _loginAccountTextField.placeholder = @"用戶名/郵箱地址/手機號";
        _loginAccountTextField.keyboardType = UIKeyboardTypeDefault;
        _loginAccountTextField.borderStyle = UITextBorderStyleRoundedRect;
        _loginAccountTextField.tag = 20001;
        [self addSubview:_loginAccountTextField];

        UIImageView *accountTextFieldLeftImageView = [[UIImageView alloc]initWithFrame:CGRectMake(_loginAccountTextField.frame.origin.x + 30, _loginAccountTextField.frame.origin.y +5 , 45, 45)];
        accountTextFieldLeftImageView.image = [UIImage imageNamed:@"Account"];

        _loginAccountTextField.leftView = accountTextFieldLeftImageView;
        _loginAccountTextField.leftViewMode = UITextFieldViewModeAlways;

        //密碼輸入框
        _loginPasswordTextField = [[UITextField alloc]initWithFrame:CGRectMake(20, 190, deviceScreenWidth - 40, 55)];
        [_loginPasswordTextField setClearButtonMode:UITextFieldViewModeWhileEditing];
        _loginPasswordTextField.placeholder = @"輸入8~20位字符的密碼";
        _loginPasswordTextField.keyboardType = UIKeyboardTypeDefault;
        _loginPasswordTextField.borderStyle = UITextBorderStyleRoundedRect;
        _loginPasswordTextField.secureTextEntry = YES;
        _loginPasswordTextField.tag = 20002;
        [self addSubview:_loginPasswordTextField];

        UIImageView *passwordTextFieldLeftImageView = [[UIImageView alloc]initWithFrame:CGRectMake(_loginPasswordTextField.frame.origin.x + 10, _loginPasswordTextField.frame.origin.y + 5, 40, 40)];
        passwordTextFieldLeftImageView.image = [UIImage imageNamed:@"password"];

        _loginPasswordTextField.leftView = passwordTextFieldLeftImageView;
        _loginPasswordTextField.leftViewMode = UITextFieldViewModeAlways;

        //驗證碼輸入框
        _verificationCodeTextField = [[UITextField alloc]initWithFrame:CGRectMake(20, 260, deviceScreenWidth - 40, 55)];
        [_verificationCodeTextField setClearButtonMode:UITextFieldViewModeWhileEditing];
        _verificationCodeTextField.placeholder = @"輸入4位短信驗證碼";
        _verificationCodeTextField.keyboardType = UIKeyboardTypeDefault;
        _verificationCodeTextField.borderStyle = UITextBorderStyleRoundedRect;
        _verificationCodeTextField.tag = 20003;
        _verificationCodeTextField.hidden = YES;
        [self addSubview:_verificationCodeTextField];

        UIImageView *verificationCodeFieldLeftImageView = [[UIImageView alloc]initWithFrame:CGRectMake(_verificationCodeTextField.frame.origin.x + 30, _verificationCodeTextField.frame.origin.y +5 , 45, 45)];
        verificationCodeFieldLeftImageView.image = [UIImage imageNamed:@"Account"];

        _verificationCodeTextField.leftView = verificationCodeFieldLeftImageView;
        _verificationCodeTextField.leftViewMode = UITextFieldViewModeAlways;

        //登錄按鈕
        _loginButton = [UIButton buttonWithType:UIButtonTypeCustom];
        _loginButton.frame = CGRectMake(20, 275, deviceScreenWidth - 40, 50);
        _loginButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
        _loginButton.titleLabel.font = [UIFont systemFontOfSize:17.0];
        [_loginButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        [_loginButton setTitle:NSLocalizedString(@"LoginButtonName", nil) forState:UIControlStateNormal];
        [_loginButton setBackgroundColor:[UIColor colorWithRed:211/255.0 green:211/255.0 blue:211/255.0 alpha:1.0]];
        [_loginButton setUserInteractionEnabled:NO];
        [self addSubview:_loginButton];

        //去註冊按鈕
        _goToRegisterButton = [UIButton buttonWithType:UIButtonTypeSystem];
        _goToRegisterButton.frame = CGRectMake(20, 335, deviceScreenWidth /2 - 20, 25);
        _goToRegisterButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
        _goToRegisterButton.titleLabel.font = [UIFont systemFontOfSize:14.0];
        [_goToRegisterButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
        [_goToRegisterButton setTitle:NSLocalizedString(@"GoToRegisterButtonName", nil) forState:UIControlStateNormal];
        [_goToRegisterButton setBackgroundColor:[UIColor clearColor]];
        [self addSubview:_goToRegisterButton];

        //找回密碼按鈕
        _findPasswordButton = [UIButton buttonWithType:UIButtonTypeSystem];
        _findPasswordButton.frame = CGRectMake(deviceScreenWidth / 2, 335, deviceScreenWidth /2 - 20, 25);
        _findPasswordButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight;
        _findPasswordButton.titleLabel.font = [UIFont systemFontOfSize:14.0];
        [_findPasswordButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
        [_findPasswordButton setTitle:NSLocalizedString(@"GetPasswordButtonName", nil) forState:UIControlStateNormal];
        [_findPasswordButton setBackgroundColor:[UIColor clearColor]];
        [self addSubview:_findPasswordButton];

        //添加通知,監測輸入內容
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loginAccountTextFieldTextDidChangeNotification:) name:UITextFieldTextDidChangeNotification object:_loginAccountTextField];

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loginPasswordTextFieldTextDidChangeNotification:) name:UITextFieldTextDidChangeNotification object:_loginPasswordTextField];

    }

    return self;

}

#pragma mark - 實現通知的方法

- (void)loginAccountTextFieldTextDidChangeNotification:(NSNotification *)notification {

    UITextField *textField = notification.object;

    [self.loginViewDelegate getTheInputStringInLoginViewFromTheTextField:textField.text withTextFieldTag : textField.tag];

}

- (void)loginPasswordTextFieldTextDidChangeNotification:(NSNotification *)notification {

    UITextField *textField = notification.object;

    [self.loginViewDelegate getTheInputStringInLoginViewFromTheTextField:textField.text withTextFieldTag : textField.tag];

}

@end

五、一些會碰到的錯誤

  1. [UIKeyboardTaskQueue waitUntilAllTasksAreFinished] may only be called from the main thread

  這個問題說的是需要在主線程來完成這個工作,碰到這個問題的地方是這個示例中,如果用戶輸入的賬戶不存在,彈出來一個警告框提示用戶是否要去註冊的時候,如果警告框的相關代碼沒有在主線程中操作的話,就會有這個問題。

  1. Main Thread Checker: UI API called on a background thread: -[UIView setHidden:]

  這個提示是說部分UI的更新需要在主線程中完成,如果沒有在主線程中完成這個操作,可能會有錯位界面。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章