ios画带数字的badge/小红点

1,143 阅读2分钟

先来个效果图

WechatIMG549.jpeg

需求背景

项目中要实现消息中心, 所以需要显示未读消息的数量.

方案设想过程

一开始想的是通过UILabel+UIView来实现, 通过UILabel显示数字, UIView添加圆角并设置背景色来完成number badge的显示. 但后面一想, UIView切圆角可能会造成离屏渲染影响性能, 还因为label和UIView之间添加的布局约束太多, 导致后面也不好维护. 所以就干脆直接用Core Graphics画好了.

实现思路

其实很简单, 拿到需要显示的数字后, 先按照Font来计算数字最终显示需要的text size. 若size的宽度+两边的留白(margin)大于小红点的直径则说明中间需要额外绘制矩形(也就是示例图中, 第2/3/4图的效果) 否则直接画个圆, 就好了.

再贴苹果官方关于bezier​Path​With​Arc​Center:​radius:​start​Angle:​end​Angle:​clockwise:的一张图, 便于理解.

image.png

代码实现

废话不多说, 直接上代码, GitHub上的可修改的样式多一点. GitHub地址

/// 使用方式
let imageView1 = UIImageView.init(image: UIImage.getNumberBadge(with: "99"));
imageView1.frame = CGRect(x: 0, y: 100, width: 20, height: 20);
extension UIImage{
    /// 获取带数字的badge图片
    static func getNumberBadge(with numStr: String) -> UIImage?{
        let radius = 9.0;
        let margin = 9.0;
        /// 当显示的数字的宽度需要的范围大于 直径 + margin后, 中间的矩形的宽度
        var rectWidth = 0.0;
        let stringSize = numStr.sizeWith(UIFont.systemFont(ofSize: 12), CGSize(width: 300, height: radius*2));
        if (stringSize.width + margin) > radius*2{
            /// 宽度加1后, 显示显示效果更平滑
            rectWidth = stringSize.width + margin - radius*2 + 1;
        }
        let totalWidth = radius * 2 + rectWidth;
        /// 文字position 的Y, 为了垂直居中显示数字
        let numStrDrawPointY = (radius*2 - stringSize.height) / 2;
        /// 最终大小
        let finalSize = CGSize.init(width: totalWidth, height: radius*2);

        UIGraphicsBeginImageContextWithOptions(finalSize, false, UIScreen.main.scale);
        let context = UIGraphicsGetCurrentContext();

        let path = UIBezierPath(arcCenter: CGPoint(x: radius, y: radius), radius: CGFloat(radius), startAngle: Double.pi/2, endAngle: Double.pi*1.5, clockwise: true); /// 先画左边的半圆
        path.addLine(to: CGPoint(x: radius + rectWidth, y: 0)); /// 画线到右边半圆的起始位置
        path.addArc(withCenter: CGPoint(x: radius + rectWidth, y: radius), radius: CGFloat(radius), startAngle: Double.pi*1.5, endAngle: Double.pi/2, clockwise: true); /// 画右边半圆
        path.addLine(to: CGPoint(x: radius, y: radius*2)); /// 画线到左边半圆的下面
        path.close();
        /// 添加指定的path
        context?.addPath(path.cgPath);

        /// 背景色为纯色
        context?.setFillColor(UIColor.red.cgColor);
        context?.drawPath(using: .fill)

        /// 将要显示的文字画到content上
        (numStr as NSString).draw(at: CGPoint(x: 5, y: numStrDrawPointY), withAttributes: [
            NSAttributedString.Key.foregroundColor: UIColor.white,
            NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12)
        ])

        let image = UIGraphicsGetImageFromCurrentImageContext();

        UIGraphicsEndImageContext();

        return image;
    }
}

extension String {
    func sizeWith(_ font : UIFont , _ maxSize : CGSize) ->CGSize {
        let options = NSStringDrawingOptions.usesLineFragmentOrigin
        var attributes : [NSAttributedString.Key : Any] = [:]
        attributes[NSAttributedString.Key.font] = font
        let textBouds = self.boundingRect(with: maxSize,
                                                  options: options,
                                                  attributes: attributes,
                                                  context: nil)
        return textBouds.size
    }
}

参考链接

Apple官方文档