用 Swift 做一個智慧機器人聊天 App (4)

發表於2015-11-28

終於有時間繼續寫我的文章了,這段時間在趕學校的軟體課程設計,可算弄完了!
下面繼續我們的創造之旅~

本篇文章你會學到

  • 用KVO方法優化鍵盤彈出動畫
  • 將同步下載訊息改為非同步,以減輕主執行緒的壓力。
  • 實現app登入、註冊的功能
    首先下載本章原始碼:
    百度網盤地址
    在上一章結尾我提到:
    我們的app在鍵盤彈出時有一些問題:
  • 在我們點出鍵盤時會遮擋訊息:
    iOS Simulator Screen Shot 2015年9月8日 下午4.14.55.png
  • 鍵盤彈出時把tableView拉到底部會有一個很難看的空白:
    iOS Simulator Screen Shot 2015年9月8日 下午4.15.21.png

    下面我們來解決它,我們需要在鍵盤彈出時修改tableView的一些屬性和約束條件,所以我們需要在鍵盤彈出時得到通知,要做到這個,我們要使用KVO(Key-Value Observing)方法。
    viewDidLoad()中的結尾新增以下程式碼來新增鍵值監控:

    首先獲取通知中心的例項,然後新增兩個觀察者,第一個用來監控UIKeyboardWillShowNotification鍵值的變化,這是系統提供的鍵值,當鍵盤將要彈出時會改變;第二個監控 UIKeyboardDidShowNotification,同樣地,這也是系統提供的,當鍵盤完全彈出時會改變。
    當這兩個鍵值改變時,會向通知中心傳送通知,然後由我們自定義的兩個selector方法處理通知,下面定義這兩個方法。
    首先第一個方法:

    很難懂?不著急,我們一步一步解釋這些程式碼!
    首先取出通知的userifno,鍵盤的所有屬性都在這裡面,他是一個字典型別的資料:

    然後通過UIKeyboardFrameEndUserInfoKeykey取出鍵盤的位置、大小資訊,也就是frame,並將其的參考view設定為tableView,記錄下它的高度

    然後我們需要計算一些資料:

    insetChange指的是那部分呢?我畫出一個圖大家就明白了:

insetChange

tableview的contentInset所指的是所圖的紅框部分。
overflow指的是所有訊息的總高度和鍵盤彈出前contentInset的差值,實際上就是沒有顯示部分的高度,也就是溢位的部分。
然後通過UIKeyboardAnimationDurationUserInfoKey
key來得到鍵盤彈出動畫的持續時間,設定自定義的動畫閉包:

我們看一下動畫閉包內部做了些什麼。
首先判斷tableView的滾動是否停止了,如果沒有停止滾動就不做任何事情。
tableView的滾動有兩種情況:

  1. 手指點選tableView,開始滾動,即tracking
  2. 手指抬起,tableView還會有一段減速滾動,也就是decelerating

    如果溢位大於0,則將tableView當前位置contentOffset向下移動,也就對應著手指向上拖動insetChange的高度,這樣可以保證訊息和鍵盤同時向上移動,但是如果滾動之後仍然是負值,且超出insetOld.top的距離,也就是導航欄的高度,就把tableView的當前位置設定成螢幕之上一個導航欄的高度。
    如果溢位是負值,但是絕對值小於insetChange,則contenOffset.y增加兩者的差值。
    當時長大於0時真正執行我們的動畫閉包,否則就即時執行閉包:

    其中要注意的是,我們的動畫曲線要和鍵盤彈出動畫的曲線相同,所以要用 UIKeyboardAnimationCurveUserInfoKeykey得到曲線資訊,這裡的型別轉換比較麻煩,要進行左移16的位運算,因為沒有對應的 as型別轉換可用,只能用最底層的方式。
    為什麼要這樣呢,其實我也不知道。。我也是查來的= =
    stackoverflow
    第二個方法,是用來防止出現底下的白邊,原理就是限制顯示出的高度,將底部切掉一部分,也就是將contenInset.bottom值變大一些,變大為鍵盤的高度:

    這樣就好了,執行一下,是不是感覺舒服多了?
    好的,下面我們解決下一個問題,在我們開啟app的時候,會看到控制檯顯示如下內容:

    意思是有一個長執行時間的操作在主執行緒執行,由於主執行緒主要用於UI顯示,所以如果有其他佔用cpu的執行緒也在其中執行的話會使得UI顯示變得很卡。
    雖然沒有什麼感覺,但是如果我們去看cpu的負荷圖的話,如下圖所示:
