Preface
之前分享了UITextView的图片混排, 现在轮到定制表情键盘的实现.
表情键盘在一些IM上用的很多, 如微信, QQ, 微博等. 这个demo是基于发布微博信息里的一个功能.
先看效果图:
那么如何定制表情键盘呢?
- 切换系统键盘, 把当前键盘收起来, 执行resignFistResponder方法
- 设置textView的inputView属性, 更改为自定义的view
- 弹出自定制键盘, 执行becomeFirstResponder
/// 切换键盘
@objc fileprivate func emotionKeyboard() {
let emotionKeyboard = WBEmotionKeyBoard(frame: CGRect(x: 0, y: 0, width: screenWidth, height: 271))
emotionKeyboard.backgroundColor = UIColor.white
//要想切换键盘, 首先需要将当前的键盘收起来
//收起键盘
//becomeFirstResponder: 弹出键盘, 把光标定位到当前控件
//收起键盘后,要迅速弹出键盘, 会产生两次动画, 让第一次动画不执行
shouldAnimation = false
textView.resignFirstResponder()
shouldAnimation = true
//如果是默认键盘, 弹出自定义键盘
if isDefaultKeyboard {
//使用自定义的键盘
textView.inputView = emotionKeyboard
isDefaultKeyboard = false
//如果是自定义键盘, 弹出系统键盘
} else {
textView.resignFirstResponder()
textView.inputView = nil
isDefaultKeyboard = true
}
//弹出键盘
textView.becomeFirstResponder()
}
表情键盘实现的思维导图
简单分析具体实现 (UI)
从上面的思维导图可知, 表情键盘view分为三个模块:
- collectionView
- 四个section(表情组)
- 每个section有多个cell
- 自定义cell(20个表情的button, 还有一个deleteButton)
- pageControl
- 用KVC的方式设置pageControl的显示样式
- setValueForkey(_currentPageImage)
- setValueForkey(_pageImage)
- toolBar
- 四个button水平分布
- 使用UIStackView(专门做平均分布用的, ios9.0以后出来的)
数据源使用了一个emotions.bundle, 创建一个model, 三维数组存放数据
collectionView, pageControl 和 toolBar三者的联动
- 点击toolBar做任意一个button时, pageControl和collectionView的联动
- collectionView上的cell在向左向右滑动时, pageControl和toolBar的联动
第一种联动实现起来比较简单, 就是在点击toolBar的一个button时, 利用代理把当前被点击的button tag值传递出去, 让pageControl的当前页为0, collectionViewCell的indexPath.section为tag值, item为0
toolBar四个button点击触发的方法如下:
extension WBEmotionToolBar {
@objc fileprivate func changeEmotion (button: UIButton) {
selectedButton?.isSelected = false
selectedButton = button
selectedButton?.isSelected = true
delegate?.changeEmotion(index: button.tag - baseTag)
}
}
代理方法:
// MARK: - WBEmotionToolBarDelegate
extension WBEmotionKeyBoard: WBEmotionToolBarDelegate {
func changeEmotion(index: Int) {
let indexPath = IndexPath(item: 0, section: index)
// toolBar与emotionCollectionView的联动,顺便完成toolBar与pageControl的联动
emtionCollectionView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition.left, animated: false)
changePageContol(indexPath: indexPath)
}
}
第二种联动实现则比较复杂, 用到一个小技巧:
在scrollViewDidScroll方法里, 计算可见的两个cell的origin.x与当前collectionView的.contentOffset.x相减的绝对值进行比较
offset与originx的差的绝对值越小, 则显示的区域越大
// MARK: - UICollectionViewDelegate
extension WBEmotionKeyBoard: UICollectionViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//获得显示的cells
let cells = emtionCollectionView.visibleCells
//如果屏幕上显示的cell的cell有两个
if cells.count > 1 {
let offset = scrollView.contentOffset.x
//第一个cell,显示的区域
let cellOne = cells[0]
//offset与origin.x的绝对值
let regionOne = abs(cellOne.frame.origin.x - offset)
//第一个cell的indexPath
let indexPathOne = emtionCollectionView.indexPath(for: cellOne)
//第二个cell
let cellTwo = cells[1]
//offset与origin.x的绝对值
let regionTwo = abs(cellTwo.frame.origin.x - offset)
//第二个cell的indexPath
let indexPathTwo = emtionCollectionView.indexPath(for: cellTwo)
//offset与originx的差的绝对值越小, 则显示的区域越大
if regionOne < regionTwo {
//使用cellOne的section
toolBar.index = (indexPathOne?.section)!
changePageContol(indexPath: indexPathOne!)
} else {
//使用cellTwo的section
toolBar.index = (indexPathTwo?.section)!
changePageContol(indexPath: indexPathTwo!)
}
}
}
}
有兴趣的同学可以看demo的代码实现
My github