-
-
Save freedom27/c709923b163e26405f62b799437243f4 to your computer and use it in GitHub Desktop.
Its not working in Swift4.
Different approach and works with Swift 4 and various view controller states: https://gist.github.com/yonat/75a0f432d791165b1fd6
I have used this for tabItem but I want to show the layer on top of tabImage but layer is added behind it. Any way through which it can be attained?
This doesn't work unless it is a custom uibarbutton unforunately. :(
I have used this for tabItem but I want to show the layer on top of tabImage but layer is added behind it. Any way through which it can be attained?
u can get it by the property "badgeLayer"
This doesn't work unless it is a custom uibarbutton unforunately. :(
u can extension a button to do it , c the #ugenlik comment
swift 4.2
extension CAShapeLayer {
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
}
}
private var handle: UInt8 = 0;
extension UIBarButtonItem {
private var badgeLayer: CAShapeLayer? {
if let b: AnyObject = objc_getAssociatedObject(self, &handle) as AnyObject? {
return b as? CAShapeLayer
} else {
return nil
}
}
func addBadge(number: Int, withOffset offset: CGPoint = CGPoint.zero, andColor color: UIColor = UIColor.red, andFilled filled: Bool = true) {
guard let view = self.value(forKey: "view") as? UIView else { return }
badgeLayer?.removeFromSuperlayer()
var badgeWidth = 8
var numberOffset = 4
if number > 9 {
badgeWidth = 12
numberOffset = 6
}
// Initialize Badge
let badge = CAShapeLayer()
let radius = CGFloat(7)
let location = CGPoint(x: view.frame.width - (radius + offset.x), y: (radius + offset.y))
badge.drawCircleAtLocation(location: location, withRadius: radius, andColor: color, filled: filled)
view.layer.addSublayer(badge)
// Initialiaze Badge's label
let label = CATextLayer()
label.string = "\(number)"
label.alignmentMode = CATextLayerAlignmentMode.center
label.fontSize = 11
label.frame = CGRect(origin: CGPoint(x: location.x - CGFloat(numberOffset), y: offset.y), size: CGSize(width: badgeWidth, height: 16))
label.foregroundColor = filled ? UIColor.white.cgColor : color.cgColor
label.backgroundColor = UIColor.clear.cgColor
label.contentsScale = UIScreen.main.scale
badge.addSublayer(label)
// 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()
}
}
extension CAShapeLayer {
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
}
}private var handle: UInt8 = 0;
extension UIBarButtonItem {
private var badgeLayer: CAShapeLayer? {
if let b: AnyObject = objc_getAssociatedObject(self, &handle) as AnyObject? {
return b as? CAShapeLayer
} else {
return nil
}
}func addBadge(number: Int, withOffset offset: CGPoint = CGPoint.zero, andColor color: UIColor = UIColor.red, andFilled filled: Bool = true) { guard let view = self.value(forKey: "view") as? UIView else { return } badgeLayer?.removeFromSuperlayer() var badgeWidth = 8 var numberOffset = 4 if number > 9 { badgeWidth = 12 numberOffset = 6 } // Initialize Badge let badge = CAShapeLayer() let radius = CGFloat(7) let location = CGPoint(x: view.frame.width - (radius + offset.x), y: (radius + offset.y)) badge.drawCircleAtLocation(location: location, withRadius: radius, andColor: color, filled: filled) view.layer.addSublayer(badge) // Initialiaze Badge's label let label = CATextLayer() label.string = "\(number)" label.alignmentMode = CATextLayerAlignmentMode.center label.fontSize = 11 label.frame = CGRect(origin: CGPoint(x: location.x - CGFloat(numberOffset), y: offset.y), size: CGSize(width: badgeWidth, height: 16)) label.foregroundColor = filled ? UIColor.white.cgColor : color.cgColor label.backgroundColor = UIColor.clear.cgColor label.contentsScale = UIScreen.main.scale badge.addSublayer(label) // 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() }
}
This is not working.
ok this code is not work for me:
guard let view = self.valueForKey("view") as? UIView else { return }
It always returns nil.
This is my code (Xcode 10, swift 5):
`private lazy var btnNoti: UIBarButtonItem = {
return EBarButtonItem.notification.create(target: self, action: #selector(btnBell_Touched))
}()
self.navigationItem.setLeftBarButton(btnNoti, animated: true)
btnNoti.addBadge(number:10)
`
Update to Swift 5 + fix SwiftLint warnings:
import UIKit
private var handle: UInt8 = 0
extension UIBarButtonItem {
private var badgeLayer: CAShapeLayer? {
if let b: AnyObject = objc_getAssociatedObject(self, &handle) as AnyObject? {
return b as? CAShapeLayer
} else {
return nil
}
}
func setBadge(text: String?, offset: CGPoint = .zero, color: UIColor = .red, filled: Bool = true, fontSize: CGFloat = 11.0) {
badgeLayer?.removeFromSuperlayer()
guard let text = text, !text.isEmpty else {
return
}
addBadge(text: text, offset: offset, color: color, filled: filled)
}
private func addBadge(text: String, offset: CGPoint = .zero, color: UIColor = .red, filled: Bool = true, fontSize: CGFloat = 11) {
guard let view = self.value(forKey: "view") as? UIView else {
return
}
var font = UIFont.systemFont(ofSize: fontSize)
if #available(iOS 9.0, *) {
font = UIFont.monospacedDigitSystemFont(ofSize: fontSize, weight: .regular)
}
let badgeSize = text.size(withAttributes: [.font: font])
// initialize Badge
let badge = CAShapeLayer()
let height = badgeSize.height
var width = badgeSize.width + 2 // padding
// make sure we have at least a circle
if width < height {
width = height
}
// x position is offset from right-hand side
let x = view.frame.width - width + offset.x
let badgeFrame = CGRect(origin: CGPoint(x: x, y: offset.y), size: CGSize(width: width, height: height))
badge.drawRoundedRect(rect: badgeFrame, andColor: color, filled: filled)
view.layer.addSublayer(badge)
// initialiaze Badge's label
let label = CATextLayer()
label.string = text
label.alignmentMode = .center
label.font = font
label.fontSize = font.pointSize
label.frame = badgeFrame
label.foregroundColor = filled ? UIColor.white.cgColor : color.cgColor
label.backgroundColor = UIColor.clear.cgColor
label.contentsScale = UIScreen.main.scale
badge.addSublayer(label)
// save Badge as UIBarButtonItem property
objc_setAssociatedObject(self, &handle, badge, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
// bring layer to front
badge.zPosition = 1_000
}
private func removeBadge() {
badgeLayer?.removeFromSuperlayer()
}
}
// MARK: - Utilities
extension CAShapeLayer {
func drawRoundedRect(rect: CGRect, andColor color: UIColor, filled: Bool) {
fillColor = filled ? color.cgColor : UIColor.white.cgColor
strokeColor = color.cgColor
path = UIBezierPath(roundedRect: rect, cornerRadius: 7).cgPath
}
}
Working in swift 5, same code of @adam-leitgeb , but i only wanted the red dot, without the number, so if anyone is looking for the same, i leave the code here 👍 .
private var handle: UInt8 = 0
extension UIBarButtonItem {
private var badgeLayer: CAShapeLayer? {
if let b: AnyObject = objc_getAssociatedObject(self, &handle) as AnyObject? {
return b as? CAShapeLayer
} else {
return nil
}
}
func setBadge(offset: CGPoint = .zero, color: UIColor = .red, filled: Bool = true, fontSize: CGFloat = 11) {
badgeLayer?.removeFromSuperlayer()
guard let view = self.value(forKey: "view") as? UIView else {
return
}
var font = UIFont.systemFont(ofSize: fontSize)
if #available(iOS 9.0, *) {
font = UIFont.monospacedDigitSystemFont(ofSize: fontSize, weight: .regular)
}
//Size of the dot
let badgeSize = UILabel(frame: CGRect(x: 22, y: -05, width: 10, height: 10))
// initialize Badge
let badge = CAShapeLayer()
let height = badgeSize.height
let width = badgeSize.width
// x position is offset from right-hand side
let x = view.frame.width - width + offset.x
// I suggest you try the x and y sets, for my case, i will use this coordinates for better result,
// but depends on the syze of your image
// let x = view.frame.width + offset.x - 17
// let y = view.frame.height + offset.y - 34
let badgeFrame = CGRect(origin: CGPoint(x: x, y: offset.y), size: CGSize(width: width, height: height))
badge.drawRoundedRect(rect: badgeFrame, andColor: color, filled: filled)
view.layer.addSublayer(badge)
// initialiaze Badge's label
let label = CATextLayer()
label.alignmentMode = .center
label.font = font
label.fontSize = font.pointSize
label.frame = badgeFrame
label.foregroundColor = filled ? UIColor.white.cgColor : color.cgColor
label.backgroundColor = UIColor.clear.cgColor
label.contentsScale = UIScreen.main.scale
badge.addSublayer(label)
// save Badge as UIBarButtonItem property
objc_setAssociatedObject(self, &handle, badge, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
// bring layer to front
badge.zPosition = 1_000
}
private func removeBadge() {
badgeLayer?.removeFromSuperlayer()
}
}
// MARK: - Utilities
extension CAShapeLayer {
func drawRoundedRect(rect: CGRect, andColor color: UIColor, filled: Bool) {
fillColor = filled ? color.cgColor : UIColor.white.cgColor
strokeColor = color.cgColor
path = UIBezierPath(roundedRect: rect, cornerRadius: 7).cgPath
}
}
I made the extension more generic so to work on any old NSObject aswel as UIViews.
A new class can confirm by implementing the var badgeParent variable.
Also added some standardisation to the API;
NOTE: remove default parameters from initial functions!
Be wary of autolayout update cycles with the view since it uses relative positioning with points
usage:
let badgeView = UIView(frame: .zero).addBrandedBadge(text: "1", size: .small, color: .regular)
private var handle: UInt8 = 0
public protocol Badge {
var badgeParentView : UIView? { get }
}
extension UIBarButtonItem : Badge {
public var badgeParentView: UIView? {
self.value(forKey: "view") as? UIView
}
}
extension UIView : Badge {
public var badgeParentView: UIView? { self }
}
public extension Badge {
private var badgeLayer: CAShapeLayer? {
if let b: AnyObject = objc_getAssociatedObject(self, &handle) as AnyObject? {
return b as? CAShapeLayer
} else {
return nil
}
}
public func setBadge(text: String?, offset: CGPoint , color: UIColor, borderColor: UIColor, borderWidth: CGFloat, textColor: UIColor, filled: Bool = true, fontSize: CGFloat) {
badgeLayer?.removeFromSuperlayer()
guard let text = text, !text.isEmpty else {
return
}
addBadge(text: text, offset: offset, color: color,borderColor: borderColor, borderWidth: borderWidth, textColor: textColor, filled: filled, fontSize: fontSize)
}
private func addBadge(text: String, offset: CGPoint , color: UIColor, borderColor: UIColor, borderWidth: CGFloat, textColor: UIColor, filled: Bool = true, fontSize: CGFloat) {
guard let view = badgeParentView else { return }
var font = UIFont.systemFont(ofSize: fontSize)
if #available(iOS 9.0, *) {
font = UIFont.monospacedDigitSystemFont(ofSize: fontSize, weight: .regular)
}
let badgeSize = text.size(withAttributes: [.font: font])
// initialize Badge
let badge = CAShapeLayer()
let height = badgeSize.height
var width = badgeSize.width + 2 // padding
// make sure we have at least a circle
if width < height {
width = height
}
// x position is offset from right-hand side
let x = view.frame.width - width + offset.x
let badgeFrame = CGRect(origin: CGPoint(x: x, y: offset.y), size: CGSize(width: width, height: height))
badge.drawRoundedRect(rect: badgeFrame, andColor: color, filled: filled)
view.layer.addSublayer(badge)
// initialiaze Badge's label
let label = CATextLayer()
label.string = text
label.alignmentMode = .center
label.font = font
label.fontSize = font.pointSize
label.foregroundColor = textColor.cgColor
label.borderWidth = borderWidth
label.borderColor = borderColor.cgColor
label.frame = badgeFrame
label.cornerRadius = label.frame.height / 2
label.foregroundColor = filled ? UIColor.white.cgColor : color.cgColor
label.backgroundColor = UIColor.clear.cgColor
label.contentsScale = UIScreen.main.scale
badge.addSublayer(label)
// save Badge as UIBarButtonItem property
objc_setAssociatedObject(self, &handle, badge, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
// bring layer to front
badge.zPosition = 1_000
}
public func removeBadge() {
badgeLayer?.removeFromSuperlayer()
}
}
public extension Badge {
@discardableResult
func addBrandedBadge(text: String, size: BadgeSizeVariant, color: BadgeColorVariant) -> Self {
self.setBadge(text: text, offset: size.offSet, color: color.backroundColor, borderColor: color.borderColor, borderWidth: size.borderWidth, textColor: color.textColor, filled: true, fontSize: size.fontSize)
return self
}
}
public struct BadgeSizeVariant {
public let borderWidth: CGFloat
public let fontSize : CGFloat
public let offSet : CGPoint
public init(borderWidth: CGFloat, fontSize: CGFloat, offSet: CGPoint) {
self.borderWidth = borderWidth
self.fontSize = fontSize
self.offSet = offSet
}
}
public extension BadgeSizeVariant {
static var small = Self(borderWidth: 1, fontSize: 12, offSet: CGPoint(x: -8, y: 4))
static var medium = Self(borderWidth: 1.5, fontSize: 16, offSet: CGPoint(x: -4, y: 2))
static var large = Self(borderWidth: 3, fontSize: 24, offSet: CGPoint(x: 8, y: -8))
}
public struct BadgeColorVariant {
public let borderColor : UIColor
public let backroundColor : UIColor
public let textColor : UIColor
public init(border: UIColor, background: UIColor, text: UIColor) {
self.borderColor = border
self.backroundColor = background
self.textColor = text
}
}
public extension BadgeColorVariant {
static var regular = Self(border: .white, background: .red, text: .white)
static var success = Self(border: .white, background: .green, text: .white)
}
// MARK: - Utilities
public extension CAShapeLayer {
public func drawRoundedRect(rect: CGRect, andColor color: UIColor, filled: Bool) {
fillColor = filled ? color.cgColor : UIColor.white.cgColor
strokeColor = color.cgColor
path = UIBezierPath(roundedRect: rect, cornerRadius: 7).cgPath
}
}
@AP-94 love u dude, but im not gay 😎
Does anyone know why this doesn't work with back button?