最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Swift 3.1聊天界面鍵盤效果的實(shí)現(xiàn)詳解

 更新時(shí)間:2017年04月26日 14:19:56   作者:Murray66  
這篇文章主要給大家介紹了Swift 3.1聊天界面鍵盤效果實(shí)現(xiàn)的相關(guān)資料,文中介紹的非常詳細(xì),相信對(duì)大家的學(xué)習(xí)或者工作具有一定的參考價(jià)值,需要的朋友們下面來(lái)一起看看吧。

前言

最近寫的 Swift 項(xiàng)目里要實(shí)現(xiàn)一個(gè)聊天界面,在處理鍵盤彈出的時(shí)候遇到了一點(diǎn)麻煩。

麻煩就在于鍵盤彈出后如何處理屏幕和鍵盤的關(guān)系

經(jīng)過(guò)一番死磕,終于做出了想要的效果,效果如下:

注:原本項(xiàng)目是 Swift 2.3 寫的,為了寫這篇博客,用 Swift 3.1 重新實(shí)現(xiàn)了一遍。

感受:方法名真的縮短了不少,😁


分析

現(xiàn)在開始,就讓我來(lái)分析一下這次死磕歷程。

一開始想到了兩種處理方法,一種是 鍵盤彈出消失的同時(shí),輸入欄隨著鍵盤移動(dòng),一種是 鍵盤彈出消失時(shí),整個(gè)屏幕隨著鍵盤移動(dòng),這兩種方法都有弊端,讓我們分類討論下:

1. 輸入欄隨著鍵盤移動(dòng)

  • 當(dāng)消息條數(shù)較少時(shí),鍵盤不會(huì)遮擋住消息
  • 消息條數(shù)多了以后,鍵盤會(huì)遮擋住屏幕中處在鍵盤位置的消息
  • 每次發(fā)送了新的消息,用戶無(wú)法及時(shí)看到(因?yàn)楸绘I盤遮住了)

結(jié)論:體驗(yàn)不好

2. 屏幕隨著鍵盤移動(dòng)

  • 消息多了以后,能在屏幕上及時(shí)看到最新的消息
  • 但消息少的時(shí)候,由于鍵盤把整個(gè) view 頂出屏幕,用戶看不到這頭幾條消息
  • 當(dāng)消息沒(méi)有占滿整個(gè)屏幕的時(shí)候,鍵盤把 view 頂上去,view 底部會(huì)留下一段空白

結(jié)論:還是體驗(yàn)不好

上述兩種情況的圖片我就不發(fā)了,大家自己腦補(bǔ)一下

那么作為強(qiáng)迫癥,怎么能容忍這種不好的體驗(yàn)?于是開始死磕,首先參考了下日常使用最多的微信、qq,分情況總結(jié)了一下微信、qq里鍵盤彈出的效果

  • 情況一:消息較少時(shí)(當(dāng)鍵盤彈出不會(huì)遮擋住消息)聊天界面不動(dòng),鍵盤彈出時(shí)只有輸入欄上滑,這樣保證了最開始的幾條消息能完整顯示
  • 情況二:消息較多但還未占滿屏幕時(shí)(當(dāng)鍵盤彈出會(huì)遮擋住部分消息),鍵盤彈出時(shí)輸入欄上滑,同時(shí)聊天界面也上滑。注意:此時(shí)輸入欄上滑的距離為鍵盤高度,聊天界面上滑距離為鍵盤可能遮擋住消息的高度
  • 情況三:消息占滿或超出屏幕時(shí),鍵盤彈出時(shí)整個(gè) view 上滑
  • 這其中還包括了發(fā)送消息時(shí),聊天界面上滑,保證最后一條消息顯示在鍵盤上方的處理。

如果大家不方便腦補(bǔ),直接掏出手機(jī),用微信或qq和女神聊個(gè)天吧

下面,我們放出代碼分析:

布局

首先導(dǎo)入 SnapKit 布局框架,對(duì)聊天界面和輸入欄進(jìn)行約束

由于我懶,怎么使用 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)
}

這里讓聊天界面的底部和輸入欄的上方貼合

監(jiān)聽

監(jiān)聽鍵盤的彈出和消失

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)

當(dāng)鍵盤彈出時(shí),會(huì)觸發(fā) keyBoardWillShow(notification:) 方法,鍵盤消失時(shí),會(huì)觸發(fā) keyBoardWillHide(notification:) 方法,我們很多復(fù)雜的邏輯,都要在這兩個(gè)方法中實(shí)現(xiàn)。另外,Swift 3.1 的版本中,把很多方法的 NS 前綴去除了,所以還在用 Swift 2.3 的童鞋,在NotificationCenter 前面加上 NS 前綴就可以了。

