溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

iOS+PHP實(shí)現(xiàn)登錄功能

發(fā)布時(shí)間:2020-05-29 19:34:25 來源:網(wǎng)絡(luò) 閱讀:12352 作者:Stefen123 欄目:移動(dòng)開發(fā)

??近期在做app開發(fā)的時(shí)候,因?yàn)橐玫絘pp登錄功能,就自己寫了個(gè)簡(jiǎn)單的iOS+PHP實(shí)現(xiàn)登錄功能的demo,經(jīng)過運(yùn)行能夠通過登錄測(cè)試。

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

一、一些約定條件

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

??當(dāng)在裝有WIN 7系統(tǒng)的電腦上配置服務(wù)器時(shí),我使用的是WAMP集成軟件,數(shù)據(jù)庫和表的編輯操作使用的是SQLyog軟件,這樣可以有效的創(chuàng)建、修改表的內(nèi)容。==注意,在WIN7的電腦上搭建完后臺(tái)并創(chuàng)建好數(shù)據(jù)庫之后,還需要進(jìn)行局域網(wǎng)的配置工作,這樣才能讓處于同一局域網(wǎng)下的設(shè)備(如手機(jī))連接到這臺(tái)電腦及后臺(tái)==。這個(gè)方法我也忘了,所以需要您和其他做PHP后臺(tái)開發(fā)的同仁咨詢。==如果您已經(jīng)知道怎么做了,也歡迎不吝賜教,我好記錄在本文章中,供更多人的來學(xué)習(xí)==。

??一些約定條件如下

  • [x] 手機(jī)客戶端能成功連接后臺(tái)服務(wù)器,并與后臺(tái)服務(wù)器進(jìn)行數(shù)據(jù)交互
  • [x] 密碼為原始輸入的字符串,不經(jīng)過MD5等加密方式的加密(正常開發(fā)時(shí),請(qǐng)務(wù)必進(jìn)行加密處理)
  • [x] 傳輸方式選擇GET傳輸(為了安全起見,最好使用POST傳輸方式)
  • [x] 登錄賬號(hào)只選擇手機(jī)號(hào)(正常開發(fā)時(shí),登錄的賬號(hào)可能還有email或者用戶名)

二、數(shù)據(jù)庫和表的創(chuàng)建及初始化

??使用SQLyog或者phpMyAdmin創(chuàng)建一個(gè)名為testAppDatabase的數(shù)據(jù)庫,“基字符集”選擇“utf8”,“數(shù)據(jù)庫排序規(guī)則”選擇“utf8_general_ci”,如下圖所示(圖像截取的是使用SQLyog軟件創(chuàng)建數(shù)據(jù)庫的情況,使用phpMyAdmin類似):
iOS+PHP實(shí)現(xiàn)登錄功能

??然后,在testAppDatabase數(shù)據(jù)庫下,新建一個(gè)名為userInformationTable的表,“引擎”選擇“InnoDB”,“字符集”選擇“utf8”,“核對(duì)”選擇“utf8_general_ci”,最后創(chuàng)建列名及每一列對(duì)應(yīng)的數(shù)據(jù)類型以及是否可以為空等,并設(shè)置userID為主鍵、正數(shù)、自增,如下圖所示(圖像截取的是使用SQLyog軟件創(chuàng)建表的情況,使用phpMyAdmin類似):
iOS+PHP實(shí)現(xiàn)登錄功能

??正常情況下,每一列都最好設(shè)置為“非空”,如果用戶沒有輸入,那么可以默認(rèn)使用“N/A”等填充,等用戶輸入了當(dāng)前列對(duì)應(yīng)的內(nèi)容了,再替換掉“N/A”即可。

??因?yàn)槲覀兪亲龅卿浤K的驗(yàn)證,沒有經(jīng)過注冊(cè),因此,數(shù)據(jù)庫中是沒有信息的。我們可以手動(dòng)先填寫一些信息,供測(cè)試使用。填寫好的內(nèi)容如下圖所示(使用的phpMyAdmin客戶端插入的數(shù)據(jù))
iOS+PHP實(shí)現(xiàn)登錄功能

??==注意,此時(shí)的密碼是完全的明文密碼,未進(jìn)行任何加密,這主要是為了測(cè)試方便使用,正常開發(fā)時(shí),請(qǐng)務(wù)必將保存到數(shù)據(jù)庫中的密碼進(jìn)行加密處理。==

??至此,數(shù)據(jù)庫相關(guān)的“配置”就處理完了,下面是php代碼相關(guān)的內(nèi)容。

三、php代碼

