Preface
之前分享了UITextView的圖片混排, 現(xiàn)在輪到定制表情鍵盤的實(shí)現(xiàn).
表情鍵盤在一些IM上用的很多, 如微信, QQ, 微博等. 這個(gè)demo是基于發(fā)布微博信息里的一個(gè)功能.
先看效果圖:

自定制表情鍵盤
那么如何定制表情鍵盤呢?
- 切換系統(tǒng)鍵盤, 把當(dāng)前鍵盤收起來, 執(zhí)行resignFistResponder方法
- 設(shè)置textView的inputView屬性, 更改為自定義的view
- 彈出自定制鍵盤, 執(zhí)行becomeFirstResponder
/// 切換鍵盤
@objc fileprivate func emotionKeyboard() {
let emotionKeyboard = WBEmotionKeyBoard(frame: CGRect(x: 0, y: 0, width: screenWidth, height: 271))
emotionKeyboard.backgroundColor = UIColor.white
//要想切換鍵盤, 首先需要將當(dāng)前的鍵盤收起來
//收起鍵盤
//becomeFirstResponder: 彈出鍵盤, 把光標(biāo)定位到當(dāng)前控件
//收起鍵盤后,要迅速彈出鍵盤, 會(huì)產(chǎn)生兩次動(dòng)畫, 讓第一次動(dòng)畫不執(zhí)行
shouldAnimation = false
textView.resignFirstResponder()
shouldAnimation = true
//如果是默認(rèn)鍵盤, 彈出自定義鍵盤
if isDefaultKeyboard {
//使用自定義的鍵盤
textView.inputView = emotionKeyboard
isDefaultKeyboard = false
//如果是自定義鍵盤, 彈出系統(tǒng)鍵盤
} else {
textView.resignFirstResponder()
textView.inputView = nil
isDefaultKeyboard = true
}
//彈出鍵盤
textView.becomeFirstResponder()
}
表情鍵盤實(shí)現(xiàn)的思維導(dǎo)圖

表情鍵盤實(shí)現(xiàn)的思維導(dǎo)圖
簡單分析具體實(shí)現(xiàn) (UI)
從上面的思維導(dǎo)圖可知, 表情鍵盤view分為三個(gè)模塊:
- collectionView
- 四個(gè)section(表情組)
- 每個(gè)section有多個(gè)cell
- 自定義cell(20個(gè)表情的button, 還有一個(gè)deleteButton)
- pageControl
- 用KVC的方式設(shè)置pageControl的顯示樣式
- setValueForkey(_currentPageImage)
- setValueForkey(_pageImage)
- toolBar
- 四個(gè)button水平分布
- 使用UIStackView(專門做平均分布用的, ios9.0以后出來的)
數(shù)據(jù)源使用了一個(gè)emotions.bundle, 創(chuàng)建一個(gè)model, 三維數(shù)組存放數(shù)據(jù)
collectionView, pageControl 和 toolBar三者的聯(lián)動(dòng)
- 點(diǎn)擊toolBar做任意一個(gè)button時(shí), pageControl和collectionView的聯(lián)動(dòng)
- collectionView上的cell在向左向右滑動(dòng)時(shí), pageControl和toolBar的聯(lián)動(dòng)
第一種聯(lián)動(dòng)實(shí)現(xiàn)起來比較簡單, 就是在點(diǎn)擊toolBar的一個(gè)button時(shí), 利用代理把當(dāng)前被點(diǎn)擊的button tag值傳遞出去, 讓pageControl的當(dāng)前頁為0, collectionViewCell的indexPath.section為tag值, item為0
toolBar四個(gè)button點(diǎn)擊觸發(fā)的方法如下:
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的聯(lián)動(dòng),順便完成toolBar與pageControl的聯(lián)動(dòng)
emtionCollectionView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition.left, animated: false)
changePageContol(indexPath: indexPath)
}
}
第二種聯(lián)動(dòng)實(shí)現(xiàn)則比較復(fù)雜, 用到一個(gè)小技巧:
在scrollViewDidScroll方法里, 計(jì)算可見的兩個(gè)cell的origin.x與當(dāng)前collectionView的.contentOffset.x相減的絕對值進(jìn)行比較
offset與originx的差的絕對值越小, 則顯示的區(qū)域越大
// MARK: - UICollectionViewDelegate
extension WBEmotionKeyBoard: UICollectionViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//獲得顯示的cells
let cells = emtionCollectionView.visibleCells
//如果屏幕上顯示的cell的cell有兩個(gè)
if cells.count > 1 {
let offset = scrollView.contentOffset.x
//第一個(gè)cell,顯示的區(qū)域
let cellOne = cells[0]
//offset與origin.x的絕對值
let regionOne = abs(cellOne.frame.origin.x - offset)
//第一個(gè)cell的indexPath
let indexPathOne = emtionCollectionView.indexPath(for: cellOne)
//第二個(gè)cell
let cellTwo = cells[1]
//offset與origin.x的絕對值
let regionTwo = abs(cellTwo.frame.origin.x - offset)
//第二個(gè)cell的indexPath
let indexPathTwo = emtionCollectionView.indexPath(for: cellTwo)
//offset與originx的差的絕對值越小, 則顯示的區(qū)域越大
if regionOne < regionTwo {
//使用cellOne的section
toolBar.index = (indexPathOne?.section)!
changePageContol(indexPath: indexPathOne!)
} else {
//使用cellTwo的section
toolBar.index = (indexPathTwo?.section)!
changePageContol(indexPath: indexPathTwo!)
}
}
}
}

定制鍵盤的文件目錄
有興趣的同學(xué)可以看demo的代碼實(shí)現(xiàn)
My github