LoginSignup
0
1

More than 5 years have passed since last update.

Add a fitted notification badge to a UIImage

Last updated at Posted at 2018-11-22

It is common to use a notification badge to alert the user to unread or new features, accessible when pressing a button. Here, I show a method of adding this badge (e.g. a white text number inside a red circle) for any image. The size of the badge circle is defined relative to the original image, and a looping font fitting approach is used to ensure the text fits inside the circle.

static func addNotificationBadgeToImage(source: UIImage, text: String) -> UIImage? {

    //define the badge as half the size of the original image, and located in the top-right corner
    let iconSizeFraction: CGFloat = 0.5
    let iconOffset = CGPoint(x: 0.5, y: 0.0)

    let diameter: CGFloat = iconSizeFraction * source.size.width
    let iconSize = CGSize(width: diameter, height: diameter)

    // define the new size of the original image
    let newSize = CGSize(
        width: max(source.size.width, iconOffset.x + iconSize.width) - min(0.0, iconOffset.x),
        height: max(source.size.height, iconOffset.y + iconSize.height) - min(0.0, iconOffset.y)
    )

    // start drawing
    UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
    let context = UIGraphicsGetCurrentContext()!
    context.saveGState()

    // put the origin of the coordinate system at the top left
    if let cgImage = source.cgImage {
        context.translateBy(x: 0, y: source.size.height)
        context.scaleBy(x: 1.0, y: -1.0)
        let rect = CGRect(x: 0.0, y: 0.0, width: source.size.width, height: source.size.height)
        context.draw(cgImage, in: rect)
    }
    context.restoreGState()

    // define the rectangle in which the badge should be drawn
    let badgeRect = CGRect(
        x: source.size.width * iconOffset.x,
        y: source.size.height * iconOffset.y,
        width: iconSize.width,
        height: iconSize.height
    )

    // draw the red badge circle
    if let circle = drawCircle(diameter: diameter, color: UIColor.red)?.cgImage {
        context.draw(circle, in: badgeRect)
    }

    let image = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()

    // add the label to this new image
    if let imageWIthText = DrawClass.textToImage(
        text: text,
        image: image,
        rect: badgeRect) { return imageWIthText }
    return image
}

A simple function to draw a circle with solid color fill with a specified diameter

static func drawCircle(diameter: CGFloat, color: UIColor) -> UIImage? {
    UIGraphicsBeginImageContextWithOptions(CGSize(width: diameter, height: diameter), false, 0.0)
    let ctx = UIGraphicsGetCurrentContext()!
    ctx.saveGState()
    ctx.setFillColor(color.cgColor)
    let rect = CGRect(x: 0, y: 0, width: diameter, height: diameter)
    ctx.fillEllipse(in: rect)
    ctx.restoreGState()
    let image = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()
    return image
}

This function draws the text in the specified rect.

static func textToImage(text: String, image: UIImage, rect: CGRect) -> UIImage? {

    // initialize new image with source image
    let scale = UIScreen.main.scale
    UIGraphicsBeginImageContextWithOptions(image.size, false, scale)
    image.draw(in: CGRect(origin: CGPoint.zero, size: image.size))

    // set limits on text area and find matching font
    let padding = 0.075 * rect.size.width
    let textWidth = rect.size.width - 2 * padding

    // use a UIFont extension to find the optimal font for the given size constraints
    // e.g. https://qiita.com/cayozin/items/f574fa803eeeb6b3310a
    guard let font = UIFont.createFittedFont(maxWidth: textWidth, maxHeight: textWidth, text: text, fontName: "Helvetica Bold") else {
        return nil
    }

    // find the equivalent size in pixels and use the text height to center it in the circle
    let size = (text as NSString).size(withAttributes: [NSAttributedStringKey.font : font])
    let xOffset = padding
    let yOffset = 0.5 * rect.size.height - 0.5 * size.height
    let rect = CGRect(
        origin: CGPoint(x: rect.origin.x + xOffset, y: rect.origin.y + yOffset),
        size: CGSize(width: textWidth, height: font.lineHeight)
    )

    // define properties of the text to be drawn
    let textColor = UIColor.white
    let paragraph = NSMutableParagraphStyle()
    paragraph.alignment = .center
    let textFontAttributes = [
        NSAttributedStringKey.font: font,
        NSAttributedStringKey.paragraphStyle: paragraph,
        NSAttributedStringKey.foregroundColor: textColor,
        ] as [NSAttributedStringKey : Any]
    text.draw(in: rect, withAttributes: textFontAttributes)

    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return newImage
}

Finally, we need to calculate the correct font size to ensure the text fits inside the specified rect. I recommend using my looping font fitting extension to UIFont to calculate this:

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1