??在php代碼中,我們主要完成的是接收客戶端傳輸過來的數(shù)據(jù),并將數(shù)據(jù)與數(shù)據(jù)庫進(jìn)行匹配驗(yàn)證,一般驗(yàn)證的內(nèi)容有兩點(diǎn):

  • [x] 用戶輸入的賬號(hào)是否存在
  • [x] 用戶輸入的賬號(hào)存在的情況下,賬號(hào)和密碼是否與數(shù)據(jù)庫中的一一匹配

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

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

??對(duì)php有一些了解的人應(yīng)該知道,保存在htddoc路徑(對(duì)于使用WAMP集成的環(huán)境來說,就是www文件夾下,如下圖)下的文件,是可以被瀏覽器通過輸入網(wǎng)址的方式讀取到的,如果將登錄數(shù)據(jù)庫使用的賬戶和密碼信息放到這個(gè)文件夾下,那么數(shù)據(jù)庫是非常不安全的。
iOS+PHP實(shí)現(xiàn)登錄功能

??因此,我們通常將連接數(shù)據(jù)庫需要的php代碼單獨(dú)編寫并保存為“.php”格式的文件,然后將這個(gè)文件放置在與“www”同級(jí)的位置,如下圖所示的“connectionToDB.php”文件。
iOS+PHP實(shí)現(xiàn)登錄功能

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

connectionToDB.php

<?php

    $dbc = mysqli_connect('192.168.1.101', 'root', '你設(shè)置的登錄數(shù)據(jù)庫的密碼', 'testAppDatabase') or die("連接失?。?.mysql_error());  

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

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

  1. 接著,編寫和登錄驗(yàn)證相關(guān)的php代碼,將其保存為“l(fā)ogin.php”文件并保存到www目錄下,如下圖所示:

iOS+PHP實(shí)現(xiàn)登錄功能

??“www”目錄就想到于在瀏覽器中輸入的localhost或者192.168.1.101這個(gè)IP地址,所以能看到,我們要編寫的“l(fā)ogin.php”在下兩級(jí)目錄下,知道這點(diǎn)這對(duì)于我們編寫“l(fā)ogin.php”文件中的某些代碼是有必要的。

login.php

<?php

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

    require_once('../../../connectionToDB.php');  
    //一個(gè)"../"代表一級(jí)目錄,
    //因?yàn)槲覀兊摹癱onnectionToDB.php”文件與“www”文件夾在同一級(jí)目錄下
    //從“l(fā)ogin.php”追溯“connectionToDB.php”需要進(jìn)過三級(jí)目錄,所以需要三個(gè)"../"

    $postedData = $_REQUEST;  //$_REQUEST既可以獲取到通過POST方式傳輸?shù)臄?shù)據(jù),也可以獲取到通過GET方式傳輸?shù)臄?shù)據(jù)

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

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

    $userPassword = $postedData['User_Password'];

    //根據(jù)賬戶形式獲取對(duì)應(yīng)的賬號(hào)內(nèi)容,用于后面的比對(duì)

    //是否賬號(hào)是否存在的標(biāo)簽以及是否登錄成功的標(biāo)簽
    $accountBeingOrNotFlag = "0";  //0代表賬號(hào)不存在,1代表賬號(hào)存在

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

    switch ($userAccountType) {

        case "Telephone":  //賬號(hào)是手機(jī)號(hào)

            $q = "SELECT * FROM userinformationtable WHERE UserTelephoneNumber = $userAccount";  //查詢數(shù)據(jù)庫有沒有這個(gè)手機(jī)號(hào)

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

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

            if($rows) {

                //行數(shù)不是0,說明有這個(gè)手機(jī)號(hào),設(shè)置標(biāo)簽的值為1
                $accountBeingOrNotFlag = "1";  //賬號(hào)存在

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

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

                $rowsA = @mysqli_num_rows($rA);

                if($rowsA) {

                    //行數(shù)不是0,說明賬號(hào)和密碼匹配,設(shè)置標(biāo)簽值為1,登錄成功
                    $loginOKOrNotFlag = "1";  

                }else {

                    //行數(shù)是0,說明賬號(hào)和密碼不匹配,設(shè)置標(biāo)簽值為0,登錄失敗
                    $loginOKOrNotFlag = "0";  

                }

            }else {

                //行數(shù)是0,說明賬號(hào)不存在,設(shè)置標(biāo)簽值為0
                $accountBeingOrNotFlag = "0";  

            }

            //將標(biāo)簽值保存到數(shù)組中,然后將其傳遞給客戶端,客戶端根據(jù)標(biāo)簽值判斷對(duì)應(yīng)的操作邏輯
            $returnArr = array("accountBeingOrNotFlag" => $accountBeingOrNotFlag, "loginOKOrNotFlag" => $loginOKOrNotFlag);

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

            //關(guān)閉數(shù)據(jù)庫連接
            mysqli_close($dbc);

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

            break;

            //下面的代碼注釋和上面的這個(gè)case里面的類似,不再贅述
        case "EmailAddress":

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

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

            @$rows = mysql_num_rows($r);

            if($rows) {

                $accountBeingOrNotFlag = "1";  //賬號(hào)存在

                $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";  //賬號(hào)不存在
            }

            $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";  //賬號(hào)存在

                $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";  //賬號(hào)不存在
            }

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

            mysqli_close($dbc);

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

            break;

    }

    ?>

