用Swift实现的IM聊天输入框

2018-05-11  本文已影响25人  莫寂岚

Just some Code,without annotation

写这段代码的时候只有我和计算机知道他是干什么的,现在.....
只有计算机知道了

//
//  ChatFooterBar.swift
//  AbysSwift
//
//  尽量在这里处理FooterBar的变化等等的问题
//
//  Created by aby on 2018/3/28.
//  Copyright © 2018年 Aby.wang. All rights reserved.
//

import UIKit
import SnapKit

enum ChatBoxState: Int {
    case normalState = 0
    case voiceState = 1
    case menuState = 2
    case menuHideState = 3
}

fileprivate enum RecordingDragStatus: Int {
    case noDrag = 0
    case dragInside = 1
    case dragOutside = 2
}

typealias CompontionBlock = () -> (Void)

protocol ChatFootBarDelegate {
    // 形态变化
    func footHeightChange(height: CGFloat, animate completion:@escaping CompontionBlock) -> Void
    func update(message: Message) -> Void
    
    // 录音事件的向外传递
    func chatFootRecord(event: RecordEvent) -> Void
}

/// 聊天的FootBar
class ChatFooterBar: UIView {
    var room_id: Int16?
    private var chatBox: UIView = UIView.init()
    private var menuBox: ChatFooterMenu = ChatFooterMenu.init(frame: CGRect.zero)
    
