Forráskód Böngészése

1.迁移NX相关到Common

wenweiwei 3 éve
szülő
commit
8eb98e7d6c

+ 17 - 0
BFCommonKit/Classes/BFBase/View/BFUIButton.swift

@@ -0,0 +1,17 @@
+//
+//  BFUIButton.swift
+//  Alamofire
+//
+//  Created by ak on 2021/8/4.
+//  功能:默认扩大 btn 的点击范围
+
+import Foundation
+
+public class BFUIButton: UIButton {
+    //扩大点击范围 PX
+    var margin: CGFloat = 60
+    public override func point(inside point: CGPoint, with _: UIEvent?) -> Bool {
+        let area = bounds.insetBy(dx: -margin, dy: -margin) // 负值是方法响应范围
+        return area.contains(point)
+    }
+}

+ 99 - 0
BFCommonKit/Classes/BFBase/View/BFUISlider.swift

@@ -0,0 +1,99 @@
+//
+//  BFSlider.swift
+//  BFFramework
+//
+//  Created by ak on 2021/8/4.
+//  功能:自定义 UISlider
+
+import Foundation
+
+class BFUISlider: UISlider {
+    
+    //slider的value文本
+    var valueText:String?{
+        didSet {
+            valueLabel.text = valueText
+            valueLabel.sizeToFit()
+            
+            let trackRect = convert(bounds, to: nil)
+            let thumbRectTemp = thumbRect(forBounds: bounds, trackRect: trackRect, value: value)
+            valueLabel.center = CGPoint.init(x: (thumbRectTemp.origin.x - thumbRectTemp.origin.x + thumbRectTemp.size.width / 2), y: -self.frame.size.height)
+            
+//            CGPointMake((thumbRect.origin.x - trackRect.origin.x + thumbRect.size.width / 2), -self.frame.size.height);
+            
+//            [super trackRectForBounds:bounds];
+//           return CGRectMake(bounds.origin.x, bounds.origin.y + (bounds.size.height - 3.0) / 2, bounds.size.width, 3.0);
+       
+        }
+    }
+    override var value: Float{
+        didSet {
+            sliderValueChanged(sender: self)
+        }
+    }
+    //slider的value字体
+    var valueFont:UIFont?
+    //slider的value文本颜色
+    var valueTextColor:UIColor?
+    
+    //返回的数值是否为整形
+    var valueIsInt:Bool = false
+    
+    lazy var valueLabel:UILabel = {
+        let valueLabel = UILabel.init()
+        valueLabel.text = "0"
+        valueLabel.textColor = valueTextColor != nil ? valueTextColor : self.thumbTintColor
+        valueLabel.font = valueFont != nil ? valueFont : UIFont.systemFont(ofSize: 14);
+        valueLabel.textAlignment = .center
+        
+        return valueLabel
+        
+    }()
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        addTarget(self, action: #selector(sliderTouchDown(sender:)), for: .touchDown)
+        addTarget(self, action: #selector(sliderValueChanged(sender:)), for: .valueChanged)
+        addTarget(self, action: #selector(sliderTouchUpInside(sender:)), for: .touchUpInside)
+ 
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    override func layoutSubviews() {
+        super.layoutSubviews()
+        addSubview(valueLabel)
+        sliderValueChanged(sender: self)
+    }
+    
+    override func setValue(_ value: Float, animated: Bool) {
+        super.setValue(value, animated: animated)
+        sliderValueChanged(sender: self)
+    }
+ 
+    //重写方法-返回进度条的bounds-修改进度条的高度
+    override func trackRect(forBounds bounds: CGRect) -> CGRect {
+ 
+        let bounds = super.trackRect(forBounds: bounds)
+        return CGRect.init(origin: CGPoint.init(x: bounds.origin.x, y: bounds.origin.y + (bounds.size.height - 3.0) / 2 ), size: CGSize.init(width:  bounds.size.width, height: 3))
+    }
+    
+    @objc func sliderTouchDown(sender: BFUISlider) {
+         
+    }
+    @objc func sliderValueChanged(sender: BFUISlider) {
+        
+        if(valueIsInt){
+            valueText = "\(Int(sender.value))x"
+        }else{
+            valueText = "\(sender.value.decimalString())x"
+        }
+      
+ 
+    }
+    @objc func sliderTouchUpInside(sender: BFUISlider) {
+         
+    }
+    
+}

+ 114 - 0
BFCommonKit/Classes/BFBase/View/NXBadgeView/NXBadgeControl.swift

@@ -0,0 +1,114 @@
+//
+//  NXBadgeControl.swift
+//  NXFramework-Swift
+//
+//  Created by ak on 2020/11/11.
+//
+
+
+import UIKit
+
+
+open class NXBadgeControl: UIControl {
+    
+    /// 记录Badge的偏移量 Record the offset of Badge
+    public var offset: CGPoint = CGPoint(x: 0, y: 0)
+    
+    /// Badge伸缩的方向, Default is NXBadgeViewFlexModeTail
+    public var flexMode: NXBadgeViewFlexMode = .tail
+    
+    private lazy var textLabel: UILabel = UILabel()
+    
+    private lazy var imageView: UIImageView = UIImageView()
+    
+    private var badgeViewColor: UIColor?
+    private var badgeViewHeightConstraint: NSLayoutConstraint?
+    
+    public class func `default`() -> Self {
+        return self.init(frame: .zero)
+    }
+    
+    required override public init(frame: CGRect) {
+        super.init(frame: frame)
+        setupSubviews()
+    }
+    
+    required public init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    /// Set Text
+    open var text: String? {
+        didSet {
+            textLabel.text = text
+        }
+    }
+    
+    /// Set AttributedText
+    open var attributedText: NSAttributedString? {
+        didSet {
+            textLabel.attributedText = attributedText
+        }
+    }
+    
+    /// Set Font
+    open var font: UIFont? {
+        didSet {
+            textLabel.font = font
+        }
+    }
+    
+    /// Set background image
+    open var backgroundImage: UIImage? {
+        didSet {
+            imageView.image = backgroundImage
+            if let _ = backgroundImage {
+                if let constraint = heightConstraint() {
+                    badgeViewHeightConstraint = constraint
+                    removeConstraint(constraint)
+                }
+                backgroundColor = UIColor.clear
+            } else {
+                if heightConstraint() == nil, let constraint = badgeViewHeightConstraint {
+                    addConstraint(constraint)
+                }
+                backgroundColor = badgeViewColor
+            }
+        }
+    }
+    
+    open override var backgroundColor: UIColor? {
+        didSet {
+            super.backgroundColor = backgroundColor
+            if let color = backgroundColor, color != .clear {
+                badgeViewColor = backgroundColor
+            }
+        }
+    }
+    
+    private func setupSubviews() {
+        layer.masksToBounds = true
+        layer.cornerRadius = 9.0
+        translatesAutoresizingMaskIntoConstraints = false
+        backgroundColor = UIColor.red
+        textLabel.textColor = UIColor.white
+        textLabel.font = UIFont.systemFont(ofSize: 13)
+        textLabel.textAlignment = .center
+        addSubview(textLabel)
+        addSubview(imageView)
+        addLayout(with: imageView, leading: 0, trailing: 0)
+        addLayout(with: textLabel, leading: 5, trailing: -5)
+    }
+    
+    private func addLayout(with view: UIView, leading: CGFloat, trailing: CGFloat) {
+        view.translatesAutoresizingMaskIntoConstraints = false
+        let topConstraint = NSLayoutConstraint(item: view, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0)
+        let leadingConstraint = NSLayoutConstraint(item: view, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1.0, constant: leading)
+        let bottomConstraint = NSLayoutConstraint(item: view, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0)
+        let trailingConstraint = NSLayoutConstraint(item: view, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: trailing)
+        leadingConstraint.priority = UILayoutPriority(rawValue: 999)
+        trailingConstraint.priority = UILayoutPriority(rawValue: 999)
+        addConstraints([topConstraint, leadingConstraint, bottomConstraint, trailingConstraint])
+    }
+}

+ 28 - 0
BFCommonKit/Classes/BFBase/View/NXBadgeView/NXBadgeView.swift

@@ -0,0 +1,28 @@
+//
+//  NXBadgeView.swift.swift
+//  NXFramework-Swift
+//
+//  Created by ak on 2020/11/11.
+//
+
+
+import UIKit
+
+public struct NX<Base> {
+    public let base: Base
+    public init(_ base: Base) {
+        self.base = base
+    }
+}
+
+public extension NSObjectProtocol {
+    var nx: NX<Self> {
+        return NX(self)
+    }
+}
+
+public enum NXBadgeViewFlexMode {
+    case head    // 左伸缩 Head Flex    : <==●
+    case tail    // 右伸缩 Tail Flex    : ●==>
+    case middle  // 左右伸缩 Middle Flex : <=●=>
+}

+ 125 - 0
BFCommonKit/Classes/BFBase/View/NXBadgeView/UIBarButtonItem+NXBadgeView.swift

@@ -0,0 +1,125 @@
+//
+//  UIBarButtonItem+NXBadgeView.swift
+//  NXFramework-Swift
+//
+//  Created by ak on 2020/11/11.
+//
+
+import UIKit
+
+public extension NX where Base: UIBarButtonItem {
+    
+    public var badgeView: NXBadgeControl {
+        return _bottomView.nx.badgeView
+    }
+    
+    /// 添加带文本内容的Badge, 默认右上角, 红色, 18pts
+    ///
+    /// Add Badge with text content, the default upper right corner, red backgroundColor, 18pts
+    ///
+    /// - Parameter text: 文本字符串
+     public  func addBadge(text: String) {
+        _bottomView.nx.addBadge(text: text)
+    }
+    
+    /// 添加带数字的Badge, 默认右上角,红色,18pts
+    ///
+    /// Add the Badge with numbers, the default upper right corner, red backgroundColor, 18pts
+    ///
+    /// - Parameter number: 整形数字
+     public  func addBadge(number: Int) {
+        _bottomView.nx.addBadge(number: number)
+    }
+    
+    /// 添加带颜色的小圆点, 默认右上角, 红色, 8pts
+    ///
+    /// Add small dots with color, the default upper right corner, red backgroundColor, 8pts
+    ///
+    /// - Parameter color: 颜色
+     public  func addDot(color: UIColor?) {
+        _bottomView.nx.addDot(color: color)
+    }
+    
+    /// 设置Badge的偏移量, Badge中心点默认为其父视图的右上角
+    ///
+    /// Set Badge offset, Badge center point defaults to the top right corner of its parent view
+    ///
+    /// - Parameters:
+    ///   - x: X轴偏移量 (x<0: 左移, x>0: 右移) axis offset (x <0: left, x> 0: right)
+    ///   - y: Y轴偏移量 (y<0: 上移, y>0: 下移) axis offset (Y <0: up,   y> 0: down)
+     public  func moveBadge(x: CGFloat, y: CGFloat) {
+        _bottomView.nx.moveBadge(x: x, y: y)
+    }
+    
+    /// 设置Badge伸缩的方向
+    ///
+    /// Setting the direction of Badge expansion
+    ///
+    /// NXBadgeViewFlexModeHead,    左伸缩 Head Flex    : <==●
+    /// NXBadgeViewFlexModeTail,    右伸缩 Tail Flex    : ●==>
+    /// NXBadgeViewFlexModeMiddle   左右伸缩 Middle Flex : <=●=>
+    /// - Parameter flexMode : Default is PPBadgeViewFlexModeTail
+     public  func setBadge(flexMode: NXBadgeViewFlexMode = .tail) {
+        _bottomView.nx.setBadge(flexMode: flexMode)
+    }
+    
+    /// 设置Badge的高度,因为Badge宽度是动态可变的,通过改变Badge高度,其宽度也按比例变化,方便布局
+    ///
+    /// (注意: 此方法需要将Badge添加到控件上后再调用!!!)
+    ///
+    /// Set the height of Badge, because the Badge width is dynamically and  variable.By changing the Badge height in proportion to facilitate the layout.
+    ///
+    /// (Note: this method needs to add Badge to the controls and then use it !!!)
+    ///
+    /// - Parameter points: 高度大小
+     public  func setBadge(height: CGFloat) {
+        _bottomView.nx.setBadge(height: height)
+    }
+    
+    /// 显示Badge
+     public  func showBadge() {
+        _bottomView.nx.showBadge()
+    }
+    
+    /// 隐藏Badge
+     public  func hiddenBadge() {
+        _bottomView.nx.hiddenBadge()
+    }
+    
+    // MARK: - 数字增加/减少, 注意:以下方法只适用于Badge内容为纯数字的情况
+    // MARK: - Digital increase /decrease, note: the following method applies only to cases where the Badge content is purely numeric
+    /// badge数字加1
+     public  func increase() {
+        _bottomView.nx.increase()
+    }
+    
+    /// badge数字加number
+     public  func increaseBy(number: Int) {
+        _bottomView.nx.increaseBy(number: number)
+    }
+    
+    /// badge数字加1
+     public  func decrease() {
+        _bottomView.nx.decrease()
+    }
+    
+    /// badge数字减number
+     public  func decreaseBy(number: Int) {
+        _bottomView.nx.decreaseBy(number: number)
+    }
+
+    /// 通过Xcode视图调试工具找到UIBarButtonItem的Badge所在父视图为:UIImageView
+    private var _bottomView: UIView {
+        let navigationButton = (self.base.value(forKey: "_view") as? UIView) ?? UIView()
+        let systemVersion = (UIDevice.current.systemVersion as NSString).doubleValue
+        let controlName = (systemVersion < 11.0 ? "UIImageView" : "UIButton" )
+        for subView in navigationButton.subviews {
+            if subView.isKind(of: NSClassFromString(controlName)!) {
+                subView.layer.masksToBounds = false
+                return subView
+            }
+        }
+        return navigationButton
+    }
+}
+

+ 126 - 0
BFCommonKit/Classes/BFBase/View/NXBadgeView/UITabBarItem+NXBadgeView.swift

@@ -0,0 +1,126 @@
+
+//
+//  UITabBarItem+NXBadgeView.swift
+//  NXFramework-Swift
+//
+//  Created by ak on 2020/11/11.
+//
+
+import Foundation
+
+public extension NX where Base: UITabBarItem {
+    
+    var badgeView: NXBadgeControl {
+        return _bottomView.nx.badgeView
+    }
+    
+    /// 添加带文本内容的Badge, 默认右上角, 红色, 18pts
+    ///
+    /// Add Badge with text content, the default upper right corner, red backgroundColor, 18pts
+    ///
+    /// - Parameter text: 文本字符串
+    func addBadge(text: String) {
+        _bottomView.nx.addBadge(text: text)
+        _bottomView.nx.moveBadge(x: 4, y: 3)
+    }
+    
+    /// 添加带数字的Badge, 默认右上角,红色,18pts
+    ///
+    /// Add the Badge with numbers, the default upper right corner, red backgroundColor, 18pts
+    ///
+    /// - Parameter number: 整形数字
+    func addBadge(number: Int) {
+        _bottomView.nx.addBadge(number: number)
+        _bottomView.nx.moveBadge(x: 4, y: 3)
+    }
+    
+    /// 添加带颜色的小圆点, 默认右上角, 红色, 8pts
+    ///
+    /// Add small dots with color, the default upper right corner, red backgroundColor, 8pts
+    ///
+    /// - Parameter color: 颜色
+    func addDot(color: UIColor?) {
+        _bottomView.nx.addDot(color: color)
+    }
+    
+    /// 设置Badge的偏移量, Badge中心点默认为其父视图的右上角
+    ///
+    /// Set Badge offset, Badge center point defaults to the top right corner of its parent view
+    ///
+    /// - Parameters:
+    ///   - x: X轴偏移量 (x<0: 左移, x>0: 右移) axis offset (x <0: left, x> 0: right)
+    ///   - y: Y轴偏移量 (y<0: 上移, y>0: 下移) axis offset (Y <0: up,   y> 0: down)
+    func moveBadge(x: CGFloat, y: CGFloat) {
+        _bottomView.nx.moveBadge(x: x, y: y)
+    }
+    
+    /// 设置Badge伸缩的方向
+    ///
+    /// Setting the direction of Badge expansion
+    ///
+    /// PPBadgeViewFlexModeHead,    左伸缩 Head Flex    : <==●
+    /// PPBadgeViewFlexModeTail,    右伸缩 Tail Flex    : ●==>
+    /// PPBadgeViewFlexModeMiddle   左右伸缩 Middle Flex : <=●=>
+    /// - Parameter flexMode : Default is PPBadgeViewFlexModeTail
+    func setBadge(flexMode: NXBadgeViewFlexMode = .tail) {
+        _bottomView.nx.setBadge(flexMode: flexMode)
+    }
+    
+    /// 设置Badge的高度,因为Badge宽度是动态可变的,通过改变Badge高度,其宽度也按比例变化,方便布局
+    ///
+    /// (注意: 此方法需要将Badge添加到控件上后再调用!!!)
+    ///
+    /// Set the height of Badge, because the Badge width is dynamically and  variable.By changing the Badge height in proportion to facilitate the layout.
+    ///
+    /// (Note: this method needs to add Badge to the controls and then use it !!!)
+    ///
+    /// - Parameter height: 高度大小
+    func setBadge(height: CGFloat) {
+        _bottomView.nx.setBadge(height: height)
+    }
+    
+    
+    /// 显示Badge
+    func showBadge() {
+        _bottomView.nx.showBadge()
+    }
+    
+    /// 隐藏Badge
+    func hiddenBadge() {
+        _bottomView.nx.hiddenBadge()
+    }
+    
+    // MARK: - 数字增加/减少, 注意:以下方法只适用于Badge内容为纯数字的情况
+    // MARK: - Digital increase /decrease, note: the following method applies only to cases where the Badge content is purely numeric
+    /// badge数字加1
+    func increase() {
+        _bottomView.nx.increase()
+    }
+    
+    /// badge数字加number
+    func increaseBy(number: Int) {
+        _bottomView.nx.increaseBy(number: number)
+    }
+    
+    /// badge数字加1
+    func decrease() {
+        _bottomView.nx.decrease()
+    }
+    
+    /// badge数字减number
+    func decreaseBy(number: Int) {
+        _bottomView.nx.decreaseBy(number: number)
+    }
+    
+    /// 通过Xcode视图调试工具找到UITabBarItem原生Badge所在父视图
+    private var _bottomView: UIView {
+        let tabBarButton = (self.base.value(forKey: "_view") as? UIView) ?? UIView()
+        for subView in tabBarButton.subviews {
+            guard let superclass = subView.superclass else { return tabBarButton }
+            if superclass == NSClassFromString("UIImageView") {
+                return subView
+            }
+        }
+        return tabBarButton
+    }
+}

+ 260 - 0
BFCommonKit/Classes/BFBase/View/NXBadgeView/UIView+NXBadgeView.swift

@@ -0,0 +1,260 @@
+//
+//  UIView+NXBadgeView.swift
+//  NXFramework-Swift
+//
+//  Created by ak on 2020/11/11.
+//
+
+import UIKit
+
+private var kBadgeView = "kNXBadgeView"
+
+// MARK: - add Badge
+public extension NX where Base: UIView {
+    
+    var badgeView: NXBadgeControl {
+        return base.badgeView
+    }
+    
+    /// 添加带文本内容的Badge, 默认右上角, 红色, 18pts
+    ///
+    /// Add Badge with text content, the default upper right corner, red backgroundColor, 18pts
+    ///
+    /// - Parameter text: 文本字符串
+    func addBadge(text: String?) {
+        showBadge()
+        base.badgeView.text = text
+        setBadge(flexMode: base.badgeView.flexMode)
+        if text == nil {
+            if base.badgeView.widthConstraint()?.relation == .equal { return }
+            base.badgeView.widthConstraint()?.isActive = false
+            let constraint = NSLayoutConstraint(item: base.badgeView, attribute: .width, relatedBy: .equal, toItem: base.badgeView, attribute: .height, multiplier: 1.0, constant: 0)
+            base.badgeView.addConstraint(constraint)
+        } else {
+            if base.badgeView.widthConstraint()?.relation == .greaterThanOrEqual { return }
+            base.badgeView.widthConstraint()?.isActive = false
+            let constraint = NSLayoutConstraint(item: base.badgeView, attribute: .width, relatedBy: .greaterThanOrEqual, toItem: base.badgeView, attribute: .height, multiplier: 1.0, constant: 0)
+            base.badgeView.addConstraint(constraint)
+        }
+    }
+    
+    /// 添加带数字的Badge, 默认右上角,红色,18pts
+    ///
+    /// Add the Badge with numbers, the default upper right corner, red backgroundColor, 18pts
+    ///
+    /// - Parameter number: 整形数字
+    func addBadge(number: Int) {
+        if number <= 0 {
+            addBadge(text: "0")
+            hiddenBadge()
+            return
+        }
+        addBadge(text: "\(number)")
+    }
+    
+    /// 添加带颜色的小圆点, 默认右上角, 红色, 8pts
+    ///
+    /// Add small dots with color, the default upper right corner, red backgroundColor, 8pts
+    ///
+    /// - Parameter color: 颜色
+    func addDot(color: UIColor? = .red) {
+        addBadge(text: nil)
+        setBadge(height: 8.0)
+        base.badgeView.backgroundColor = color
+    }
+    
+    /// 设置Badge的偏移量, Badge中心点默认为其父视图的右上角
+    ///
+    /// Set Badge offset, Badge center point defaults to the top right corner of its parent view
+    ///
+    /// - Parameters:
+    ///   - x: X轴偏移量 (x<0: 左移, x>0: 右移) axis offset (x <0: left, x> 0: right)
+    ///   - y: Y轴偏移量 (y<0: 上移, y>0: 下移) axis offset (Y <0: up,   y> 0: down)
+    func moveBadge(x: CGFloat, y: CGFloat) {
+        base.badgeView.offset = CGPoint(x: x, y: y)
+        base.centerYConstraint(with: base.badgeView)?.constant = y
+        
+        let badgeHeight = base.badgeView.heightConstraint()?.constant ?? 0
+        switch base.badgeView.flexMode {
+        case .head:
+            base.centerXConstraint(with: base.badgeView)?.isActive = false
+            base.leadingConstraint(with: base.badgeView)?.isActive = false
+            if let constraint = base.trailingConstraint(with: base.badgeView) {
+                constraint.constant = badgeHeight * 0.5 + x
+                return
+            }
+            let trailingConstraint = NSLayoutConstraint(item: base.badgeView, attribute: .trailing, relatedBy: .equal, toItem: base, attribute: .trailing, multiplier: 1.0, constant: badgeHeight * 0.5 + x)
+            base.addConstraint(trailingConstraint)
+            
+        case .tail:
+            base.centerXConstraint(with: base.badgeView)?.isActive = false
+            base.trailingConstraint(with: base.badgeView)?.isActive = false
+            if let constraint = base.leadingConstraint(with: base.badgeView) {
+                constraint.constant = x - badgeHeight * 0.5
+                return
+            }
+            let leadingConstraint = NSLayoutConstraint(item: base.badgeView, attribute: .leading, relatedBy: .equal, toItem: base, attribute: .trailing, multiplier: 1.0, constant: x - badgeHeight * 0.5)
+            base.addConstraint(leadingConstraint)
+            
+        case .middle:
+            base.leadingConstraint(with: base.badgeView)?.isActive = false
+            base.trailingConstraint(with: base.badgeView)?.isActive = false
+            base.centerXConstraint(with: base.badgeView)?.constant = x
+            if let constraint = base.centerXConstraint(with: base.badgeView) {
+                constraint.constant = x
+                return
+            }
+            let centerXConstraint = NSLayoutConstraint(item: base.badgeView, attribute: .centerX, relatedBy: .equal, toItem: base, attribute: .centerX, multiplier: 1.0, constant: x)
+            base.addConstraint(centerXConstraint)
+        }
+    }
+    
+    /// 设置Badge伸缩的方向
+    ///
+    /// Setting the direction of Badge expansion
+    ///
+    /// NXBadgeViewFlexModeHead,    左伸缩 Head Flex    : <==●
+    /// NXBadgeViewFlexModeTail,    右伸缩 Tail Flex    : ●==>
+    /// NXBadgeViewFlexModeMiddle   左右伸缩 Middle Flex : <=●=>
+    /// - Parameter flexMode : Default is PPBadgeViewFlexModeTail
+    func setBadge(flexMode: NXBadgeViewFlexMode = .tail) {
+        base.badgeView.flexMode = flexMode
+        moveBadge(x: base.badgeView.offset.x, y: base.badgeView.offset.y)
+    }
+    
+    /// 设置Badge的高度,因为Badge宽度是动态可变的,通过改变Badge高度,其宽度也按比例变化,方便布局
+    ///
+    /// (注意: 此方法需要将Badge添加到控件上后再调用!!!)
+    ///
+    /// Set the height of Badge, because the Badge width is dynamically and  variable.By changing the Badge height in proportion to facilitate the layout.
+    ///
+    /// (Note: this method needs to add Badge to the controls and then use it !!!)
+    ///
+    /// - Parameter height: 高度大小
+    func setBadge(height: CGFloat) {
+        base.badgeView.layer.cornerRadius = height * 0.5
+        base.badgeView.heightConstraint()?.constant = height
+        moveBadge(x: base.badgeView.offset.x, y: base.badgeView.offset.y)
+    }
+    
+    /// 显示Badge
+    func showBadge() {
+        base.badgeView.isHidden = false
+    }
+    
+    /// 隐藏Badge
+    func hiddenBadge() {
+        base.badgeView.isHidden = true
+    }
+    
+    // MARK: - 数字增加/减少, 注意:以下方法只适用于Badge内容为纯数字的情况
+    // MARK: - Digital increase /decrease, note: the following method applies only to cases where the Badge content is purely numeric
+    /// badge数字加1
+    func increase() {
+        increaseBy(number: 1)
+    }
+    
+    /// badge数字加number
+    func increaseBy(number: Int) {
+        let label = base.badgeView
+        let result = (Int(label.text ?? "0") ?? 0) + number
+        if result > 0 {
+            showBadge()
+        }
+        label.text = "\(result)"
+    }
+    
+    /// badge数字加1
+    func decrease() {
+        decreaseBy(number: 1)
+    }
+    
+    /// badge数字减number
+    func decreaseBy(number: Int) {
+        let label = base.badgeView
+        let result = (Int(label.text ?? "0") ?? 0) - number
+        if (result <= 0) {
+            hiddenBadge()
+            label.text = "0"
+            return
+        }
+        label.text = "\(result)"
+    }
+}
+
+extension UIView {
+    
+      public  func addBadgeViewLayoutConstraint() {
+        badgeView.translatesAutoresizingMaskIntoConstraints = false
+        let centerXConstraint = NSLayoutConstraint(item: badgeView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: 0)
+        let centerYConstraint = NSLayoutConstraint(item: badgeView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0)
+        let widthConstraint = NSLayoutConstraint(item: badgeView, attribute: .width, relatedBy: .greaterThanOrEqual, toItem: badgeView, attribute: .height, multiplier: 1.0, constant: 0)
+        let heightConstraint = NSLayoutConstraint(item: badgeView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 18)
+        addConstraints([centerXConstraint, centerYConstraint])
+        badgeView.addConstraints([widthConstraint, heightConstraint])
+    }
+}
+
+// MARK: - getter/setter
+extension UIView {
+
+    public var badgeView: NXBadgeControl {
+        get {
+            if let aValue = objc_getAssociatedObject(self, &kBadgeView) as? NXBadgeControl {
+                return aValue
+            }
+            else {
+                let badgeControl = NXBadgeControl.default()
+                self.addSubview(badgeControl)
+                self.bringSubviewToFront(badgeControl)
+                self.badgeView = badgeControl
+                self.addBadgeViewLayoutConstraint()
+                return badgeControl
+            }
+        }
+        set {
+            objc_setAssociatedObject(self, &kBadgeView, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+        }
+    }
+    
+       public  func topConstraint(with item: AnyObject?) -> NSLayoutConstraint? {
+        return constraint(with: item, attribute: .top)
+    }
+    
+       public  func leadingConstraint(with item: AnyObject?) -> NSLayoutConstraint? {
+        return constraint(with: item, attribute: .leading)
+    }
+    
+       public  func bottomConstraint(with item: AnyObject?) -> NSLayoutConstraint? {
+        return constraint(with: item, attribute: .bottom)
+    }
+
+       public  func trailingConstraint(with item: AnyObject?) -> NSLayoutConstraint? {
+        return constraint(with: item, attribute: .trailing)
+    }
+    
+       public  func widthConstraint() -> NSLayoutConstraint? {
+        return constraint(with: self, attribute: .width)
+    }
+    
+       public  func heightConstraint() -> NSLayoutConstraint? {
+        return constraint(with: self, attribute: .height)
+    }
+
+       public  func centerXConstraint(with item: AnyObject?) -> NSLayoutConstraint? {
+        return constraint(with: item, attribute: .centerX)
+    }
+    
+       public  func centerYConstraint(with item: AnyObject?) -> NSLayoutConstraint? {
+        return constraint(with: item, attribute: .centerY)
+    }
+    
+       public  func constraint(with item: AnyObject?, attribute: NSLayoutConstraint.Attribute) -> NSLayoutConstraint? {
+        for constraint in constraints {
+            if let isSame = constraint.firstItem?.isEqual(item), isSame, constraint.firstAttribute == attribute {
+                return constraint
+            }
+        }
+        return nil
+    }
+}

+ 166 - 0
BFCommonKit/Classes/BFBase/View/bubbleLayer/NXBubbleLayer.swift

@@ -0,0 +1,166 @@
+//
+//  NXBubbleLayer.swift
+//  bubbleLayer_swift
+//
+//  Created by liuming on 2020/8/23.
+//  Copyright © 2020 liuming. All rights reserved.
+//
+
+import UIKit
+// 箭头方向枚举
+public enum ArrowDirection: Int {
+    case right = 0 // 指向右边, 即在圆角矩形的右边
+    case bottom = 1 // 指向下边
+    case left = 2 // 指向左边
+    case top = 3 // 指向上边
+}
+
+class NXBubbleLayer: NSObject {
+    // 矩形的圆角的半径
+    var cornerRadius: CGFloat = 8
+    // 箭头位置的圆角半径
+    var arrowRadius: CGFloat = 3
+    // 箭头的高度
+    var arrowHeight: CGFloat = 12
+    // 箭头的宽度
+    var arrowWidth: CGFloat = 30
+    // 箭头方向
+    var arrowDirection: ArrowDirection = .bottom
+    // 箭头的相对位置
+    var arrowPosition: CGFloat = 0.5
+    // 这里的size是需要mask成气泡形状的view的size
+    public var size: CGSize = CGSize.zero
+
+    /// 气泡layer 在视图层的位置
+    public var bubbleLayerRect: CGRect = .zero
+
+    init(originalSize: CGSize) {
+        size = originalSize
+    }
+
+    // 最终拿这个layer去设置mask
+    func layer() -> CAShapeLayer {
+        let layer = CAShapeLayer()
+        layer.path = bubblePath()
+        return layer
+    }
+
+    // 绘制气泡形状,获取path
+    func bubblePath() -> CGPath? {
+        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
+        let ctx = UIGraphicsGetCurrentContext()
+
+        // 获取绘图所需要的关键点
+        let points = keyPoints()
+
+        // 第一步是要画箭头的“第一个支点”所在的那个角,所以要把“笔”放在这个支点顺时针顺序的上一个点
+        // 所以把“笔”放在最后才画的矩形框的角的位置, 准备开始画箭头
+        let currentPoint = points[6]
+        ctx?.move(to: currentPoint)
+
+        // 用于 CGContextAddArcToPoint函数的变量
+        var pointA = CGPoint.zero
+        var pointB = CGPoint.zero
+        var radius: CGFloat = 0
+        var count: Int = 0
+
+        while count < 7 {
+            // 整个过程需要画七个圆角(矩形框的四个角和箭头处的三个角),所以分为七个步骤
+
+            // 箭头处的三个圆角和矩形框的四个圆角不一样
+            radius = count < 3 ? arrowRadius : cornerRadius
+
+            pointA = points[count]
+            pointB = points[(count + 1) % 7]
+            // 画矩形框最后一个角的时候,pointB就是points[0]
+
+            ctx?.addArc(tangent1End: pointA, tangent2End: pointB, radius: radius)
+
+            count = count + 1
+        }
+
+        ctx?.closePath()
+        UIGraphicsEndImageContext()
+
+        return ctx?.path?.copy()
+    }
+
+    // 关键点: 绘制气泡形状前,需要计算箭头的三个点和矩形的四个角的点的坐标
+    func keyPoints() -> [CGPoint] {
+        // 先确定箭头的三个点
+        var beginPoint = CGPoint.zero // 按顺时针画箭头时的第一个支点,例如箭头向上时的左边的支点
+        var topPoint = CGPoint.zero // 顶点
+        var endPoint = CGPoint.zero // 另外一个支点
+
+        // 箭头顶点topPoint的X坐标(或Y坐标)的范围(用来计算arrowPosition)
+        let tpXRange = size.width - 2 * cornerRadius - arrowWidth
+        let tpYRange = size.height - 2 * cornerRadius - arrowWidth
+
+        // 用于表示矩形框的位置和大小
+        var rX: CGFloat = 0
+        var rY: CGFloat = 0
+        var rWidth = size.width
+        var rHeight = size.height
+
+        // 计算箭头的位置,以及调整矩形框的位置和大小
+        switch arrowDirection {
+        case .right: // 箭头在右时
+            topPoint = CGPoint(x: size.width, y: size.height / 2 + tpYRange * (arrowPosition - 0.5))
+            beginPoint = CGPoint(x: topPoint.x - arrowHeight, y: topPoint.y - arrowWidth / 2)
+            endPoint = CGPoint(x: beginPoint.x, y: beginPoint.y + arrowWidth)
+
+            rWidth = rWidth - arrowHeight // 矩形框右边的位置“腾出”给箭头
+
+        case .bottom: // 箭头在下时
+            topPoint = CGPoint(x: size.width / 2 + tpXRange * (arrowPosition - 0.5), y: size.height)
+            beginPoint = CGPoint(x: topPoint.x + arrowWidth / 2, y: topPoint.y - arrowHeight)
+            endPoint = CGPoint(x: beginPoint.x - arrowWidth, y: beginPoint.y)
+
+            rHeight = rHeight - arrowHeight
+
+        case .left: // 箭头在左时
+            topPoint = CGPoint(x: 0, y: size.height / 2 + tpYRange * (arrowPosition - 0.5))
+            beginPoint = CGPoint(x: topPoint.x + arrowHeight, y: topPoint.y + arrowWidth / 2)
+            endPoint = CGPoint(x: beginPoint.x, y: beginPoint.y - arrowWidth)
+
+            rX = arrowHeight
+            rWidth = rWidth - arrowHeight
+
+        case .top: // 箭头在上时
+            topPoint = CGPoint(x: size.width / 2 + tpXRange * (arrowPosition - 0.5), y: 0)
+            beginPoint = CGPoint(x: topPoint.x - arrowWidth / 2, y: topPoint.y + arrowHeight)
+            endPoint = CGPoint(x: beginPoint.x + arrowWidth, y: beginPoint.y)
+
+            rY = arrowHeight
+            rHeight = rHeight - arrowHeight
+
+        default:
+            ()
+        }
+        bubbleLayerRect = CGRect(x: rX, y: rY, width: rWidth, height: rHeight)
+        // 先把箭头的三个点放进关键点数组中
+        var points = [beginPoint, topPoint, endPoint]
+
+        // 确定圆角矩形的四个点
+        let bottomRight = CGPoint(x: rX + rWidth, y: rY + rHeight) // 右下角的点
+        let bottomLeft = CGPoint(x: rX, y: rY + rHeight)
+        let topLeft = CGPoint(x: rX, y: rY)
+        let topRight = CGPoint(x: rX + rWidth, y: rY)
+
+        // 先放在一个临时数组, 放置顺序跟下面紧接着的操作有关
+        let rectPoints = [bottomRight, bottomLeft, topLeft, topRight]
+
+        // 绘制气泡形状的时候,从箭头开始,顺时针地进行
+        // 箭头向右时,画完箭头之后会先画到矩形框的右下角
+        // 所以此时先把矩形框右下角的点放进关键点数组,其他三个点按顺时针方向添加
+        // 箭头在其他方向时,以此类推
+
+        var rectPointIndex: Int = arrowDirection.rawValue
+        for _ in 0...3 {
+            points.append(rectPoints[rectPointIndex])
+            rectPointIndex = (rectPointIndex + 1) % 4
+        }
+
+        return points
+    }
+}

+ 23 - 0
BFCommonKit/Classes/BFBase/View/bubbleLayer/NXContainView.swift

@@ -0,0 +1,23 @@
+//
+//  NXContainView.swift
+//  bubbleLayer_swift
+//
+//  Created by liuming on 2020/9/1.
+//  Copyright © 2020 liuming. All rights reserved.
+//
+
+import UIKit
+public class NXBubbleContainView: UIView {
+    override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
+        var view = super.hitTest(point, with: event)
+        if view == nil {
+            subviews.forEach { subView in
+                let p = subView.convert(point, from: self)
+                if subView.bounds.contains(p) {
+                    view = subView
+                }
+            }
+        }
+        return view
+    }
+}

+ 73 - 0
BFCommonKit/Classes/BFBase/View/bubbleLayer/NXInteractiveView.swift

@@ -0,0 +1,73 @@
+//
+//  NXInteractiveView.swift
+//  bubbleLayer_swift
+//
+//  Created by liuming on 2020/8/23.
+//  Copyright © 2020 liuming. All rights reserved.
+//
+
+import UIKit
+public class NXInteractiveView: UIView {
+    public var tapGestureRecognizer: UITapGestureRecognizer?
+    public var longPressGestureRecognizer: UILongPressGestureRecognizer?
+    // 点击回调
+    public var tapGestureHander: (() -> Void)?
+    // 长按回调
+    public var longPressGestureHander: (() -> Void)?
+
+    override public init(frame: CGRect) {
+        super.init(frame: frame)
+        addTagGestureRecognizer()
+        addLongGestureRecogizer()
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    // 单机方法
+    private func addTagGestureRecognizer() {
+        let tap = UITapGestureRecognizer(target: self, action: #selector(tapGestureRecognizerHandler(sender:)))
+        tap.numberOfTouchesRequired = 1
+        tap.numberOfTapsRequired = 1
+        addGestureRecognizer(tap)
+        tapGestureRecognizer = tap
+    }
+
+    @objc
+    public func tapGestureRecognizerHandler(sender _: UITapGestureRecognizer) {
+        print("---- tapGestureRecognizerHandler -----")
+        if tapGestureHander != nil {
+            tapGestureHander!()
+        }
+    }
+
+    // 长按事件
+    private func addLongGestureRecogizer() {
+        let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longGestureRecognizerHandler(sender:)))
+        longPressGestureRecognizer.numberOfTapsRequired = 1
+        longPressGestureRecognizer.numberOfTouchesRequired = 1
+        addGestureRecognizer(longPressGestureRecognizer)
+        self.longPressGestureRecognizer = longPressGestureRecognizer
+    }
+
+    @objc
+    public func longGestureRecognizerHandler(sender _: UILongPressGestureRecognizer) {
+        print("---- longGestureRecognizerHandler -----")
+        if longPressGestureHander != nil {
+            longPressGestureHander!()
+        }
+    }
+
+    public func removeTapGestureRecognizer() {
+        if let tap = tapGestureRecognizer {
+            removeGestureRecognizer(tap)
+        }
+    }
+
+    public func removeLongPressGestureRecognizer() {
+        if let long = longPressGestureRecognizer {
+            removeGestureRecognizer(long)
+        }
+    }
+}

+ 80 - 0
BFCommonKit/Classes/BFBase/View/bubbleLayer/NXNormalBubbleView.swift

@@ -0,0 +1,80 @@
+//
+//  NXBaseBubbleView.swift
+//  bubbleLayer_swift
+//
+//  Created by liuming on 2020/8/23.
+//  Copyright © 2020 liuming. All rights reserved.
+//
+
+import Foundation
+import UIKit
+public class NXNormalBubbleView: NXInteractiveView {
+    let bubbleLayer = NXBubbleLayer(originalSize: .zero)
+    var currentLayer: CALayer?
+    /// 内部内容控件
+    public var containView = NXBubbleContainView(frame: .zero)
+
+    // 矩形的圆角的半径
+    public var cornerRadius: CGFloat = 8
+    // 箭头位置的圆角半径
+    public var arrowRadius: CGFloat = 3
+    // 箭头的高度
+    public var arrowHeight: CGFloat = 12
+    // 箭头的宽度
+    public var arrowWidth: CGFloat = 30
+    // 箭头方向
+    public var arrowDirection: ArrowDirection = .bottom
+    // 箭头的相对位置
+    public var arrowPosition: CGFloat = 0.5
+
+    public var bubbleColor: UIColor = .white {
+        didSet {
+            self.setNeedsLayout()
+            self.layoutIfNeeded()
+        }
+    }
+
+    override public init(frame: CGRect) {
+        super.init(frame: frame)
+        addSubview(containView)
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override public func layoutSubviews() {
+        super.layoutSubviews()
+        if let layer = currentLayer {
+            layer.removeFromSuperlayer()
+        }
+        bubbleLayer.size = frame.size
+        bubbleLayer.cornerRadius = cornerRadius
+        bubbleLayer.arrowRadius = arrowRadius
+        bubbleLayer.arrowHeight = arrowHeight
+        bubbleLayer.arrowWidth = arrowWidth
+        bubbleLayer.arrowDirection = arrowDirection
+        bubbleLayer.arrowPosition = arrowPosition
+        let layer = bubbleLayer.layer()
+        layer.fillColor = bubbleColor.cgColor
+        self.layer.insertSublayer(layer, at: 0)
+        currentLayer = layer
+        // 调整 contain坐标
+        containView.frame = bubbleLayer.bubbleLayerRect
+
+        backgroundColor = .clear
+        containView.backgroundColor = .clear
+    }
+
+    // MARK: - 重写交互层的长按和点击事件
+
+    override public func tapGestureRecognizerHandler(sender: UITapGestureRecognizer) {
+        super.tapGestureRecognizerHandler(sender: sender)
+        print("点击了普通气泡")
+    }
+
+    override public func longGestureRecognizerHandler(sender: UILongPressGestureRecognizer) {
+        super.longGestureRecognizerHandler(sender: sender)
+        print("长按了 点击了普通气泡")
+    }
+}

+ 40 - 0
BFCommonKit/Classes/BFBase/View/bubbleLayer/NXTextBubbleView.swift

@@ -0,0 +1,40 @@
+//
+//  NXTextBubbleView.swift
+//  bubbleLayer_swift
+//
+//  Created by liuming on 2020/8/23.
+//  Copyright © 2020 liuming. All rights reserved.
+//
+
+import SnapKit
+import UIKit
+
+class NXTextBubbleView: NXNormalBubbleView {
+    public let textLabel = UILabel(frame: .zero)
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        initSubViews()
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    private func initSubViews() {
+        containView.addSubview(textLabel)
+        textLabel.snp.makeConstraints { make in
+            make.edges.equalTo(UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5))
+        }
+    }
+
+    // MARK: - 重写交互层的长按和点击事件
+
+    override public func tapGestureRecognizerHandler(sender _: UITapGestureRecognizer) {
+        print("点击了文字气泡")
+    }
+
+    override public func longGestureRecognizerHandler(sender _: UILongPressGestureRecognizer) {
+        print("长按了 点击了文字气泡")
+    }
+}

+ 184 - 0
BFCommonKit/Classes/BFBase/View/bubbleLayer/NXVoiceBubbleView.swift

@@ -0,0 +1,184 @@
+//
+//  NXVoiceBubbleView.swift
+//  bubbleLayer_swift
+//
+//  Created by liuming on 2020/8/23.
+//  Copyright © 2020 liuming. All rights reserved.
+//
+
+import UIKit
+public class NXVoiceBubbleView: NXNormalBubbleView {
+    /// 关闭按钮
+    private let closeBtn = UIButton(type: .custom)
+    /// 声音图片
+    private let voiceImgView = UIImageView(frame: .zero)
+
+    /// 语音时间文本
+    public let durationLabel = UILabel(frame: .zero)
+    /// 语音图片序列帧动画时间
+    public var animationDuration: TimeInterval = 0.5 {
+        didSet {
+            self.initVoiceAnimation()
+        }
+    }
+
+    // 加载圈
+    lazy var activityIndicator: UIActivityIndicatorView = {
+        let activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView(style:
+            .gray)
+        return activityIndicator
+    }()
+
+    /// 语音图片帧图片
+    public var animationImages: [UIImage] = Array() {
+        didSet {
+            initVoiceAnimation()
+        }
+    }
+
+    /// 语音图片动画重复次数
+    public var animationRepeatCount: Int = 1 {
+        didSet {
+            initVoiceAnimation()
+        }
+    }
+
+    /// 音频显示的总时间
+    public var duration: Float64 = 0 {
+        didSet {
+            showTime()
+            activityIndicatorStop()
+        }
+    }
+
+    public var closeBtnClickedHander: (() -> Void)?
+    override public init(frame: CGRect) {
+        super.init(frame: frame)
+        initSubviews()
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    private func initSubviews() {
+        containView.addSubview(voiceImgView)
+        voiceImgView.image = UIImage(named: "icon_voice")
+
+        containView.addSubview(durationLabel)
+
+        closeBtn.setImage(UIImage(named: "videomk_serach_clear"), for: .normal)
+        closeBtn.setImage(UIImage(named: "videomk_serach_clear"), for: .highlighted)
+        closeBtn.addTarget(self, action: #selector(closeBtnClicked(sender:)), for: .touchUpInside)
+        containView.addSubview(closeBtn)
+
+        containView.addSubview(activityIndicator)
+
+        // 布局
+        voiceImgView.snp.makeConstraints { maker in
+            maker.centerY.equalTo(self.containView)
+            maker.left.equalTo(self.containView).offset(10)
+            maker.width.height.equalTo(20)
+        }
+        durationLabel.snp.makeConstraints { maker in
+            maker.right.equalTo(self.containView).offset(-10)
+            maker.centerY.equalTo(self.containView)
+            maker.height.equalTo(20)
+        }
+        closeBtn.snp.makeConstraints { maker in
+
+            maker.right.equalTo(self.containView).offset(10)
+            maker.top.equalTo(self.containView).offset(-8)
+            maker.width.height.equalTo(20)
+        }
+
+        activityIndicator.snp.makeConstraints { maker in
+
+            maker.right.equalTo(self.containView).offset(-10)
+            maker.centerY.equalTo(self.containView)
+            maker.height.equalTo(20)
+        }
+    }
+
+    private func showTime() {
+        durationLabel.text = duration < 1 ? "1'" : duration.formatDurationToMS()
+    }
+
+    private func initVoiceAnimation() {
+        if animationImages.count > 0 {
+            voiceImgView.animationImages = animationImages
+            voiceImgView.animationDuration = animationDuration
+            voiceImgView.animationRepeatCount = animationRepeatCount
+        }
+    }
+
+    /// 加载圈开始动画
+    public func activityIndicatorStart() {
+        durationLabel.text = ""
+        activityIndicator.startAnimating()
+    }
+
+    /// 加载圈结束动画
+    public func activityIndicatorStop() {
+        activityIndicator.stopAnimating()
+    }
+
+    /// 开始动画
+    public func startAnimation() {
+        voiceImgView.startAnimating()
+    }
+
+    /// 结束动画
+    public func stopAnimation() {
+        voiceImgView.stopAnimating()
+    }
+
+    /// 按照中心点抖动
+    func animation() {
+        let animati = CAKeyframeAnimation(keyPath: "transform.rotation")
+        // rotation 旋转,需要添加弧度值
+        // 角度转弧度
+        animati.values = [angle2Radion(angle: -50), angle2Radion(angle: 50), angle2Radion(angle: -50)]
+        animati.repeatCount = 4
+        layer.add(animati, forKey: nil)
+    }
+
+    // MARK: - 重写交互层的长按和点击事件
+
+    override public func tapGestureRecognizerHandler(sender: UITapGestureRecognizer) {
+        super.tapGestureRecognizerHandler(sender: sender)
+        startAnimation()
+        print("点击了语音气泡")
+    }
+
+    override public func longGestureRecognizerHandler(sender: UILongPressGestureRecognizer) {
+        super.longGestureRecognizerHandler(sender: sender)
+        print("长按了 点击了语音气泡")
+        animation()
+    }
+
+    func angle2Radion(angle: Float) -> Float {
+        return angle / Float(180.0 * Double.pi)
+    }
+
+    // MARK: 关闭按钮点击事件
+
+    @objc
+    func closeBtnClicked(sender _: UIButton) {
+        print("点击了关闭按钮")
+        if let block = closeBtnClickedHander {
+            block()
+        }
+    }
+
+    override public func point(inside point: CGPoint, with _: UIEvent?) -> Bool {
+        if bounds.contains(point) {
+            return true
+        }
+        let p = convert(point, to: closeBtn)
+        if closeBtn.bounds.contains(p) {
+            return true
+        }
+        return false
+    }
+}

+ 34 - 0
BFCommonKit/Classes/BFCategorys/BFColor+Ext.swift

@@ -44,6 +44,40 @@ public extension UIColor {
         return UIColor(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: 1)
     }
 
+    public  class func hexColor(hexadecimal: String,alpha:CGFloat = 1) -> UIColor {
+        var cstr = hexadecimal.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).uppercased() as NSString
+        if cstr.length < 6 {
+            return UIColor.clear
+        }
+        if cstr.hasPrefix("0X") {
+            cstr = cstr.substring(from: 2) as NSString
+        }
+        if cstr.hasPrefix("#") {
+            cstr = cstr.substring(from: 1) as NSString
+        }
+        if cstr.length != 6 {
+            return UIColor.clear
+        }
+        var range = NSRange()
+        range.location = 0
+        range.length = 2
+        // r
+        let rStr = cstr.substring(with: range)
+        // g
+        range.location = 2
+        let gStr = cstr.substring(with: range)
+        // b
+        range.location = 4
+        let bStr = cstr.substring(with: range)
+        var r: UInt32 = 0x0
+        var g: UInt32 = 0x0
+        var b: UInt32 = 0x0
+        Scanner(string: rStr).scanHexInt32(&r)
+        Scanner(string: gStr).scanHexInt32(&g)
+        Scanner(string: bStr).scanHexInt32(&b)
+        return UIColor(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: alpha)
+    }
+    
     // MARK: - hex (0x000000) -> UIColor
 
     ///

+ 25 - 1
BFCommonKit/Classes/BFCategorys/BFInt+Ext.swift → BFCommonKit/Classes/BFCategorys/BFNumber+Ext.swift

@@ -1,5 +1,5 @@
 //
-//  Int+Ext.swift
+//  BFNumber+Ext.swift
 //  PQSpeed
 //
 //  Created by SanW on 2020/7/20.
@@ -106,3 +106,27 @@ public extension Float64 {
         return result
     }
 }
+
+extension Float {
+    /// 准确的小数尾截取 - 没有进位
+    /*
+     // 11.999003  -> 12.0
+     var pp = 11.999003
+     String(format: "%.1f", pp)  这个方法会进行四舍五入
+     */
+    func decimalString(_ base: Self = 1) -> String {
+       return "\(self.decimalNumber(base))"
+    }
+    func decimalNumber(_ base: Self = 1) -> Float {
+        let tempCount: Self = pow(10, base)
+        let temp = self*tempCount
+        
+        let target = Self(Int(temp))
+        let stepone = target/tempCount
+        if stepone.truncatingRemainder(dividingBy: 1) == 0 {
+            return Float(String(format: "%.0f", stepone)) ?? 0.0
+        }else{
+            return stepone
+        }
+    }
+}

+ 33 - 0
BFFloat+Ext.swift

@@ -0,0 +1,33 @@
+//
+//  BFFloat+Ext.swift
+//  BFFramework
+//
+//  Created by ak on 2021/10/11.
+//
+
+import Foundation
+
+extension Float {
+    /// 准确的小数尾截取 - 没有进位
+    /*
+     // 11.999003  -> 12.0
+     var pp = 11.999003
+     String(format: "%.1f", pp)  这个方法会进行四舍五入
+     */
+    func decimalString(_ base: Self = 1) -> String {
+       return "\(self.decimalNumber(base))"
+    }
+    func decimalNumber(_ base: Self = 1) -> Float {
+        let tempCount: Self = pow(10, base)
+        let temp = self*tempCount
+        
+        let target = Self(Int(temp))
+        let stepone = target/tempCount
+        if stepone.truncatingRemainder(dividingBy: 1) == 0 {
+            return Float(String(format: "%.0f", stepone)) ?? 0.0
+        }else{
+            return stepone
+        }
+    }
+}
+ 

+ 70 - 0
UICollectionView+Ext.swift

@@ -0,0 +1,70 @@
+//
+//  UICollectionView+Ext.swift
+//  PQCreativeCommunity
+//
+//  Created by SanW on 2021/8/4.
+//  Copyright © 2021 BytesFlow. All rights reserved.
+//
+
+import Foundation
+import MJRefresh
+
+extension UICollectionView{
+    /// 获取当前cell
+    /// - Returns: <#description#>
+    func visibleCell() -> UICollectionViewCell? {
+        let visibleRect = CGRect(origin: contentOffset, size: bounds.size)
+        let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
+        guard let visibleIndexPath = indexPathForItem(at: visiblePoint) else { return nil }
+        return cellForItem(at: visibleIndexPath)
+    }
+
+    /// 添加刷新组件
+    /// - Parameters:
+    ///   - scroller: <#scroller description#>
+    ///   - type: 1-头部跟尾部 2-头部 3-尾部
+    public func addRefreshView(type: REFRESH_TYPE = .REFRESH_TYPE_ALL, refreshHandle: ((_ isHeader: Bool) -> Void)?) {
+        if type == .REFRESH_TYPE_ALL || type == .REFRESH_TYPE_HEADER {
+            let header = MJRefreshNormalHeader.init {
+                if refreshHandle != nil {
+                    refreshHandle!(true)
+                }
+            }
+            header.setTitle("下拉刷新", for: .willRefresh)
+            header.setTitle("正在刷新...", for: .refreshing)
+            header.setTitle("松开刷新", for: .pulling)
+            header.setTitle("下拉刷新", for: .idle)
+            header.lastUpdatedTimeLabel?.isHidden = true
+            mj_header = header
+        }
+        if type == .REFRESH_TYPE_ALL || type == .REFRESH_TYPE_FOOTER {
+            // MJRefreshBackNormalFooter 不会附在上面
+            // MJRefreshAutoFooter 不会便宜
+            let footer = MJRefreshBackNormalFooter.init {
+                if refreshHandle != nil {
+                    refreshHandle!(false)
+                }
+            }
+            footer.setTitle("暂时没有更多了", for: .noMoreData)
+            footer.setTitle("精彩内容正在加载中...", for: .refreshing)
+            mj_footer = footer
+        }
+        
+        if type == .REFRESH_TYPE_AUTOFOOTER {
+            let footer = MJRefreshAutoNormalFooter.init {
+                if refreshHandle != nil {
+                    refreshHandle!(false)
+                }
+            }
+            footer.triggerAutomaticallyRefreshPercent = -70
+            footer.setTitle("暂时没有更多了", for: .noMoreData)
+            footer.setTitle("精彩内容正在加载中...", for: .refreshing)
+            mj_footer = footer
+        }
+    }
+
+    func indexPathsForElements(in rect: CGRect) -> [IndexPath] {
+        let allLayoutAttributes = collectionViewLayout.layoutAttributesForElements(in: rect)!
+        return allLayoutAttributes.map { $0.indexPath }
+    }
+}