??好了,和登錄有關(guān)的php代碼已經(jīng)編寫完成了,下面就開始編寫iOS客戶端的代碼。

四、iOS客戶端

??iOS客戶端的代碼,我們將采用MVC的架構(gòu)來編寫。

??可能有人會(huì)問,只是一個(gè)demo,為什么不將M也合并到V中一起寫呢?這個(gè)就和我在文章開頭提到的坑有關(guān)了。

??我們先來看一個(gè)將MVC寫在一個(gè)viewController中的例子

  1. 將MVC寫在一個(gè)viewController中的例子

??我們隨便新建一個(gè)基于單視圖的工程,然后在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];

    //設(shè)置請(qǐng)求方式 POST
    request.HTTPMethod = @"POST";

    //設(shè)置請(qǐng)求的超時(shí)時(shí)間
    request.timeoutInterval = 60;

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

    NSURLSession *session = [NSURLSession sharedSession];

    //4 創(chuàng)建網(wǎng)絡(luò)任務(wù) NSURLSessionTask
    //通過網(wǎng)絡(luò)會(huì)話 來創(chuàng)建數(shù)據(jù)任務(wù)
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

        NSLog(@"網(wǎng)絡(luò)請(qǐng)求完成");

        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 發(fā)起網(wǎng)絡(luò)任務(wù)

    [dataTask resume];

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

??這段代碼本來是想完成的工作是:將登陸的信息傳遞給后臺(tái)之后,后臺(tái)進(jìn)行驗(yàn)證,并將驗(yàn)證的結(jié)果(沒有賬號(hào)、賬號(hào)密碼不匹配、賬號(hào)密碼匹配)傳回給客戶端,然后由客戶端根據(jù)返回回來的標(biāo)簽值做響應(yīng)的操作。但是運(yùn)行這段代碼之后,通過斷點(diǎn)調(diào)試,會(huì)發(fā)現(xiàn),dataTaskWithRequest:completionHandler:并沒有按照順序執(zhí)行,而是直接跳過,然后執(zhí)行了[dataTask resume];方法,接著就是NSLog函數(shù)輸出_str的值,會(huì)發(fā)現(xiàn)值是空的。當(dāng)viewDidLoad代碼塊全部執(zhí)行完畢后(即執(zhí)行到最后一個(gè)右大括號(hào)}),才會(huì)執(zhí)行dataTaskWithRequest:completionHandler:代碼塊中的內(nèi)容。雖然此后會(huì)更新_str的值,但此時(shí)其實(shí)客戶端已經(jīng)接收了第一次的_str的值了,如果不做其它的工作,我們是很難得到想要的結(jié)果了。

??后來經(jīng)過多次的調(diào)試、驗(yàn)證,最終才發(fā)現(xiàn),使用通知可以解決這個(gè)問題。這也就是為啥我要把M單獨(dú)寫的原因:我們可以在M里面發(fā)送通知,然后在view里面注冊(cè)通知和實(shí)現(xiàn)通知的方法。

