先来个效果图
需求背景
项目中要实现消息中心, 所以需要显示未读消息的数量.
方案设想过程
一开始想的是通过UILabel+UIView来实现, 通过UILabel显示数字, UIView添加圆角并设置背景色来完成number badge的显示. 但后面一想, UIView切圆角可能会造成离屏渲染影响性能, 还因为label和UIView之间添加的布局约束太多, 导致后面也不好维护. 所以就干脆直接用Core Graphics画好了.
实现思路
其实很简单, 拿到需要显示的数字后, 先按照Font来计算数字最终显示需要的text size. 若size的宽度+两边的留白(margin)大于小红点的直径则说明中间需要额外绘制矩形(也就是示例图中, 第2/3/4图的效果) 否则直接画个圆, 就好了.
再贴苹果官方关于bezierPathWithArcCenter:radius:startAngle:endAngle:clockwise:的一张图, 便于理解.
代码实现
废话不多说, 直接上代码, 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
}
}