自定义表情视图(collectionView+自定义UICollectionViewFlowLayout)
HQEmjioView是定义的表情视图类,HQEmjioCell是collectionview的cell,HQEmjioTool是一些工具类的封装。
首先我们自定义一个表情键盘的思路就是
1、先把表情键盘页面写好,包括一些代理事件,表情布局等。
2、视图写好以后会去思考事件。包括表情点击如何展示在textView上,表情的添加和删除,点击发送如何把数据传给后台,以及如何将后台发来的数据还原。这里我们用到了一个很重要的属性NSAttributedString,通过富文本去实现这一系列的操作。
具体代码思路
1、懒加载表情视图
private lazy var emjioView:HQEmjioView? = HQEmjioView.emjioView()
2、监听键盘
func addKeyBoardNotification() {
let notif = NotificationCenter.default
notif.addObserver(self,
selector: #selector(keyboardWillChangeFrame),
name: .UIKeyboardWillChangeFrame,
object: nil)
}
3、键盘响应时做出相应的操作
/// 键盘响应通知
@objc func keyboardWillChangeFrame(_ note: Notification) {
guard let kbInfo = note.userInfo,
let duration = kbInfo[UIKeyboardAnimationDurationUserInfoKey] as? TimeInterval,
let kbFrameInfo = kbInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue else {
return
}
let kbFrame = kbFrameInfo.cgRectValue
let kbInset = UIScreen.main.bounds.size.height - (kbFrame.minY) // 键盘偏移量
let animationCurve = kbInfo[UIKeyboardAnimationCurveUserInfoKey] as! UInt
if kbInset > 0.0 {
UIView.animate(withDuration: duration, delay: 0.0,
options: UIViewAnimationOptions(rawValue: animationCurve),
animations: { () -> Void in
self.viewBottomConstrains.constant = -(kbInset)
self.view.layoutIfNeeded()
}, completion: {(finished) -> () in
})
} else {
self.viewBottomConstrains.constant = self.bottomLayoutGuide.length
}
}
4、表情按钮点击时切换键盘
@IBAction func emjioBtnClick(_ sender: UIButton) {
textView.resignFirstResponder()
sender.isSelected = !sender.isSelected
if sender.isSelected {
textView.inputView = self.emjioView
}else {
textView.inputView = nil
}
textView.becomeFirstResponder()
}
5、点击按钮让表情显示在textview上
func emjioSelect(attachment: NSAttributedString) {
/// 在插入表情时先判断是否有选择的字符,如果有选择则先删除选择的字符再进行插入操作
if textView.selectedRange.length > 0 {
textView.textStorage.deleteCharacters(in: textView.selectedRange)
textView.selectedRange = NSMakeRange(textView.selectedRange.location, 0)
}
textView.textStorage.insert(attachment, at: textView.selectedRange.location)
textView.selectedRange = NSMakeRange(textView.selectedRange.location + 1, textView.selectedRange.length)
textViewDidChange(textView)
}
// 将表情图片转化为可以显示的富文本 - HQEmjioTool.swift
func getAttachment(imageName:String,dic:(String,String)) -> NSAttributedString {
let attachment = HQTextAttachment()
attachment.text = dic.0
attachment.image = UIImage(named: imageName)
attachment.bounds = CGRect(x: 0, y: -4, width: 16, height: 16)
let attributedStr = NSAttributedString(attachment: attachment)
let muAtt = NSMutableAttributedString(attributedString: attributedStr)
muAtt.addAttribute(.font, value: UIFont.systemFont(ofSize: 14), range: NSRange(location: 0, length: muAtt.length))
return muAtt
}
6、删除表情、和TextView自适应大小
func emjioDelete() {
if textView.selectedRange.length > 0 {
let range = textView.selectedRange
textView.textStorage.deleteCharacters(in: range)
textView.selectedRange = NSMakeRange(range.location, 0)
} else if textView.selectedRange.location > 0 {
let range = NSMakeRange(textView.selectedRange.location - 1, 1)
textView.textStorage.deleteCharacters(in: range)
textView.selectedRange = NSMakeRange(range.location, 0)
}
textViewDidChange(textView)
}
// textView自适应大小
func textViewDidChange(_ textView: UITextView) {
var bounds = textView.bounds
let maxSize = CGSize(width: bounds.size.width, height: CGFloat.greatestFiniteMagnitude)
let newSize = textView.sizeThatFits(maxSize)
bounds.size.height = newSize.height
if bounds.size.height < MAX_HEIGHT {
viewHeightConstrains.constant = bounds.size.height > 36 ? bounds.size.height : 36
textView.isScrollEnabled = false
}else {
viewHeightConstrains.constant = MAX_HEIGHT
textView.isScrollEnabled = true
}
}
7、如何将系统键盘改为发送,并监听发送事件
textView.returnKeyType = .send
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == "\n" {
sendBtnClick()
return false
}
return true
}
8、如何将TextView中的属性文字以字符串的形式发送给后台(后台是不支持直接属性文字的)- HQEmjioTool.swift
// 将表情文字转化成后台能接收的文字
func plainText() -> (String) {
let plainStr = NSMutableString(string: string)
var base = 0
enumerateAttribute(NSAttributedStringKey.attachment,
in: NSMakeRange(0, length),
options: NSAttributedString.EnumerationOptions(rawValue: 0)) { (value, range, stop) -> Void in
if let attachment = value as? HQTextAttachment {
plainStr.replaceCharacters(in: NSMakeRange(range.location + base, range.length), with: attachment.text)
base += attachment.text.characters.count - 1
}
}
return (plainStr as String)
}
9、如何将后台发送回来的数据转化成属性文字再显示出来
// 将后台发过来的文字转化成能显示给用户的文字
func attributedText(withChatMsg content: String) -> NSMutableAttributedString {
let attrContent = NSMutableAttributedString(string: content)
do {
let regex = try NSRegularExpression(pattern: regulaPattern, options: .caseInsensitive)
let allMatches = regex.matches(in: content,
options: NSRegularExpression.MatchingOptions(rawValue: 0),
range: NSMakeRange(0, attrContent.length))
let resultAttrString = NSMutableAttributedString()
var range = NSMakeRange(0, 0)
for match in allMatches {
var emotionPath = ""
if match.range.location != range.location {
range.length = match.range.location - range.location
resultAttrString.append(attrContent.attributedSubstring(from: range))
}
range.location = match.range.location + match.range.length
if match.range.length > 0 {
let emot = attrContent.attributedSubstring(from: match.range)
for emotDic in emotions() {
if emotDic.0 == emot.string {
emotionPath = emotPath + emotDic.1
resultAttrString.append(getAttachment(imageName: emotionPath, dic: emotDic))
break
}
}
if emotionPath == "" {
resultAttrString.append(emot) // 找不到对应图像名称就直接加上去
}
}
}
if range.location != attrContent.length {
range.length = attrContent.length - range.location
resultAttrString.append(attrContent.attributedSubstring(from: range))
}
return resultAttrString
} catch let error {
print(error)
}
return attrContent
}
}
变成属性文字的过程。。。
具体如何自定义collectionview的表情键盘可以看我项目里面的代码。GitHub