372 lines
11 KiB
Swift
372 lines
11 KiB
Swift
//
|
|
// ChatKeyboardView.swift
|
|
// yinmeng-ios
|
|
//
|
|
// Created by MaiMang on 2024/2/29.
|
|
//
|
|
|
|
import UIKit
|
|
|
|
protocol ChatKeyboardViewDelegate: NSObjectProtocol {
|
|
/// 输入完消息
|
|
func keyboard(_ keyboard: ChatKeyboardView, DidFinish content: String)
|
|
/// 键盘收起/弹出
|
|
func keyboard(_ keyboard: ChatKeyboardView, DidBecome isBecome: Bool)
|
|
/// 键盘的y值
|
|
func keyboard(_ keyboard: ChatKeyboardView, DidObserver offsetY: CGFloat)
|
|
/// 菜单栏点击
|
|
func keyboard(_ keyboard: ChatKeyboardView, DidMoreMenu type: ChatMoreMenuType)
|
|
}
|
|
|
|
extension ChatKeyboardViewDelegate {
|
|
func keyboard(_ keyboard: ChatKeyboardView, DidFinish content: String) {}
|
|
func keyboard(_ keyboard: ChatKeyboardView, DidBecome isBecome: Bool) {}
|
|
func keyboard(_ keyboard: ChatKeyboardView, DidObserver offsetY: CGFloat) {}
|
|
func keyboard(_ keyboard: ChatKeyboardView, DidMoreMenu type: ChatMoreMenuType) {}
|
|
}
|
|
|
|
|
|
class ChatKeyboardView: UIView {
|
|
|
|
private let kSpace: CGFloat = 8.0
|
|
private let kViewWH: CGFloat = 36.0
|
|
private let kLineHeight: CGFloat = 0.75
|
|
|
|
// MARK: - var lazy
|
|
|
|
weak var delegate: ChatKeyboardViewDelegate?
|
|
|
|
fileprivate var toolBarHeight: CGFloat = 52.0
|
|
fileprivate var lastTextHeight: CGFloat = 34.0
|
|
fileprivate var keyboardHeight: CGFloat = 0.0
|
|
|
|
/// 底部菜单容器高度
|
|
fileprivate var contentHeight: CGFloat = 0.0
|
|
fileprivate var isShowMore = false
|
|
|
|
/// 是否弹出了系统键盘
|
|
fileprivate var isShowKeyboard = false
|
|
|
|
/// 更多按钮
|
|
fileprivate lazy var moreButton : UIButton = {
|
|
let button = UIButton(type: .custom)
|
|
let x: CGFloat = ScreenWidth - self.kViewWH - self.kSpace
|
|
button.frame = CGRect(x: x, y: self.kSpace, width: self.kViewWH, height: self.kViewWH)
|
|
button.setImage(UIImage(named: "chat_more"), for: .normal)
|
|
button.setImage(UIImage(named: "chat_more"), for: .highlighted)
|
|
button.addTarget(self, action: #selector(moreDidAction(_:)), for: .touchUpInside)
|
|
return button
|
|
}()
|
|
|
|
/// 文本输入框
|
|
fileprivate lazy var chatTextView: ChatGrowingTextView = {
|
|
let w: CGFloat = ScreenWidth - self.kViewWH * 2 - self.kSpace * 3 - self.kSpace
|
|
let textView = ChatGrowingTextView(frame: CGRect(x: self.kSpace, y: self.kSpace, width: w, height: self.kViewWH))
|
|
textView.placeholder = "请输入..."
|
|
textView.textColor = ThemeColor(hexStr: "#000000")
|
|
textView.maxNumberOfLines = 5
|
|
textView.delegate = self
|
|
textView.didTextChangedHeightClosure = { [weak self] height in
|
|
self?.changeKeyboardHeight(height: height)
|
|
}
|
|
return textView
|
|
}()
|
|
|
|
fileprivate lazy var topLineView: UIView = {
|
|
let lineView1 = UIView(frame: CGRect(x: 0, y: 0, width: ScreenWidth, height: kLineHeight))
|
|
lineView1.backgroundColor = ThemeColor(hexStr: "#E5E5E5")
|
|
return lineView1
|
|
}()
|
|
|
|
fileprivate lazy var bottomLineView: UIView = {
|
|
let lineView2 = UIView(frame: CGRect(x: 0, y: self.toolBarHeight - kLineHeight, width: ScreenWidth, height: kLineHeight))
|
|
lineView2.backgroundColor = ThemeColor(hexStr: "#E5E5E5")
|
|
return lineView2
|
|
}()
|
|
|
|
fileprivate lazy var toolBarView: UIView = {
|
|
let view = UIView(frame: CGRect(x: 0, y: 0, width: ScreenWidth, height: self.toolBarHeight))
|
|
view.backgroundColor = ThemeColor(hexStr: "#F7F7F7")
|
|
return view
|
|
}()
|
|
|
|
/// 底部背景容器
|
|
fileprivate lazy var contentView: UIView = {
|
|
let y = self.toolBarView.maxY
|
|
let view = UIView(frame: CGRect(x: 0, y: y, width: ScreenWidth, height: self.contentHeight))
|
|
view.backgroundColor = ThemeColor(hexStr: "#F8F8F8")
|
|
return view
|
|
}()
|
|
|
|
/// 更多菜单
|
|
fileprivate lazy var moreMenuView: ChatMoreMenuView = {
|
|
let view = ChatMoreMenuView(frame: self.contentView.bounds)
|
|
view.isHidden = true
|
|
view.delegate = self
|
|
return view
|
|
}()
|
|
|
|
// MARK: - life cycle
|
|
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
|
|
setupKeyboardView()
|
|
registerNotification()
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
super.init(coder: aDecoder)
|
|
|
|
setupKeyboardView()
|
|
registerNotification()
|
|
}
|
|
|
|
func setupKeyboardView() {
|
|
self.backgroundColor = ThemeColor(hexStr: "#F7F7F7")
|
|
self.isUserInteractionEnabled = true
|
|
|
|
addSubview(toolBarView)
|
|
toolBarView.addSubview(moreButton)
|
|
toolBarView.addSubview(chatTextView)
|
|
toolBarView.addSubview(topLineView)
|
|
toolBarView.addSubview(bottomLineView)
|
|
|
|
addSubview(contentView)
|
|
contentView.addSubview(moreMenuView)
|
|
}
|
|
|
|
// MARK: - 监听键盘通知
|
|
private func registerNotification() {
|
|
// 监听键盘弹出通知
|
|
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)),
|
|
name:UIResponder.keyboardWillShowNotification,object: nil)
|
|
// 监听键盘隐藏通知
|
|
NotificationCenter.default.addObserver(self,selector: #selector(keyboardWillHide(_:)),
|
|
name: UIResponder.keyboardWillHideNotification, object: nil)
|
|
|
|
// 主要是为了获取点击空白处回收键盘的处理
|
|
NotificationCenter.default.addObserver(self,selector: #selector(keyboardNeedHide),
|
|
name: .kChatTextKeyboardNeedHide, object: nil)
|
|
// 添加KVO监听输入键盘y值
|
|
addObserver(self, forKeyPath: "frame", options: [.new, .old], context: nil)
|
|
}
|
|
|
|
// MARK: - KVO监听
|
|
|
|
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
|
if keyPath == "frame" && ((object as? UIView) != nil) {
|
|
if let changeObject = change.value {
|
|
if let newFrame = changeObject[.newKey] as? CGRect {
|
|
delegate?.keyboard(self, DidObserver: newFrame.origin.y)
|
|
print("y值发生改变\(newFrame.origin.y)")
|
|
}
|
|
}
|
|
}else {
|
|
|
|
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
|
|
self.removeObserver(self, forKeyPath: "frame")
|
|
}
|
|
}
|
|
|
|
|
|
// MARK: - ChatMoreMenuViewDelegate
|
|
|
|
extension ChatKeyboardView: ChatMoreMenuViewDelegate {
|
|
|
|
func menu(_ view: ChatMoreMenuView, DidSelected type: ChatMoreMenuType) {
|
|
|
|
delegate?.keyboard(self, DidMoreMenu: type)
|
|
}
|
|
}
|
|
|
|
// MARK: - UITextViewDelegate
|
|
|
|
extension ChatKeyboardView: UITextViewDelegate {
|
|
|
|
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
|
// 发送键&回车键处理
|
|
if (text == "\n") {
|
|
if (isShowKeyboard) {
|
|
isShowKeyboard = true
|
|
}
|
|
|
|
sendChatMessage()
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
|
|
/// 发送消息内容
|
|
private func sendChatMessage() {
|
|
delegate?.keyboard(self, DidFinish: self.chatTextView.text ?? "")
|
|
|
|
changeKeyboardHeight(height: lastTextHeight)
|
|
chatTextView.text = ""
|
|
chatTextView.attributedText = NSAttributedString(string: "")
|
|
}
|
|
}
|
|
|
|
|
|
// MARK: - KeyBoard Manager
|
|
|
|
extension ChatKeyboardView {
|
|
|
|
/// 键盘将要显示
|
|
@objc func keyboardWillShow(_ noti: NSNotification) {
|
|
guard let userInfo = noti.userInfo else {
|
|
return
|
|
}
|
|
|
|
contentHeight = 0
|
|
delegate?.keyboard(self, DidBecome: true)
|
|
|
|
let duration = userInfo["UIKeyboardAnimationDurationUserInfoKey"] as! Double
|
|
let endFrame = (userInfo["UIKeyboardFrameEndUserInfoKey"] as! NSValue).cgRectValue
|
|
let y = endFrame.origin.y
|
|
// 获取键盘的高度
|
|
keyboardHeight = endFrame.height
|
|
// 键盘弹出状态
|
|
isShowKeyboard = true
|
|
|
|
let option = userInfo["UIKeyboardAnimationCurveUserInfoKey"] as! Int
|
|
|
|
var changedY = y - self.toolBarHeight - NavHeight - contentHeight
|
|
if (isShowMore) { //显示系统键盘
|
|
isShowMore = false
|
|
self.moreMenuView.isHidden = true
|
|
self.moreMenuView.hidePageController = true
|
|
changedY = y - self.toolBarHeight - NavHeight
|
|
}
|
|
|
|
UIView.animate(withDuration: duration, delay: 0, options: UIView.AnimationOptions(rawValue: UIView.AnimationOptions.RawValue(option)), animations: {
|
|
self.frame = CGRect(x: 0, y: changedY, width: ScreenWidth, height: self.toolBarHeight + self.contentHeight)
|
|
}, completion: nil)
|
|
}
|
|
|
|
/// 键盘将要消失
|
|
@objc func keyboardWillHide(_ noti: NSNotification) {
|
|
guard let userInfo = noti.userInfo else {
|
|
return
|
|
}
|
|
|
|
delegate?.keyboard(self, DidBecome: false)
|
|
|
|
let duration = userInfo["UIKeyboardAnimationDurationUserInfoKey"] as! Double
|
|
let endFrame = (userInfo["UIKeyboardFrameEndUserInfoKey"] as! NSValue).cgRectValue
|
|
//let y = endFrame.origin.y
|
|
// 获取键盘的高度
|
|
keyboardHeight = endFrame.height
|
|
// 键盘弹出状态
|
|
isShowKeyboard = false
|
|
|
|
let option = userInfo["UIKeyboardAnimationCurveUserInfoKey"] as! Int
|
|
let changedY = ScreenHeight - NavHeight - self.toolBarHeight - SafeAraeBottomHeight - self.contentHeight
|
|
UIView.animate(withDuration: duration, delay: 0, options: UIView.AnimationOptions(rawValue: UIView.AnimationOptions.RawValue(option)), animations: {
|
|
self.frame = CGRect(x: 0, y: changedY, width: ScreenWidth, height: self.toolBarHeight + self.contentHeight)
|
|
}, completion: nil)
|
|
}
|
|
|
|
@objc func keyboardNeedHide(_ noti: NSNotification) {
|
|
|
|
chatTextView.resignFirstResponder()
|
|
moreMenuView.hidePageController = true
|
|
|
|
contentHeight = 0.0
|
|
restToolbarContentHeight(true)
|
|
|
|
delegate?.keyboard(self, DidBecome: false)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// MARK: - Action
|
|
|
|
extension ChatKeyboardView {
|
|
/// 更多按钮点击处理
|
|
@objc func moreDidAction(_ button: UIButton) {
|
|
// 如有弹出菜单
|
|
if isShowMore {
|
|
return
|
|
}
|
|
|
|
isShowMore = true
|
|
contentHeight = 250
|
|
contentView.isHidden = false
|
|
moreMenuView.isHidden = false
|
|
chatTextView.resignFirstResponder()
|
|
|
|
restToolbarContentHeight()
|
|
moreMenuView.reloadData()
|
|
|
|
delegate?.keyboard(self, DidBecome: true)
|
|
}
|
|
|
|
/// 更改容器高度
|
|
func restToolbarContentHeight(_ isRest: Bool = false) {
|
|
var changedY = ScreenHeight - self.toolBarHeight - NavHeight - contentHeight
|
|
if (isRest) {
|
|
if (isShowMore) {
|
|
isShowMore = false
|
|
}
|
|
|
|
changedY = ScreenHeight - self.toolBarHeight - NavHeight - contentHeight - SafeAraeBottomHeight
|
|
}
|
|
|
|
UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut, animations: {
|
|
self.contentView.frame = CGRect(x: 0, y: self.toolBarView.maxY, width: ScreenWidth, height: self.contentHeight)
|
|
self.moreMenuView.frame = self.contentView.bounds
|
|
self.frame = CGRect(x: 0, y: changedY, width: ScreenWidth, height: self.toolBarHeight + self.contentHeight)
|
|
}, completion: nil)
|
|
|
|
self.layoutIfNeeded()
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - 改变输入框高度位置
|
|
|
|
extension ChatKeyboardView {
|
|
|
|
func changeKeyboardHeight(_ isClear: Bool = false, height: CGFloat) {
|
|
let textHeight = height
|
|
|
|
toolBarHeight = textHeight + kSpace * 2
|
|
toolBarView.frame = CGRect(x: toolBarView.x, y: 0, width: toolBarView.width, height: toolBarHeight)
|
|
|
|
let spaceY = toolBarView.height - kSpace - kViewWH
|
|
chatTextView.frame = CGRect(x: chatTextView.x, y: chatTextView.x, width: chatTextView.width, height: textHeight)
|
|
moreButton.frame = CGRect(x: moreButton.x, y: spaceY, width: moreButton.width, height: moreButton.height)
|
|
contentView.frame = CGRect(x: contentView.x, y: toolBarView.maxY, width: contentView.width, height: contentHeight)
|
|
|
|
topLineView.frame = CGRect(x: 0, y: 0, width: ScreenWidth, height: kLineHeight)
|
|
bottomLineView.frame = CGRect(x: 0, y: toolBarView.height - kLineHeight, width: ScreenWidth, height: kLineHeight)
|
|
|
|
|
|
if (isShowKeyboard) {
|
|
if (isShowMore) {
|
|
isShowMore = false
|
|
}
|
|
|
|
let changedY = ScreenHeight - keyboardHeight - toolBarHeight - NavHeight
|
|
self.frame = CGRect(x: 0, y: changedY, width: ScreenWidth, height: toolBarView.height + contentView.height)
|
|
}else {
|
|
let changedY = ScreenHeight - NavHeight - (toolBarView.height + contentView.height)
|
|
self.frame = CGRect(x: 0, y: changedY, width: ScreenWidth, height: toolBarView.height + contentView.height)
|
|
}
|
|
|
|
self.setNeedsLayout()
|
|
print("self y === \(self.frame.origin.y)")
|
|
}
|
|
}
|
|
|
|
|