前言

最近写的 Swift 项目里要实现一个聊天界面,在处理键盘弹出的时候遇到了一点麻烦。
麻烦就在于键盘弹出后如何处理屏幕和键盘的关系
经过一番死磕,终于做出了想要的效果,效果如下:
注:原本项目是 Swift 2.3 写的,为了写这篇博客,用 Swift 3.1 重新实现了一遍。
感受:方法名真的缩短了不少,😁
分析
现在开始,就让我来分析一下这次死磕历程。
一开始想到了两种处理方法,一种是 键盘弹出消失的同时,输入栏随着键盘移动,一种是 键盘弹出消失时,整个屏幕随着键盘移动,这两种方法都有弊端,让我们分类讨论下:
1. 输入栏随着键盘移动
结论:体验不好
2. 屏幕随着键盘移动
结论:还是体验不好
上述两种情况的图片我就不发了,大家自己脑补一下
那么作为强迫症,怎么能容忍这种不好的体验?于是开始死磕,首先参考了下日常使用最多的微信、qq,分情况总结了一下微信、qq里键盘弹出的效果
如果大家不方便脑补,直接掏出手机,用微信或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
}
枚举类型对应了上述分析的三种效果
让我们回顾一下三种情况
实现
当消息数量为 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
}
}
如此这般,就实现了三种滑动的效果。但是别急,问题又来了。在情况一和情况二中,聊天界面上滑,怎么保证最后一条消息显示在键盘上方呢?
这就需要我们在发送完消息后,刷新列表的方法中进行处理,这里贴出整个刷新列表方法
实现思路为:
费尽唇舌,可能还是说不清楚,所以上代码吧😭:
// 刷新列表
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./201704/yuanma/NbChatView-Swift-master().rar
总结
开局只是想简单实现聊天效果,没想到因为强迫症和实现优秀的体验,在键盘效果上死磕了许久。前后共花了一天半时间,当真是茶饭不思,夜不能寐。中间尝试了无数滑动方法,在笔记本上画图模拟各种情况,最终做出来后,就像那啥之后,整个人瞬间疲软了,迫不及待地睡了一觉,但内心却是无比激动。
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。
# swift
# 聊天界面
# 3.1
# 键盘监听
# iOS开发中Swift3 监听UITextView文字改变的方法(三种方法)
# Swift让输入框跟随键盘弹起避免输入输入法挡住输入框问题
# swift3.0键盘弹起遮挡输入框问题的解决方案
# Swift实现监听键盘通知及一些处理详解
# 弹出
# 栏上
# 三种
# 输入框
# 占满
# 两种
# 设为
# 少时
# 几条
# 死磕
# 让我们
# 就不
# 只需
# 所需
# 不动
# 大功告成
# 较多
# 还未
# 如此这般
# 则可
相关文章:
如何快速辨别茅台真假?关键步骤解析
如何通过宝塔面板实现本地网站访问?
在线ppt制作网站有哪些软件,如何把网页的内容做成ppt?
,想在网上投简历,哪几个网站比较好?
C++如何使用std::optional?(处理可选值)
香港网站服务器数量如何影响SEO优化效果?
公司门户网站制作公司有哪些,怎样使用wordpress制作一个企业网站?
非常酷的网站设计制作软件,酷培ai教育官方网站?
制作网站建设的公司有哪些,网站建设比较好的公司都有哪些?
建站之星多图banner生成与模板自定义指南
制作网站的网址是什么,请问后缀为.com和.com.cn还有.cn的这三种网站是分别是什么类型的网站?
网站制作外包价格怎么算,招聘网站上写的“外包”是什么意思?
网站制作话术技巧,网站推广做的好怎么话术?
如何通过VPS搭建网站快速盈利?
再谈Python中的字符串与字符编码(推荐)
用v-html解决Vue.js渲染中html标签不被解析的问题
制作ppt免费网站有哪些,有哪些比较好的ppt模板下载网站?
外贸公司网站制作哪家好,maersk船公司官网?
如何撰写建站申请书?关键要点有哪些?
C++中的Pimpl idiom是什么,有什么好处?(隐藏实现)
简单实现Android文件上传
新网站制作渠道有哪些,跪求一个无线渠道比较强的小说网站,我要发表小说?
微信小程序制作网站有哪些,微信小程序需要做网站吗?
黑客如何通过漏洞一步步攻陷网站服务器?
网站广告牌制作方法,街上的广告牌,横幅,用PS还是其他软件做的?
如何快速查询网址的建站时间与历史轨迹?
如何选择适合PHP云建站的开源框架?
西安市网站制作公司,哪个相亲网站比较好?西安比较好的相亲网站?
实现虚拟支付需哪些建站技术支撑?
如何选择服务器才能高效搭建专属网站?
如何在云服务器上快速搭建个人网站?
建站之星导航配置指南:自助建站与SEO优化全解析
唐山网站制作公司有哪些,唐山找工作哪个网站最靠谱?
娃派WAP自助建站:免费模板+移动优化,快速打造专业网站
常州企业建站如何选择最佳模板?
建站主机选虚拟主机还是云服务器更好?
ui设计制作网站有哪些,手机UI设计网址吗?
定制建站哪家更专业可靠?推荐榜单揭晓
如何快速搭建支持数据库操作的智能建站平台?
如何选择可靠的免备案建站服务器?
建站之星图片链接生成指南:自助建站与智能设计教程
招贴海报怎么做,什么是海报招贴?
如何通过虚拟主机空间快速建站?
长沙企业网站制作哪家好,长沙水业集团官方网站?
英语简历制作免费网站推荐,如何将简历翻译成英文?
深圳网站制作公司好吗,在深圳找工作哪个网站最好啊?
保定网站制作方案定制,保定招聘的渠道有哪些?找工作的人一般都去哪里看招聘信息?
建站中国官网:模板定制+SEO优化+建站流程一站式指南
如何在万网ECS上快速搭建专属网站?
如何通过服务器快速搭建网站?完整步骤解析
*请认真填写需求信息,我们会在24小时内与您取得联系。