久久久久久久av_日韩在线中文_看一级毛片视频_日本精品二区_成人深夜福利视频_武道仙尊动漫在线观看

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

總結

開局只是想簡單實現聊天效果,沒想到因為強迫癥和實現優秀的體驗,在鍵盤效果上死磕了許久。前后共花了一天半時間,當真是茶飯不思,夜不能寐。中間嘗試了無數滑動方法,在筆記本上畫圖模擬各種情況,最終做出來后,就像那啥之后,整個人瞬間疲軟了,迫不及待地睡了一覺,但內心卻是無比激動。

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對html5模板網的支持。

【網站聲明】本站部分內容來源于互聯網,旨在幫助大家更快的解決問題,如果有圖片或者內容侵犯了您的權益,請聯系我們刪除處理,感謝您的支持!

相關文檔推薦

這篇文章主要給大家介紹了關于RxSwift學習教程之類型對象Subject的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著
這篇文章主要給大家介紹了關于RxSwift學習教程之Observable的相關資料,文中詳細的給大家介紹了關于新建Observable、訂閱Observable和取消訂閱并消除內存泄漏等相關的內容,需要的朋友可以
RxSwift是Swift函數響應式編程的一個開源庫,由Github的ReactiveX組織開發,維護。下面這篇文章主要給大家介紹了關于RxSwift學習之基礎篇的相關資料,文中通過示例代碼介紹的非常詳細,需
這篇文章主要為大家詳細介紹了Swift4.0 Array數組的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
CATransition動畫主要在過渡時使用,比如兩個頁面層級改變的時候添加一個轉場效果。CATransition分為兩類,一類是公開的動畫效果,一類是非公開的動畫效果。這篇文章主要給大家介紹了
近日,iPhone X的發布在人們群眾引起了很大的轟動,下面這篇文章主要給大家介紹了關于利用Swift如何判斷iPhone X機型的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友們下面
主站蜘蛛池模板: 九九热最新地址 | 综合激情久久 | 亚洲欧洲一区 | 国产一区二区三区在线 | 亚洲一区 | 国产精品久久一区二区三区 | 久久av影院| 自拍亚洲 | 国精日本亚洲欧州国产中文久久 | 精品永久 | 夜夜摸天天操 | 伊人二区 | 国产欧美日韩一区二区三区 | 欧美日韩国产中文 | 亚洲成人播放器 | 成人免费淫片aa视频免费 | 日韩电影免费观看中文字幕 | 中文字幕av色 | 国产精品爱久久久久久久 | 性网站免费 | 国产一卡二卡三卡 | 精品国产乱码久久久久久闺蜜 | 久久高清精品 | 综合精品久久久 | 欧美在线视频一区二区 | 在线亚洲欧美 | 黄色av大片 | av中文网 | av电影一区二区 | 欧美日韩在线免费观看 | 日韩中文字幕在线观看 | 99久久国产免费 | 日本高清视频在线播放 | 免费性视频 | 亚洲高清视频一区二区 | 国产欧美日韩在线播放 | 国产精品久久久久久久久久免费看 | 青青草综合 | 成人三级av | 久久久久国产一区二区三区 | 男人阁久久 |