    private let normalTitle = "按住 说话"
    private let highlightedTitle = "松开 结束"
    private var dragStatus: RecordingDragStatus = .noDrag // 默认未开始拖拽
    // 切换输入状态的方法
    private lazy var changeStateBtn: UIButton = {
        let btn = UIButton.init(type: UIButtonType.custom)
        btn.addTarget(self, action: #selector(changeStateAction(_:)), for: .touchUpInside)
        return btn
    }()
    // 打开菜单的方法
    lazy var menuBtn: UIButton = {
        let btn = UIButton.init(type: .custom)
        btn.addTarget(self, action: #selector(menuAction(_:)), for: .touchUpInside)
        return btn
    }()
    // 发送消息的方法
    lazy var sendBtn: UIButton = {
        let btn = UIButton.init(type: .custom)
        btn.addTarget(self, action: #selector(sendAction(_:)), for: .touchUpInside)
        return btn
    }()
    // 消息输入框
    var textMsgInput: UITextField = UITextField.init()

    // 语音按钮
    lazy var voiceBtn: UIButton = {
        let btn = UIButton.init(type: .custom)
        btn.setTitleColor(UIColor.init(hexString: "333333"), for: .normal)
        btn.setTitle("按住 说话", for: .normal)
        btn.isHidden = true
        btn.layer.cornerRadius = 5.0
        btn.layer.borderColor = UIColor.init(hexString: "cfcfcf").cgColor
        btn.layer.borderWidth = 1 / UIScreen.main.scale
        btn.backgroundColor = UIColor.white
        return btn
    }()
    // 记录当前视图的状态信息
    var isMenuShow: Bool = false
    private var lastHeight: CGFloat?
    // MARK: -Public property
    var menuDelegate: ChatFootMenuDelegate?
    var delegate: ChatFootBarDelegate?


    override init(frame: CGRect) {
        super.init(frame: frame)
        initUIElement() //初始化并添加UI
        setUIStyle() // 设置UI的属性
        makeChildConstraints() // 设定约束
        setupEventes() // 初始化录音按钮事件
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        // 假如高度没有变化,则不执行代理方法
        if lastHeight != nil && lastHeight != self.bounds.height {
            lastHeight = self.bounds.height
            self.delegate?.footHeightChange(height: lastHeight!, animate: { () -> (Void) in

            })
        }
        if lastHeight == nil {
            lastHeight = self.bounds.height
        }
    }

    private func initUIElement() -> Void {
        self.addSubview(chatBox)
        chatBox.addSubview(changeStateBtn)
        chatBox.addSubview(menuBtn)
        chatBox.addSubview(sendBtn)
        chatBox.addSubview(textMsgInput)
        chatBox.addSubview(voiceBtn)
        self.addSubview(self.menuBox)
    }

    private func setUIStyle() -> Void {
        self.backgroundColor = UIColor.white
        var edgeInsert = UIEdgeInsets.init(top: 5, left: 15, bottom: 5, right: 5)
        changeStateBtn.imageEdgeInsets = edgeInsert
        changeStateBtn.imageView?.contentMode = .scaleAspectFit
        changeStateBtn.setImage(#imageLiteral(resourceName: "footer_voice_icon"), for: .normal)
        changeStateBtn.setImage(#imageLiteral(resourceName: "footer_scanf_icon"), for: .selected)
        edgeInsert.right = 15
        edgeInsert.left = 5
        menuBtn.setImage(#imageLiteral(resourceName: "footer_more_icon"), for: .normal)
        menuBtn.imageEdgeInsets = edgeInsert
        menuBtn.imageView?.contentMode = .scaleAspectFit
        sendBtn.setImage(#imageLiteral(resourceName: "sender_btn_icon"), for: .normal)
        sendBtn.imageEdgeInsets = edgeInsert
        sendBtn.imageView?.contentMode = .scaleAspectFit
        textMsgInput.placeholder = "请输入..."
        self.menuBox.isHidden = !isMenuShow
    }

    func makeChildConstraints() -> Void {
        chatBox.snp.makeConstraints { (make) in
            make.height.greaterThanOrEqualTo(55)
            make.width.greaterThanOrEqualTo(W750(750)) // 与屏幕等宽
            make.top.equalToSuperview()
            make.centerX.equalToSuperview()
        }
        changeStateBtn.snp.makeConstraints { (make) in
            make.left.equalToSuperview().offset(10)
            make.centerY.equalToSuperview()
            make.width.equalTo(40)
            make.height.equalTo(30)
        }
        sendBtn.snp.makeConstraints { (make) in
            make.right.equalToSuperview().offset(-10)
            make.centerY.equalToSuperview()
            make.height.equalTo(30)
            make.width.equalTo(40)
        }
        menuBtn.snp.makeConstraints { (make) in
            make.right.equalTo(sendBtn.snp.left)
            make.centerY.equalToSuperview()
            make.height.equalTo(30)
            make.width.equalTo(40)
        }
        textMsgInput.snp.makeConstraints { (make) in
            make.left.equalTo(changeStateBtn.snp.right).offset(15)
            make.right.equalTo(menuBtn.snp.left).offset(-15)
            make.height.greaterThanOrEqualTo(30)
            make.width.lessThanOrEqualTo(W750(750) - 170)
            make.centerY.equalToSuperview()
        }
        voiceBtn.snp.makeConstraints { (make) in
            make.left.equalTo(changeStateBtn.snp.right).offset(15)
            make.right.equalToSuperview().offset(-70)
            make.height.greaterThanOrEqualTo(30)
            make.centerY.equalToSuperview()
        }
    }

    private func setMenuCons() -> Void {
        self.menuBox.snp.makeConstraints { (make) in
            make.width.equalToSuperview()
            make.top.equalTo(self.chatBox.snp.bottom)
            make.height.equalTo(76)
            make.bottom.equalTo(self.snp.bottom)
            make.centerX.equalToSuperview()
        }
    }
    private func removeMenuCons() -> Void {
        self.menuBox.snp.removeConstraints()
    }

    func showMenu() -> Void {
        self.menuBox.delegate = self.menuDelegate
        isMenuShow = true
        lastHeight = 131
        weak var weakSelf = self;
        self.delegate?.footHeightChange(height: 131, animate: {
            weakSelf?.setMenuCons()
            weakSelf?.menuBox.isHidden = !self.isMenuShow
        })
    }

    func hideMenu(_ needAnimation: Bool? = true) -> Void {
        menuBox.delegate = nil
        isMenuShow = false
        self.removeMenuCons()
        self.menuBox.isHidden = !isMenuShow
        lastHeight = 55
        self.delegate?.footHeightChange(height: 55, animate: {})
    }

    private func changeState(_ state: ChatBoxState) -> Void {
        switch state {
        case .normalState:
            UIView.animate(withDuration: 0.3, animations: {
                self.menuBtn.snp.remakeConstraints({ (make) in
                    make.right.equalTo(self.sendBtn.snp.left)
                    make.centerY.equalToSuperview()
                    make.height.equalTo(30)
                    make.width.equalTo(40)
                })
                self.layoutIfNeeded()
            }, completion: { (complete) in
                self.sendBtn.isHidden = false
            })
            break
        case .voiceState:
            sendBtn.isHidden = true
            UIView.animate(withDuration: 0.3, animations: {
                self.menuBtn.snp.remakeConstraints({ (make) in
                    make.right.equalToSuperview().offset(-10)
                    make.centerY.equalToSuperview()
                    make.height.equalTo(30)
                    make.width.equalTo(40)
                })
                self.layoutIfNeeded()
            })
            break
        case .menuState:
            break
        default:
            break
        }
    }
}

// MARK: - 处理语音消息的点击事件
extension ChatFooterBar {
    /// 点击按钮的方法
    ///
    /// - Parameter btn: 按钮
    @objc
    func touchDownInSide(_ btn: UIButton, event: UIEvent) -> Void {
        dragStatus = .dragInside
        replaceRecordBtnUI(isRecording: true)
        self.delegate?.chatFootRecord(event: RecordEvent.start) // 开始录音
    }
    /// 拖拽的处理
    ///
    /// - Parameter btn: 按钮
    @objc
    func dragon(_ btn: UIButton, event: UIEvent) -> Void {
        guard let touch: UITouch = event.allTouches?.first else { return }
        let isTouchInside: Bool = voiceBtn.point(inside: touch.location(in: voiceBtn), with: event)
        if isTouchInside {
            guard dragStatus == .dragOutside else { return }
            dragStatus = .dragInside
            self.delegate?.chatFootRecord(event: RecordEvent.recording) // 录音
        } else {
            guard dragStatus == .dragInside else {return}
            dragStatus = .dragOutside
            self.delegate?.chatFootRecord(event: RecordEvent.parpareToCancel) // 准备取消
        }
    }
    /// 在里面抬起的方法
    ///
    /// - Parameter btn: 按钮
    @objc
    func touchUpInSide(_ btn: UIButton, event: UIEvent) -> Void {
        dragStatus = .noDrag
        replaceRecordBtnUI(isRecording: false)
        self.delegate?.chatFootRecord(event: RecordEvent.stop) // 停止
    }
    /// 在外部抬起的方法
    ///
    /// - Parameter btn: 按钮
    @objc
    func touchUpOutSide(_ btn: UIButton, event: UIEvent) -> Void {
        replaceRecordBtnUI(isRecording: false)
        dragStatus = .noDrag
        self.delegate?.chatFootRecord(event: RecordEvent.cancel) // 取消
    }
    /// 取消的方法
    @objc
    func touchCancel() -> Void {
        dragStatus = .noDrag
        replaceRecordBtnUI(isRecording: false)
    }
    fileprivate func setupEventes() {
        voiceBtn.addTarget(self, action: #selector(touchDownInSide(_:event:)), for: .touchDown)
        voiceBtn.addTarget(self, action: #selector(touchUpInSide(_:event:)), for: .touchUpInside)
        voiceBtn.addTarget(self, action: #selector(touchUpOutSide(_:event:)), for: .touchUpOutside)
        voiceBtn.addTarget(self, action: #selector(dragon(_:event:)), for: .touchDragOutside)
        voiceBtn.addTarget(self, action: #selector(dragon(_:event:)), for: .touchDragInside)
        voiceBtn.addTarget(self, action: #selector(touchCancel), for: .touchCancel)
    }
    // 切换 录音按钮的UI
    fileprivate func replaceRecordBtnUI(isRecording: Bool) {
        if isRecording {
            voiceBtn.setTitle(highlightedTitle, for: .normal)
        } else {
            voiceBtn.setTitle(normalTitle, for: .normal)
        }
    }
}

// MARK: -存放计算属性
extension ChatFooterBar {
    var isTextNil: Bool {
        guard let text = self.textMsgInput.text else { return true }
        return text == ""
    }

    var textMsg: String {
        return self.textMsgInput.text ?? ""
    }
}

// MARK: - 处理按钮的点击事件
extension ChatFooterBar {
    @objc
    func changeStateAction(_ button: UIButton) -> Void {
        if self.textMsgInput.isEditing {
            self.textMsgInput.endEditing(true)
        }
        if !button.isSelected {
            AudioTool.defaut.checkPermission()
            // 跳转之前检查语音权限
            if AudioTool.defaut.hasPermisssion == .granted {
                changeState(.voiceState)
//                textMsgInput.isHidden = !button.isSelected
//                voiceBtn.isHidden = button.isSelected
            } else if AudioTool.defaut.hasPermisssion == .undetermined {
                AudioTool.defaut.requestAccessForAudio()
                return
            } else if AudioTool.defaut.hasPermisssion == .denied {
                // 没有录音权限,需要提示
                return
            }
        } else {
            changeState(.normalState)
//            textMsgInput.isHidden = !button.isSelected
//            voiceBtn.isHidden = button.isSelected
        }
        textMsgInput.isHidden = !button.isSelected
        voiceBtn.isHidden = button.isSelected
        button.isSelected = !button.isSelected
    }

    @objc
    func menuAction(_ button: UIButton) -> Void {
        if self.textMsgInput.isEditing {
            self.textMsgInput.endEditing(true)
        }
        if isMenuShow {
            hideMenu()
        } else {
            showMenu()
        }
    }

    @objc
    func sendAction(_ button: UIButton) -> Void {
        // 在这里的发送方法只涉及到了文本消息的发送
        // 首先判断消息发送框是否为空
        guard !isTextNil else {
            ABYPrint("warning: 消息为空")
            return
        }
        guard let room_id = room_id else {
            ABYPrint("warning: 房间号不存在")
            return
        }
        ABYPrint("log 发送消息")
        let message = Message.init(text: textMsg, room_id:room_id ) // 组装消息
        self.delegate?.update(message: message) // 更新视图
        self.textMsgInput.text = "" // 清空文本框
    }
}


后面在慢慢补充思路吧。

上一篇下一篇

猜你喜欢

热点阅读