??我們分別創(chuàng)建一個(gè)繼承于NSObject的RegisterAndLoginModel文件,一個(gè)繼承于UIViewController的LoginViewController文件,以及一個(gè)繼承于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方式提交數(shù)據(jù)
- (void)checkTheUserAccount : (NSString*)userAccount andPassword : (NSString*)userPassword withAccountType : (NSString*)accountType {

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

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

    //1.構(gòu)造URL網(wǎng)絡(luò)地址
    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.構(gòu)造網(wǎng)絡(luò)請(qǐng)求對(duì)象  NSURLRequest
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];

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

    //3.設(shè)置請(qǐng)求方式 GET
    request.HTTPMethod = @"GET";
    //設(shè)置請(qǐng)求的超時(shí)時(shí)間
    request.timeoutInterval = 60;

    NSURLSession *session = [NSURLSession sharedSession];

    //4 創(chuàng)建網(wǎng)絡(luò)任務(wù) NSURLSessionTask。通過網(wǎng)絡(luò)會(huì)話 來創(chuàng)建數(shù)據(jù)任務(wù)
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

        NSLog(@"網(wǎng)絡(luò)請(qǐng)求完成");

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

        NSLog(@"接收到的數(shù)據(jù)為%@",jsonDic);

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

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

    }];

    //5.發(fā)起網(wǎng)絡(luò)任務(wù)
    [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;  //賬號(hào)是否為空
int loginPasswordIsOK = 0;  //密碼格式是否正確
int loginBtnPressedNumbers = 0;  //登錄按鈕累計(jì)點(diǎn)擊次數(shù)

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

    //添加通知,監(jiān)測(cè)后臺(tái)服務(wù)器返回的標(biāo)簽值
    [[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(@"點(diǎn)擊了登錄");

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

    //首先判斷用戶輸入的賬號(hào)的類型

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

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

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

        }else {

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

    }else {

        //是手機(jī)號(hào)

        accountTypeString = @"Telephone";

    }

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

    loginBtnPressedNumbers += 1;

}

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

    NSLog(@"去注冊(cè)");

    [_loginViewControllerDelegate goToRegisterViewController];

}

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

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

#pragma mark - 實(shí)現(xiàn)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 - 使用正則表達(dá)式判斷手機(jī)號(hào)格式是否正確

-(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 - 正則表達(dá)式判斷郵箱格式是否正確

- (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 實(shí)現(xiàn)通知方法

- (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"]) {

        //賬號(hào)不存在,提示用戶是否去注冊(cè)

        NSLog(@"對(duì)不起,賬號(hào)不存在");

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

            // do something

            UIAlertController *accountNotBeingAlert = [UIAlertController alertControllerWithTitle:@"賬號(hào)不存在" message:@"對(duì)不起,您輸入的賬號(hào)不存在,是否前去注冊(cè)?" preferredStyle:UIAlertControllerStyleAlert];

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

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

                //進(jìn)入注冊(cè)界面

                [_loginViewControllerDelegate goToRegisterViewController];

            }];

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

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

        });

    }else {

        //賬號(hào)存在

        //判斷賬號(hào)和密碼是否匹配

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

            //賬號(hào)和密碼不匹配

            NSLog(@"賬號(hào)和密碼不匹配,請(qǐng)重新輸入");

            if(loginBtnPressedNumbers > 2) {

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

                    //用戶輸入的賬號(hào)是手機(jī)號(hào),顯示獲取短信驗(yàn)證碼
                    //短信驗(yàn)證碼一段時(shí)間內(nèi)只能獲取三次,如果超過三次,那么顯示圖形驗(yàn)證碼

                    //更新界面元素的時(shí)候,也需要回到主線程,否則程序就崩潰或者界面UI更新錯(cuò)位
                    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 {

                    //賬號(hào)是郵箱地址或者一般用戶名,顯示圖形驗(yàn)證碼
                }

            }

        }else {

            //賬號(hào)和密碼匹配,登錄成功

            //登錄成功后將登錄狀態(tài)信息保存到NSUserDefaults中,供程序調(diào)用

            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) {

        //賬號(hào)輸入框
        _loginAccountTextField = [[UITextField alloc]initWithFrame:CGRectMake(20, 120, deviceScreenWidth - 40, 55)];
        [_loginAccountTextField setClearButtonMode:UITextFieldViewModeWhileEditing];
        _loginAccountTextField.placeholder = @"用戶名/郵箱地址/手機(jī)號(hào)";
        _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;

        //驗(yàn)證碼輸入框
        _verificationCodeTextField = [[UITextField alloc]initWithFrame:CGRectMake(20, 260, deviceScreenWidth - 40, 55)];
        [_verificationCodeTextField setClearButtonMode:UITextFieldViewModeWhileEditing];
        _verificationCodeTextField.placeholder = @"輸入4位短信驗(yàn)證碼";
        _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];

        //去注冊(cè)按鈕
        _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];

        //添加通知,監(jiān)測(cè)輸入內(nèi)容
        [[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 - 實(shí)現(xiàn)通知的方法

- (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

五、一些會(huì)碰到的錯(cuò)誤

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

??這個(gè)問題說的是需要在主線程來完成這個(gè)工作,碰到這個(gè)問題的地方是這個(gè)示例中,如果用戶輸入的賬戶不存在,彈出來一個(gè)警告框提示用戶是否要去注冊(cè)的時(shí)候,如果警告框的相關(guān)代碼沒有在主線程中操作的話,就會(huì)有這個(gè)問題。

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

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

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI