自定義表情視圖(collectionView+自定義UICollectionViewFlowLayout)
image.png
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