Swift 3.1聊天界面鍵盤效果的實現詳解。本站提示廣大學習愛好者:(Swift 3.1聊天界面鍵盤效果的實現詳解)文章只能為提供參考,不一定能成為您想要的結果。以下是Swift 3.1聊天界面鍵盤效果的實現詳解正文
前言
最近寫的 Swift 項目裡要實現一個聊天界面,在處理鍵盤彈出的時候遇到了一點麻煩。
麻煩就在於鍵盤彈出後如何處理屏幕和鍵盤的關系
經過一番死磕,終於做出了想要的效果,效果如下:
注:原本項目是 Swift 2.3 寫的,為了寫這篇博客,用 Swift 3.1 重新實現了一遍。
感受:方法名真的縮短了不少,😁
分析
現在開始,就讓我來分析一下這次死磕歷程。
一開始想到了兩種處理方法,一種是 鍵盤彈出消失的同時,輸入欄隨著鍵盤移動,一種是 鍵盤彈出消失時,整個屏幕隨著鍵盤移動,這兩種方法都有弊端,讓我們分類討論下:
1. 輸入欄隨著鍵盤移動
當消息條數較少時,鍵盤不會遮擋住消息 消息條數多了以後,鍵盤會遮擋住屏幕中處在鍵盤位置的消息 每次發送了新的消息,用戶無法及時看到(因為被鍵盤遮住了)結論:體驗不好
2. 屏幕隨著鍵盤移動
消息多了以後,能在屏幕上及時看到最新的消息 但消息少的時候,由於鍵盤把整個 view 頂出屏幕,用戶看不到這頭幾條消息 當消息沒有占滿整個屏幕的時候,鍵盤把 view 頂上去,view 底部會留下一段空白結論:還是體驗不好
上述兩種情況的圖片我就不發了,大家自己腦補一下
那麼作為強迫症,怎麼能容忍這種不好的體驗?於是開始死磕,首先參考了下日常使用最多的微信、qq,分情況總結了一下微信、qq裡鍵盤彈出的效果
情況一:消息較少時(當鍵盤彈出不會遮擋住消息)聊天界面不動,鍵盤彈出時只有輸入欄上滑,這樣保證了最開始的幾條消息能完整顯示 情況二:消息較多但還未占滿屏幕時(當鍵盤彈出會遮擋住部分消息),鍵盤彈出時輸入欄上滑,同時聊天界面也上滑。注意:此時輸入欄上滑的距離為鍵盤高度,聊天界面上滑距離為鍵盤可能遮擋住消息的高度 情況三:消息占滿或超出屏幕時,鍵盤彈出時整個 view 上滑 這其中還包括了發送消息時,聊天界面上滑,保證最後一條消息顯示在鍵盤上方的處理。如果大家不方便腦補,直接掏出手機,用微信或qq和女神聊個天吧
下面,我們放出代碼分析:
布局
首先導入 SnapKit 布局框架,對聊天界面和輸入欄進行約束
由於我懶,怎麼使用 Snapkit 就不贅述 😁
toolBarView.snp.makeConstraints { (make) in make.left.equalTo(view.snp.left) make.right.equalTo(view.snp.right) make.height.equalTo(toolBarHeight) make.bottom.equalTo(view.snp.bottom) } chatTableView.snp.makeConstraints { (make) in make.left.equalTo(view.snp.left) make.right.equalTo(view.snp.right) make.bottom.equalTo(toolBarView.snp.top) make.top.equalTo(view.snp.top).offset(64) }
這裡讓聊天界面的底部和輸入欄的上方貼合
監聽
監聽鍵盤的彈出和消失
NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
當鍵盤彈出時,會觸發 keyBoardWillShow(notification:)
方法,鍵盤消失時,會觸發 keyBoardWillHide(notification:)
方法,我們很多復雜的邏輯,都要在這兩個方法中實現。另外,Swift 3.1 的版本中,把很多方法的 NS 前綴去除了,所以還在用 Swift 2.3 的童鞋,在NotificationCenter 前面加上 NS 前綴就可以了。
下面重頭戲來了,實現上述三種情況的效果
效果
彈出動畫
想要 view 隨著鍵盤彈出上滑,需要得到鍵盤的高度和鍵盤彈出動畫的時間,這裡我們通過如下代碼得到:
func keyBoardWillShow(notification: Notification) { let userInfo = notification.userInfo! as Dictionary let value = userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue let keyBoardRect = value.cgRectValue // 得到鍵盤高度 let keyBoardHeight = keyBoardRect.size.height mKeyBoardHeight = keyBoardHeight // 得到鍵盤彈出所需時間 let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber mKeyBoardAnimateDuration = duration.doubleValue ... }
然後實現動畫
之前在實現輸入欄隨著鍵盤彈出的時候,嘗試過兩種寫法:
1、更新 frame
var animate: (()->Void) = { let newFrame = CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: SCREEN_HEIGHT - mKeyBoardHeight) self.toolBarView.frame = newFrame } UIView.animate(withDuration: mKeyBoardAnimateDuration, delay: 0, options: options, animations: animate)
2、更新約束
var animate: (()->Void) = { self.toolBarView.snp.updateConstraints(closure: { (make) in make.bottom.equalTo(self.view.snp_bottom).offset(-mKeyBoardHeight) } } UIView.animate(withDuration: mKeyBoardAnimateDuration, delay: 0, options: options, animations: animate)
但最後發現,由於滑動的速度不一樣,會造成鍵盤彈出和輸入欄上滑時出現縫隙。一句話,體驗不好。
於是去網上找了一種方法(必須要感謝下那位大哥),利用一個動畫的 options,和 view 的 transform 方法完美解決問題。讓 view 和鍵盤滑動時無縫貼合、如絲般順滑。
方法如下:
處理所需的動畫
var animate: (()->Void) = { self.toolBarView.transform = CGAffineTransform(translationX: 0, y: -keyBoardHeight) }
創建動畫 options
let options = UIViewAnimationOptions(rawValue: UInt((userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber).intValue << 16))
實現動畫
UIView.animate(withDuration: mKeyBoardAnimateDuration, delay: 0, options: options, animations: animate)
如此這般,大功告成!親個嘴兒 😙
現在有了絲滑的滑動效果,我們來處理上述分析的三種情況
定義情況
首先定義效果枚舉類型,枚舉的好處就不贅述了
enum AnimateType { case animate1 // 鍵盤彈出的話不會遮擋消息 case animate2 // 鍵盤彈出的話會遮擋消息,但最後一條消息距離輸入框有一段距離 case animate3 // 最後一條消息距離輸入框在小范圍內,這裡設為 30 }
枚舉類型對應了上述分析的三種效果
讓我們回顧一下三種情況
情況一:消息較少時(當鍵盤彈出不會遮擋住消息)聊天界面不動,鍵盤彈出時只有輸入欄上滑,這樣保證了最開始的幾條消息能完整顯示 情況二:消息較多但還未占滿屏幕時(當鍵盤彈出會遮擋住部分消息),鍵盤彈出時輸入欄上滑,同時聊天界面也上滑。注意:此時輸入欄上滑的距離為鍵盤高度,聊天界面上滑距離為鍵盤可能遮擋住消息的高度 情況三:消息占滿或超出屏幕時,鍵盤彈出時整個 view 上滑實現
當消息數量為 0 時,默認動畫為輸入框滑動
var animate: (()->Void) = { self.toolBarView.transform = CGAffineTransform(translationX: 0, y: -keyBoardHeight) }
當消息數量不為 0 時,需要進行計算判斷情況
首先得到最後一條消息在屏幕的位置,其中 cellDistance 就是最後一條消息相對於當前屏幕的 y 值
let lastIndex = IndexPath(row: msgList.count - 1, section: 0) let rectCellView = chatTableView.rectForRow(at: lastIndex) let rect = chatTableView.convert(rectCellView, to: chatTableView.superview) let cellDistance = rect.origin.y + rect.height
限定兩個位置 distance1 和 distance2
distance1 代表彈出鍵盤後鍵盤頂部的位置相對於當前屏幕的 y 值,對應第一和第二種情況的判斷,distance2 代表未彈出鍵盤時輸入框頂部的位置當對於當前屏幕的 y 值。
let distance1 = SCREEN_HEIGHT - toolBarHeight - keyBoardHeight let distance2 = SCREEN_HEIGHT - toolBarHeight - 2 * fitBlank
計算出最後一條消息的位置和限定 distance1 的差值
這樣,當處於第二種情況時,輸入框上滑距離為鍵盤高度,聊天界面上滑距離為計算出的差值,完美實現對應效果
對應代碼如下:
let difY = cellDistance - distance1 if cellDistance <= distance1 { animate = { self.toolBarView.transform = CGAffineTransform(translationX: 0, y: -keyBoardHeight) } animateType = .animate1 } else if distance1 < cellDistance && cellDistance <= distance2 { animate = { self.toolBarView.transform = CGAffineTransform(translationX: 0, y: -keyBoardHeight) self.chatTableView.transform = CGAffineTransform(translationX: 0, y: -difY) self.lastDifY = difY //這裡記錄下最後一次滑動的dif值,以後有用 } animateType = .animate2 } else { animate = { self.view.transform = CGAffineTransform(translationX: 0, y: -keyBoardHeight) } animateType = .animate3 }
以上代碼都發生在 keyBoardWillShow(notification: Notification)
中,每次判斷完動畫的情況後,記錄下動畫情況,然後當鍵盤消失時,在 keyBoardWillHide(notification: Notification)
中還原
代碼如下:
// 返回 view 或 toolBarView 或 chatTableView 到原有狀態 switch animateType { case .animate1: animate = { self.toolBarView.transform = CGAffineTransform.identity self.chatTableView.transform = CGAffineTransform.identity } case .animate2: animate = { self.toolBarView.transform = CGAffineTransform.identity self.chatTableView.transform = CGAffineTransform.identity } case .animate3: animate = { self.view.transform = CGAffineTransform.identity } }
如此這般,就實現了三種滑動的效果。但是別急,問題又來了。在情況一和情況二中,聊天界面上滑,怎麼保證最後一條消息顯示在鍵盤上方呢?
這就需要我們在發送完消息後,刷新列表的方法中進行處理,這裡貼出整個刷新列表方法
實現思路為:
處於情況三時,由於之前約束了聊天界面在輸入欄上方,並且整個界面一起上滑,約束依舊成立,只需把聊天界面最後一條消息滾動到聊天界面底部 處於情況一和情況二時,如果聊天界面上滑的總距離(lastDifY + difY)小於鍵盤高度,則可以繼續上滑,上滑距離為新增消息的高度 一旦聊天界面上滑的總距離將要超過鍵盤高度,則上滑總距離設為鍵盤高度,如果聊天界面上滑的總距離超過鍵盤高度,界面上會出現多余的空白 一旦聊天界面上滑的總距離為鍵盤高度,則按照情況三處理費盡唇舌,可能還是說不清楚,所以上代碼吧😭:
// 刷新列表 func reloadTableView() { chatTableView.reloadData() chatTableView.layoutIfNeeded() // 得到最後一條消息在view中的位置 let lastIndex = IndexPath(row: msgList.count - 1, section: 0) let rectCellView = chatTableView.rectForRow(at: lastIndex) let rect = chatTableView.convert(rectCellView, to: chatTableView.superview) let cellDistance = rect.origin.y + rect.height let distance1 = SCREEN_HEIGHT - toolBarHeight - mKeyBoardHeight // 計算鍵盤可能遮住的消息的長度 let difY = cellDistance - distance1 if animateType == .animate3 { // 處於情況三時,由於之前的約束(聊天界面在輸入欄上方),並且 // 是整個界面一起上滑,所以約束依舊成立,只需把聊天界面最後 // 一條消息滾動到聊天界面底部即可 scrollToBottom() } else if (animateType == .animate1 || animateType == .animate2) && difY > 0{ // 在情況一和情況二中,如果聊天界面上滑的總距離小於鍵盤高度,則可以繼續上滑 // 一旦聊天界面上滑的總距離 lastDifY + difY 將要超過鍵盤高度,則上滑總距離設為鍵盤高度 // 此時執行 trans 動畫 // 一旦聊天界面上滑總距離為鍵盤高度,則變為情況三的情況,把聊天界面最後 // 一條消息滾動到聊天界面底部即可 if lastDifY + difY < mKeyBoardHeight { lastDifY += difY let animate: (()->Void) = { self.chatTableView.transform = CGAffineTransform(translationX: 0, y: -self.lastDifY) } UIView.animate(withDuration: mKeyBoardAnimateDuration, delay: 0, options: animateOption, animations: animate) } else if lastDifY + difY > mKeyBoardHeight { if lastDifY != mKeyBoardHeight { let animate: (()->Void) = { self.chatTableView.transform = CGAffineTransform(translationX: 0, y: -self.mKeyBoardHeight) } UIView.animate(withDuration: mKeyBoardAnimateDuration, delay: 0, options: animateOption, animations: animate) lastDifY = mKeyBoardHeight } scrollToBottom() } } }
再貼一下滾動最後一條消息到聊天界面底部的代碼:
func scrollToBottom() { if msgList.count > 0 { chatTableView.scrollToRow(at: IndexPath(row: msgList.count - 1, section: 0), at: .bottom, animated: true) } }
至此,就真的大功告成了
最後,附上源碼地址:
github地址:https://github.com/Newbeeee/NbChatView-Swift
本地地址:http://xiazai.jb51.net/201704/yuanma/NbChatView-Swift-master(jb51.net).rar
總結
開局只是想簡單實現聊天效果,沒想到因為強迫症和實現優秀的體驗,在鍵盤效果上死磕了許久。前後共花了一天半時間,當真是茶飯不思,夜不能寐。中間嘗試了無數滑動方法,在筆記本上畫圖模擬各種情況,最終做出來後,就像那啥之後,整個人瞬間疲軟了,迫不及待地睡了一覺,但內心卻是無比激動。
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對的支持。