下面重頭戲來(lái)了,實(shí)現(xiàn)上述三種情況的效果

效果

彈出動(dòng)畫

想要 view 隨著鍵盤彈出上滑,需要得到鍵盤的高度和鍵盤彈出動(dòng)畫的時(shí)間,這里我們通過(guò)如下代碼得到:

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

 // 得到鍵盤彈出所需時(shí)間
 let duration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber
 mKeyBoardAnimateDuration = duration.doubleValue

 ...
}

然后實(shí)現(xiàn)動(dòng)畫

之前在實(shí)現(xiàn)輸入欄隨著鍵盤彈出的時(shí)候,嘗試過(guò)兩種寫法:

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)

但最后發(fā)現(xiàn),由于滑動(dòng)的速度不一樣,會(huì)造成鍵盤彈出和輸入欄上滑時(shí)出現(xiàn)縫隙。一句話,體驗(yàn)不好。

于是去網(wǎng)上找了一種方法(必須要感謝下那位大哥),利用一個(gè)動(dòng)畫的 options,和 view 的 transform 方法完美解決問(wèn)題。讓 view 和鍵盤滑動(dòng)時(shí)無(wú)縫貼合、如絲般順滑。

方法如下:

處理所需的動(dòng)畫

var animate: (()->Void) = {
 self.toolBarView.transform = CGAffineTransform(translationX: 0, y: -keyBoardHeight)
}

創(chuàng)建動(dòng)畫 options

let options = UIViewAnimationOptions(rawValue: 
UInt((userInfo[UIKeyboardAnimationCurveUserInfoKey] 
as! NSNumber).intValue << 16))

實(shí)現(xiàn)動(dòng)畫

UIView.animate(withDuration: mKeyBoardAnimateDuration,
 delay: 0, options: options, animations: animate)

如此這般,大功告成!親個(gè)嘴兒 😙

現(xiàn)在有了絲滑的滑動(dòng)效果,我們來(lái)處理上述分析的三種情況

定義情況

首先定義效果枚舉類型,枚舉的好處就不贅述了

enum AnimateType {
 case animate1 // 鍵盤彈出的話不會(huì)遮擋消息
 case animate2 // 鍵盤彈出的話會(huì)遮擋消息,但最后一條消息距離輸入框有一段距離
 case animate3 // 最后一條消息距離輸入框在小范圍內(nèi),這里設(shè)為 30
}

枚舉類型對(duì)應(yīng)了上述分析的三種效果

讓我們回顧一下三種情況

  • 情況一:消息較少時(shí)(當(dāng)鍵盤彈出不會(huì)遮擋住消息)聊天界面不動(dòng),鍵盤彈出時(shí)只有輸入欄上滑,這樣保證了最開始的幾條消息能完整顯示
  • 情況二:消息較多但還未占滿屏幕時(shí)(當(dāng)鍵盤彈出會(huì)遮擋住部分消息),鍵盤彈出時(shí)輸入欄上滑,同時(shí)聊天界面也上滑。注意:此時(shí)輸入欄上滑的距離為鍵盤高度,聊天界面上滑距離為鍵盤可能遮擋住消息的高度
  • 情況三:消息占滿或超出屏幕時(shí),鍵盤彈出時(shí)整個(gè) view 上滑

實(shí)現(xiàn)

當(dāng)消息數(shù)量為 0 時(shí),默認(rèn)動(dòng)畫為輸入框滑動(dòng)

var animate: (()->Void) = {
 self.toolBarView.transform = CGAffineTransform(translationX: 0, y: -keyBoardHeight)
}

當(dāng)消息數(shù)量不為 0 時(shí),需要進(jìn)行計(jì)算判斷情況

首先得到最后一條消息在屏幕的位置,其中 cellDistance 就是最后一條消息相對(duì)于當(dāng)前屏幕的 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

限定兩個(gè)位置 distance1 和 distance2

distance1 代表彈出鍵盤后鍵盤頂部的位置相對(duì)于當(dāng)前屏幕的 y 值,對(duì)應(yīng)第一和第二種情況的判斷,distance2 代表未彈出鍵盤時(shí)輸入框頂部的位置當(dāng)對(duì)于當(dāng)前屏幕的 y 值。

