Skip to content

Instantly share code, notes, and snippets.

@mohn93
Forked from freedom27/UIBarButtonItem+Badge.swift
Last active September 18, 2017 23:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mohn93/bd950eeed82749b2ecad40ba3d4d0f1d to your computer and use it in GitHub Desktop.
Save mohn93/bd950eeed82749b2ecad40ba3d4d0f1d to your computer and use it in GitHub Desktop.

This is fork from UIBarButtonItem+Badge.swift ##Features

  • Added support for RTL, i mean positioning the badge in the opposite side based on view direction
  • Added support to change the text color
  • Make the badge width expandable based on the label content
  • Solved the problem of the View property can't be found on ios 10 and replace the implementation idea with UIButtonView
  • Since i use cutomview property to implement the solution the view will be replaced with the customview, so i let the customview take the image of the original UIBarButtonItem

Code Example

I changed implementation from extension based to extending the UIBarButtonItem, so you have to set the class in the identity tab for the desired bar button item to BadgeUIBarButtonItem.

After that you can add the badge like this

barButtonItem.addBadge(number: 10)

and when ever you want toe update the value you call

barButtonItem.updateBadge(number: 20)

You can also remove it

barButtonItem.removeBadge()
//
// UIBarButtonBadge.swift
// Finzione
//
// Created by Mohaned Benmesken on 8/31/17.
// Copyright © 2017 Mohaned Benmesken. All rights reserved.
//
import Foundation
import UIKit
extension CAShapeLayer {
//previous method used for creating circle badge
func drawCircleAtLocation(location: CGPoint, withRadius radius: CGFloat, andColor color: UIColor, filled: Bool) {
fillColor = filled ? color.cgColor : UIColor.white.cgColor
strokeColor = color.cgColor
let origin = CGPoint(x: location.x - radius, y: location.y - radius)
path = UIBezierPath(ovalIn: CGRect(origin: origin, size: CGSize(width: radius * 2, height: radius * 2))).cgPath
}
func drawRoundedRectAtLocation(location: CGPoint,size: CGSize, withRadius radius: CGFloat, andColor color: UIColor, filled: Bool) {
fillColor = filled ? color.cgColor : UIColor.white.cgColor
strokeColor = color.cgColor
let origin = CGPoint(x: location.x - radius, y: location.y - radius)
//path = UIBezierPath(ovalIn: CGRect(origin: origin, size: CGSize(width: radius * 2, height: radius * 2))).cgPath
path = UIBezierPath(roundedRect: CGRect(origin: origin, size: size), cornerRadius: radius).cgPath
}
}
private var handle: UInt8 = 0;
// this class made for the badge uibarbutton and made to have flexible width based on the content
class BadgeUIBarButtonItem : UIBarButtonItem{
private var badgeLayer: CAShapeLayer? {
if let b: AnyObject = objc_getAssociatedObject(self, &handle) as AnyObject? {
return b as? CAShapeLayer
} else {
return nil
}
}
var btn = UIButton()
override func awakeFromNib() {
// setting background image for the UIBarImage if it exist
btn.frame = CGRect(x: 0, y: 0, width: 32, height: 32)
btn.setBackgroundImage(self.image, for: .normal)
}
func addBadge(number: Int, withOffset offset: CGPoint = CGPoint.zero, andColor color: UIColor = UIColor.yellow, andFilled filled: Bool = true, fontColor:UIColor = UIColor.black) {
badgeLayer?.removeFromSuperlayer()
// Initialize Badge
let badge = CAShapeLayer()
// check if the view is RTL
var isRight = false
if #available(iOS 9.0, *) {
if UIView.userInterfaceLayoutDirection(
for: btn.semanticContentAttribute) == .rightToLeft {
isRight = true
// The view is shown in right-to-left mode right now.
}
} else {
// Use the previous technique
if UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft {
isRight = true
// The view is in right-to-left mode
}
}
// set the position of the badge based if the direction of the view
let location = CGPoint(x: (isRight ? offset.x : btn.frame.width), y: (offset.y))
// Initialiaze Badge's label
let label = CATextLayer()
label.string = "\(number)"
label.alignmentMode = kCAAlignmentCenter
label.fontSize = 11
label.frame = CGRect(origin: CGPoint(x: location.x - 4, y: offset.y-7), size: label.preferredFrameSize())
label.foregroundColor = filled ? fontColor.cgColor : color.cgColor
label.backgroundColor = UIColor.clear.cgColor
label.contentsScale = UIScreen.main.scale
badge.drawRoundedRectAtLocation(location: location, size: CGSize.init(width: label.preferredFrameSize().width+8, height: 16), withRadius: 8, andColor: color, filled: filled)
btn.layer.addSublayer(badge)
badge.addSublayer(label)
customView = btn
// btn.frame = CGRect(x: 0, y: 0, width: width, height: width)
// Save Badge as UIBarButtonItem property
objc_setAssociatedObject(self, &handle, badge, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
func updateBadge(number: Int) {
if let text = badgeLayer?.sublayers?.filter({ $0 is CATextLayer }).first as? CATextLayer {
text.string = "\(number)"
}
}
func removeBadge() {
badgeLayer?.removeFromSuperlayer()
}
}
@grdsdev
Copy link

grdsdev commented Sep 14, 2017

Awesome code, but I'm facing an issue. I have the bar button item in a view controller and when a push another on navigation stack, then pop back to the old view, the badge goes below the image.
See the image below:
screen shot 2017-09-14 at 13 58 57
How do I let it above everything?

@mohn93
Copy link
Author

mohn93 commented Sep 18, 2017

Sorry for the delay i just saw ur comment
Actually i tried to demo what happened to you but i couldn't get the same result, so
Please show me any code that try to call this class, make sure to illustrate the position of the call

And thanks for feedback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment