Files
yingmeng-ios-switf/yinmeng-ios/Modules/Chat/Keyboard/ChatKeyboardView.swift
2024-03-01 14:22:06 +08:00

472 lines
15 KiB
Swift

//
// ChatKeyboardView.swift
// yinmeng-ios
//
// Created by MaiMang on 2024/2/29.
//
import UIKit
import NIMSDK
protocol ChatKeyboardViewDelegate: NSObjectProtocol {
///
func keyboard(_ keyboard: ChatKeyboardView, voiceDidFinish path: String)
///
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 kLeft: CGFloat = 12.0
private let kSpace: CGFloat = 12.0
private let kViewWH: CGFloat = 26.0
private let kLineHeight: CGFloat = 0.75
// MARK: - var lazy
weak var delegate: ChatKeyboardViewDelegate?
fileprivate var toolBarHeight: CGFloat = (52.0 + SafeAraeBottomHeight)
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
private lazy var sendVoiceBtn: UIButton = {
let button = UIButton(type: .custom)
let w: CGFloat = ScreenWidth - self.kViewWH * 2 - self.kSpace * 3 - self.kSpace
button.frame = CGRect(x: self.kSpace + self.kLeft + self.kViewWH, y: self.kSpace, width: w, height: 36)
button.setTitle("按住说话", for: .normal)
button.setTitle("松开结束", for: .normal)
button.backgroundColor = ThemeColor(hexStr: "F1F1FA")
button.setTitleColor(ThemeColor(hexStr: "#282828"), for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .medium)
button.layer.masksToBounds = true
button.layer.cornerRadius = 4
button.isHidden = true
return button
}()
///
fileprivate lazy var changeButton : UIButton = {
let button = UIButton(type: .custom)
let x: CGFloat = self.kLeft
button.frame = CGRect(x: x, y: self.kSpace + 5, width: self.kViewWH, height: self.kViewWH)
button.setImage(UIImage(named: "chat_input_text"), for: .normal)
button.setImage(UIImage(named: "chat_voice"), for: .highlighted)
button.addTarget(self, action: #selector(changeDidAction(_:)), for: .touchUpInside)
return button
}()
///
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 + 5, 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 + self.kLeft + self.kViewWH, y: self.kSpace, width: w, height: 36))
textView.placeholder = "请输入..."
textView.textColor = ThemeColor(hexStr: "#000000")
textView.maxNumberOfLines = 5
textView.delegate = self
textView.backgroundColor = ThemeColor(hexStr: "#F1F1FA")
textView.layer.cornerRadius = 4
textView.layer.masksToBounds = true
textView.didTextChangedHeightClosure = { [weak self] height in
self?.changeKeyboardHeight(height: height)
}
return textView
}()
fileprivate lazy var toolBarView: UIView = {
let view = UIView(frame: CGRect(x: 0, y: 0, width: ScreenWidth, height: self.toolBarHeight))
view.backgroundColor = .clear
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 = .white
return view
}()
///
fileprivate lazy var moreMenuView: ChatMoreMenuView = {
let view = ChatMoreMenuView(frame: self.contentView.bounds)
view.isHidden = true
view.delegate = self
return view
}()
private lazy var voiceView: ChatSendVoiceView = {
let view = ChatSendVoiceView()
view.backgroundColor = .clear
return view
}()
// MARK: - life cycle
override init(frame: CGRect) {
super.init(frame: frame)
NIMSDK.shared().mediaManager.add(self)
setupKeyboardView()
registerNotification()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupKeyboardView()
registerNotification()
}
func setupKeyboardView() {
self.backgroundColor = .clear
self.isUserInteractionEnabled = true
addSendVoiceAction()
addSubview(toolBarView)
toolBarView.addSubview(moreButton)
toolBarView.addSubview(changeButton)
toolBarView.addSubview(chatTextView)
toolBarView.addSubview(sendVoiceBtn)
addSubview(contentView)
contentView.addSubview(moreMenuView)
}
private func addSendVoiceAction() {
sendVoiceBtn.addTarget(self, action: #selector(audioTouchDownAction), for: .touchDown)
sendVoiceBtn.addTarget(self, action: #selector(audioTouchUpOutsideAction), for: .touchUpOutside)
sendVoiceBtn.addTarget(self, action: #selector(audioTouchUpInsideAction), for: .touchUpInside)
sendVoiceBtn.addTarget(self, action: #selector(audioTouchDragEnterAction), for: .touchDragEnter)
sendVoiceBtn.addTarget(self, action: #selector(audioTouchDragExitAction), for: .touchDragExit)
}
// 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)
// KVOy
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 {
NIMSDK.shared().mediaManager.remove(self)
self.removeObserver(self, forKeyPath: "frame")
}
}
extension ChatKeyboardView: NIMMediaManagerDelegate {
func recordAudioInterruptionBegin() {
voiceView.cancelAudioRecord()
}
func recordAudioProgress(_ currentTime: TimeInterval) {
voiceView.updateAudioRecordProgress(recordTime: currentTime)
}
func recordAudio(_ filePath: String?, didCompletedWithError error: Error?) {
if let path = filePath {
delegate?.keyboard(self, voiceDidFinish: path)
}
}
}
// 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 - contentHeight
if (isShowMore) { //
isShowMore = false
self.moreMenuView.isHidden = true
self.moreMenuView.hidePageController = true
changedY = y - self.toolBarHeight
}
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 - self.toolBarHeight - 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 audioTouchDownAction() {
sendVoiceBtn.isSelected = true
//
if voiceView.superview == nil {
UIApplication.shared.keyWindow?.addSubview(voiceView)
voiceView.translatesAutoresizingMaskIntoConstraints = false
voiceView.snp.makeConstraints { make in
make.center.equalToSuperview()
}
voiceView.configAudioRecord(imageName: "chat_voice_record_first", title: "手指上滑,取消发送", isAnimation: true)
voiceView.beginAudioRecord()
}
}
@objc func audioTouchUpOutsideAction() {
sendVoiceBtn.isSelected = false
voiceView.cancelAudioRecord()
voiceView.removeFromSuperview()
}
@objc func audioTouchUpInsideAction() {
sendVoiceBtn.isSelected = false
voiceView.finishAudioRecord()
voiceView.removeFromSuperview()
}
@objc func audioTouchDragEnterAction() {
voiceView.configAudioRecord(imageName: "chat_voice_record_first", title: "手指上滑,取消发送", isAnimation: true)
}
@objc func audioTouchDragExitAction() {
voiceView.configAudioRecord(imageName: "chat_voice_record_cancel", title: "松开手指,取消发送", isAnimation: false)
}
@objc func changeDidAction(_ button: UIButton) {
button.isSelected = !button.isSelected
isShowMore = false
contentView.isHidden = true
moreMenuView.isHidden = true
contentHeight = 0.0
restToolbarContentHeight(true)
if button.isSelected {
chatTextView.resignFirstResponder()
sendVoiceBtn.isHidden = false
chatTextView.isHidden = true
} else {
chatTextView.becomeFirstResponder()
sendVoiceBtn.isHidden = true
chatTextView.isHidden = false
}
delegate?.keyboard(self, DidBecome: false)
}
///
@objc func moreDidAction(_ button: UIButton) {
//
if isShowMore {
isShowMore = false
contentView.isHidden = true
moreMenuView.isHidden = true
contentHeight = 0.0
restToolbarContentHeight(true)
delegate?.keyboard(self, DidBecome: false)
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 - contentHeight
if (isRest) {
if (isShowMore) {
isShowMore = false
}
changedY = ScreenHeight - self.toolBarHeight - contentHeight
}
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)
if (isShowKeyboard) {
if (isShowMore) {
isShowMore = false
}
let changedY = ScreenHeight - keyboardHeight - toolBarHeight
self.frame = CGRect(x: 0, y: changedY, width: ScreenWidth, height: toolBarView.height + contentView.height)
}else {
let changedY = ScreenHeight - (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)")
}
}