let distance1 = SCREEN_HEIGHT - toolBarHeight - keyBoardHeight
let distance2 = SCREEN_HEIGHT - toolBarHeight - 2 * fitBlank

計(jì)算出最后一條消息的位置和限定 distance1 的差值

這樣,當(dāng)處于第二種情況時(shí),輸入框上滑距離為鍵盤高度,聊天界面上滑距離為計(jì)算出的差值,完美實(shí)現(xiàn)對(duì)應(yīng)效果

對(duì)應(yīng)代碼如下:

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 //這里記錄下最后一次滑動(dòng)的dif值,以后有用
 }
 animateType = .animate2
} else {
 animate = {
  self.view.transform = CGAffineTransform(translationX: 0, y: -keyBoardHeight)
 }
 animateType = .animate3
}

以上代碼都發(fā)生在 keyBoardWillShow(notification: Notification) 中,每次判斷完動(dòng)畫的情況后,記錄下動(dòng)畫情況,然后當(dāng)鍵盤消失時(shí),在 keyBoardWillHide(notification: Notification) 中還原

代碼如下:

// 返回 view 或 toolBarView 或 chatTableView 到原有狀態(tài)
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
 }
}

如此這般,就實(shí)現(xiàn)了三種滑動(dòng)的效果。但是別急,問(wèn)題又來(lái)了。在情況一和情況二中,聊天界面上滑,怎么保證最后一條消息顯示在鍵盤上方呢?

這就需要我們?cè)诎l(fā)送完消息后,刷新列表的方法中進(jìn)行處理,這里貼出整個(gè)刷新列表方法

實(shí)現(xiàn)思路為:

  • 處于情況三時(shí),由于之前約束了聊天界面在輸入欄上方,并且整個(gè)界面一起上滑,約束依舊成立,只需把聊天界面最后一條消息滾動(dòng)到聊天界面底部
  • 處于情況一和情況二時(shí),如果聊天界面上滑的總距離(lastDifY + difY)小于鍵盤高度,則可以繼續(xù)上滑,上滑距離為新增消息的高度
  • 一旦聊天界面上滑的總距離將要超過(guò)鍵盤高度,則上滑總距離設(shè)為鍵盤高度,如果聊天界面上滑的總距離超過(guò)鍵盤高度,界面上會(huì)出現(xiàn)多余的空白
  • 一旦聊天界面上滑的總距離為鍵盤高度,則按照情況三處理

費(fèi)盡唇舌,可能還是說(shuō)不清楚,所以上代碼吧😭:

// 刷新列表
 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

 // 計(jì)算鍵盤可能遮住的消息的長(zhǎng)度
 let difY = cellDistance - distance1


 if animateType == .animate3 {
  // 處于情況三時(shí),由于之前的約束(聊天界面在輸入欄上方),并且
  // 是整個(gè)界面一起上滑,所以約束依舊成立,只需把聊天界面最后
  // 一條消息滾動(dòng)到聊天界面底部即可
  scrollToBottom()
 } else if (animateType == .animate1 || animateType == .animate2) && difY > 0{
  // 在情況一和情況二中,如果聊天界面上滑的總距離小于鍵盤高度,則可以繼續(xù)上滑
  // 一旦聊天界面上滑的總距離 lastDifY + difY 將要超過(guò)鍵盤高度,則上滑總距離設(shè)為鍵盤高度
  // 此時(shí)執(zhí)行 trans 動(dòng)畫
  // 一旦聊天界面上滑總距離為鍵盤高度,則變?yōu)榍闆r三的情況,把聊天界面最后
  // 一條消息滾動(dòng)到聊天界面底部即可
  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()
  }
 }

 }

再貼一下滾動(dòng)最后一條消息到聊天界面底部的代碼:

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

總結(jié)