cpu負荷圖.png

會看到一個瞬間cpu負荷暴漲到了32%!這樣很不酷對不對?
我們的解決辦法就是,將這個佔用cpu很多使用量的操作放在另一個執行緒中,但首先我們要找到這是哪個操作,細心的你一定注意到,當載入聊天介面的時候會比較慢,沒錯就是那個操作在作怪!
所以呢,我們對initData()方法進行一些優化。
首先改變我們從Parse伺服器下載資料的方法query.findObjects(),這是同步下載資料,會佔據我們很大一部分cpu負載,所以我們要改為非同步下載,也就是放到其他執行緒執行,將以下程式碼修改一下:

修改為以下使用findObjectsInBackgroundWithBlock的版本:

由於這是非同步下載,所以tableView仍然會繼續載入cell而不會去管messages裡有沒有值,這時一定會崩潰,所以為了防止這種情況發生,我們要首先給messages賦一個歡迎訊息,在方法開頭加上這一行程式碼:

然後執行一下,同時看一下cpu的負荷率表:

螢幕快照 2015-09-14 下午10.25.58.png

僅有7%了!乾的漂亮!
下面我們來為我們的app增加一個登入的功能,因為沒有辦法去區分聊天資訊,所有人的聊天資訊都是共享的,真正的聊天app可不會是這樣的。
要做到這個,我們要為我們資料庫上的聊天訊息類增加一個新屬性:

新增新屬性
選擇屬性型別

)
User類是Parse預設的使用者類,我們的型別用指標,指向使用者類,將資訊與使用者進行繫結,這樣就能知道該條資訊屬於哪個使用者了。
幸運的是Parse已經提供了登入的檢視控制器,同樣還有註冊的檢視控制器:
PFLogInViewControllerPFSignUpViewController
雖然它本身的語言是英文,但是我在初始專案裡對他們進行了一下漢化修改,其實有更好的辦法進行國際化,但這個只是為了演示。
首先我們建立一個歡迎頁面:

螢幕快照 2015-09-15 上午8.55.18.png

還有登入頁面,註冊頁面:

登入頁面
註冊頁面

都加上

LogInViewController.swift中的viewDidLoad()方法裡新增以下程式碼來自定義logo:

同樣地,在SignUpViewController.swift中的viewDidLoad()方法裡新增以下程式碼:

WelcomeViewController.swift增加import模組:

使WelcomeViewController遵循PFSignUpViewControllerDelegate
PFLogInViewControllerDelegate代理:

增加屬性,登入檢視控制器和註冊檢視控制器,還有歡迎介面的logowelcomeLabel用來顯示logo和歡迎語:

我們來實現一些代理方法,首先是登入的代理方法:

第一個方法是執行我們自定義的使用者名稱密碼的合法性檢查方法;第二個是在登入之後執行,可以通過user引數知道登入的是哪個使用者;第三個是如果登入出現錯誤,錯誤資訊可以在這裡找到。
同樣地,實現註冊相應的三個方法:

下面我們在viewDidLoad()中配置一下歡迎介面:

我們在viewWillAppear()方法中實現歡迎頁面邏輯,當已經登入時,顯示歡迎語歡迎某某某,然後2s後進入聊天介面,否則顯示未登入,進入登入介面:

定義這個延時方法,在import下面:

執行之前還有一步,就是在AppDelegate.swiftapplication()方法裡修改我們的初始檢視控制器:

還有一件事,我們要在讀取資料的時候只讀取當前登入使用者的資訊,而不是全部,所以我們要加上一個限制,在query.findObjectsInBackgroundWithBlock執行前加上以下程式碼:

同樣地,我們儲存訊息的時候,將當前使用者賦值給createdBy屬性,修改一下saveMessage()方法:

至此我們的登入註冊功能就整合進我們的app了,當然這只是一個演示,為了演示如何用ParseUI庫實現登入功能,並沒有太多的自定義,更復雜的應用這裡先不進行擴充套件了。

登入.gif

到此我們的app已經有一些正式的樣子了,下一章還會對其進行功能的擴充和優化!請持續關注!
本章完成原始碼下載
如果我的文章對你有幫助,請點一下喜歡,大家的支援是我繼續寫作的動力!

相關文章