開局只是想簡(jiǎn)單實(shí)現(xiàn)聊天效果,沒(méi)想到因?yàn)閺?qiáng)迫癥和實(shí)現(xiàn)優(yōu)秀的體驗(yàn),在鍵盤效果上死磕了許久。前后共花了一天半時(shí)間,當(dāng)真是茶飯不思,夜不能寐。中間嘗試了無(wú)數(shù)滑動(dòng)方法,在筆記本上畫圖模擬各種情況,最終做出來(lái)后,就像那啥之后,整個(gè)人瞬間疲軟了,迫不及待地睡了一覺(jué),但內(nèi)心卻是無(wú)比激動(dòng)。

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • Swift如何在應(yīng)用中添加圖標(biāo)更換功能的方法

    Swift如何在應(yīng)用中添加圖標(biāo)更換功能的方法

    本篇文章主要介紹了Swift如何在應(yīng)用中添加圖標(biāo)更換功能的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-02-02
  • 詳細(xì)講解Swift中的類型占位符

    詳細(xì)講解Swift中的類型占位符

    類型占位符是swift5.6中引?的?個(gè)新功能,在處理稍微復(fù)雜的通?類型時(shí),它可能真的很有用,這篇文章主要給大家介紹了關(guān)于Swift類型占位符的相關(guān)資料,需要的朋友可以參考下
    2022-06-06
  • Swift實(shí)現(xiàn)表格視圖單元格單選(1)

    Swift實(shí)現(xiàn)表格視圖單元格單選(1)

    這篇文章主要為大家詳細(xì)介紹了Swift實(shí)現(xiàn)表格視圖單元格單選,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • Swift如何為網(wǎng)頁(yè)承載頁(yè)面添加更多功能詳解

    Swift如何為網(wǎng)頁(yè)承載頁(yè)面添加更多功能詳解

    這篇文章主要給大家介紹了關(guān)于Swift如何為網(wǎng)頁(yè)承載頁(yè)面添加更多功能的相關(guān)資料,包括添加菊花加載的效果、添加跳轉(zhuǎn)到Safari的功能、添加復(fù)制鏈接的功能以及添加分享網(wǎng)頁(yè)的功能,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2018-05-05
  • Swift如何為設(shè)置中心添加常用功能

    Swift如何為設(shè)置中心添加常用功能

    這篇文章主要給大家介紹了關(guān)于Swift如何為設(shè)置中心添加常用功能的相關(guān)資料,包含了跳轉(zhuǎn)到AppStore、郵件反饋功能、系統(tǒng)分享功能以及打開某些網(wǎng)址等功能,需要的朋友可以參考借鑒,下面來(lái)一起看看吧
    2018-05-05
  • 淺談在Swift中關(guān)于函數(shù)指針的實(shí)現(xiàn)

    淺談在Swift中關(guān)于函數(shù)指針的實(shí)現(xiàn)

    這篇文章主要介紹了淺談在Swift中關(guān)于函數(shù)指針的實(shí)現(xiàn),是作者根據(jù)C語(yǔ)言的指針特性在Swifft中做出的一個(gè)實(shí)驗(yàn),需要的朋友可以參考下
    2015-07-07
  • Swift中Optional值的鏈?zhǔn)秸{(diào)用學(xué)習(xí)筆記

    Swift中Optional值的鏈?zhǔn)秸{(diào)用學(xué)習(xí)筆記

    這篇文章主要介紹了Swift中Optional值的鏈?zhǔn)秸{(diào)用學(xué)習(xí)筆記,Optional鏈?zhǔn)荢wift入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下
    2016-07-07
  • 深入理解Swift中的訪問(wèn)控制關(guān)鍵字

    深入理解Swift中的訪問(wèn)控制關(guān)鍵字

    這篇文章主要給大家介紹了Swift中訪問(wèn)控制關(guān)鍵字的相關(guān)資料,文中介紹的非常詳細(xì),對(duì)大家具有一定的參考價(jià)值,需要的朋友們下面來(lái)一起看看吧。
    2017-03-03
  • Swift使用表格組件實(shí)現(xiàn)單列表

    Swift使用表格組件實(shí)現(xiàn)單列表

    這篇文章主要為大家詳細(xì)介紹了Swift使用表格組件實(shí)現(xiàn)單列表,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • Swift實(shí)現(xiàn)JSON轉(zhuǎn)Model的方法及HandyJSON使用講解

    Swift實(shí)現(xiàn)JSON轉(zhuǎn)Model的方法及HandyJSON使用講解

    這篇文章給大家介紹了Swift實(shí)現(xiàn)JSON轉(zhuǎn)Model的方法及HandyJSON使用講解,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧
    2017-07-07

最新評(píng)論

辽中县| 南通市| 略阳县| 左权县| 朔州市| 津南区| 仪陇县| 恩平市| 大英县| 江北区| 兴隆县| 龙南县| 高唐县| 交城县| 鄂州市| 茂名市| 杭锦旗| 墨江| 宜宾县| 宁国市| 高阳县| 右玉县| 资溪县| 兴海县| 临江市| 定州市| 手游| 饶河县| 和顺县| 淮南市| 平邑县| 垣曲县| 昔阳县| 永吉县| 吴桥县| 婺源县| 克东县| 余姚市| 页游| 宜兰县| 赤峰市|