I bring the issue forward which I face. I am creating a UITextField programmatically as below.
UItextField *mobileNumberField = [[UITextField alloc] initWithFrame:CGRectMake(10, 195, 300, 41)];
mobileNumberField.delegate = self;
mobileNumberField.borderStyle = UITextBorderStyleRoundedRect;
[mobileNumberField.layer setCornerRadius:14.0f];
mobileNumberField.placeholder = #"Mobile Number";
[self.paymentsHomeView addSubview:mobileNumberField];
The output is the attached image.
I dont know why is it breaking at the corners. Help me to fix my text field like the image attached below.
Just remove this line...
mobileNumberField.borderStyle = UITextBorderStyleRoundedRect;
and add this code also..
[mobileNumberField setBackgroundColor:[UIColor whiteColor]];
[mobileNumberField.layer setBorderColor:[UIColor grayColor].CGColor];
[mobileNumberField.layer setBorderWidth:1.0];
Update your like below.
UITextField *mobileNumberField = [[UITextField alloc] initWithFrame:CGRectMake(10, 195, 300, 41)];
mobileNumberField.delegate = self;
mobileNumberField.layer.borderWidth = 1.0f;
mobileNumberField.layer.borderColor = [UIColor lightGrayColor].CGColor;
mobileNumberField.
// mobileNumberField.borderStyle = UITextBorderStyleRoundedRect;
[mobileNumberField.layer setCornerRadius:14.0f];
mobileNumberField.placeholder = #"Mobile Number";
[self.paymentsHomeView addSubview:mobileNumberField];
textField.layer.cornerRadius=textfield.frame.size.height/2;
textField.clipsToBounds=YES;
The reason the corners are cut is because there is an enclosing view to the text field. When you set the corner radius, applies to THAT view and thus the corners of the inside text field seem to be cut - in reality they have not even changed.
The solution is to put the UITextField inside UIView, set textfield borderstyle to none. Then apply the border and corner radius specification to the uiview. Note the borderColor, which is very close, if not the same, to the UITextField borderColor.
As of writing, tested and works in Xcode 7.3.1, Swift 2.2, iOS 8 and 9.
Swift:
textField.borderStyle = UITextBorderStyle.None
textBorderView.layer.cornerRadius = 5
textBorderView.layer.borderWidth = 1
textBorderView.layer.borderColor = UIColor.lightGrayColor().colorWithAlphaComponent(0.2).CGColor
The following code has given me the following result in Swift 5, XCode 11.4.
Definitely more bells and whistles can be added. I think this good MVP
naviTextField = UITextField.init(frame: CGRect(x: 0, y: 0, width: (self.navigationController?.navigationBar.frame.size.width)!, height: 21))
self.navigationItem.titleView = naviTextField
naviTextField.becomeFirstResponder()
naviTextField.placeholder = "Type target name here"
naviTextField.borderStyle = .roundedRect
naviTextField.layer.cornerRadius = 5.0
naviTextField.textAlignment = .center
Here is the solution of your problem
UITextField * txtField = [[UITextField alloc]initWithFrame:CGRectMake(0, 0, 200, 50)];
[txtField setBorderStyle:UITextBorderStyleNone];
[txtField.layer setMasksToBounds:YES];
[txtField.layer setCornerRadius:10.0f];
[txtField.layer setBorderColor:[[UIColor lightGrayColor]CGColor]];
[txtField.layer setBorderWidth:1];
[txtField setTextAlignment:UITextAlignmentCenter];
[txtField setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter];
[self.view addSubview:txtField];
Swift 3 solution:
I have written separate function to set border and corner radius to any layer in swift, you have to just pass the layer of any view, border width, corner radius and border color to the following function
` func setBorderAndCornerRadius(layer: CALayer, width: CGFloat, radius: CGFloat,color : UIColor ) {
layer.borderColor = color.cgColor
layer.borderWidth = width
layer.cornerRadius = radius
layer.masksToBounds = true
}
`
swift solution:
textField.layer.borderWidth = anyWidth
textField.layer.borderColor = anyColor
textField.layer.cornerRadius = textField.frame.size.height/2
textField.clipsToBounds = true
All above answer is good , but here I am adding the code through #IBDesignable.
#IBDesignable class DesignableUITextField: UITextField {
// Provides left padding for images
override func leftViewRect(forBounds bounds: CGRect) -> CGRect {
var textRect = super.leftViewRect(forBounds: bounds)
textRect.origin.x += leftPadding
return textRect
}
// Provides right padding for images
override func rightViewRect(forBounds bounds: CGRect) -> CGRect {
var textRect = super.rightViewRect(forBounds: bounds)
textRect.origin.x -= rightPadding
return textRect
}
#IBInspectable var leftImage: UIImage? {
didSet {
updateView()
}
}
#IBInspectable var rightImage: UIImage? {
didSet {
updateRightView()
}
}
#IBInspectable var leftPadding: CGFloat = 0
#IBInspectable var rightPadding: CGFloat = 0
#IBInspectable var gapPadding: CGFloat = 0
#IBInspectable var color: UIColor = UIColor.lightGray {
didSet {
updateView()
}
}
#IBInspectable var cornerRadius: CGFloat = 0
#IBInspectable var borderColor: UIColor? = .lightGray
override func draw(_ rect: CGRect) {
layer.cornerRadius = cornerRadius
layer.masksToBounds = true
layer.borderWidth = 1
layer.borderColor = borderColor?.cgColor
}
//#IBInspectable var roundCornersRadius: CGFloat = 0 {
// didSet{
// roundCornersRadiusTextField(radius: roundCornersRadius)
// }
// }
func roundCornersRadiusTextField(radius:CGFloat) {
roundCorners(corners: [UIRectCorner.topLeft, UIRectCorner.topRight, UIRectCorner.bottomLeft, UIRectCorner.bottomRight], radius:radius)
}
func roundBottomCornersRadius(radius:CGFloat) {
roundCorners(corners: [UIRectCorner.topLeft, UIRectCorner.topRight], radius:radius)
}
func updateView() {
if let image = leftImage {
leftViewMode = UITextField.ViewMode.always
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
imageView.contentMode = .scaleAspectFit
imageView.image = image
// Note: In order for your image to use the tint color, you have to select the image in the Assets.xcassets and change the "Render As" property to "Template Image".
imageView.tintColor = color
leftView = imageView
} else {
leftViewMode = UITextField.ViewMode.never
leftView = nil
}
// Placeholder text color
attributedPlaceholder = NSAttributedString(string: placeholder != nil ? placeholder! : "", attributes:[NSAttributedString.Key.foregroundColor: color])
}
func updateRightView() {
if let image = rightImage {
rightViewMode = UITextField.ViewMode.always
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
imageView.contentMode = .scaleAspectFit
imageView.image = image
// Note: In order for your image to use the tint color, you have to select the image in the Assets.xcassets and change the "Render As" property to "Template Image".
imageView.tintColor = color
rightView = imageView
} else {
rightViewMode = UITextField.ViewMode.never
rightView = nil
}
// Placeholder text color
attributedPlaceholder = NSAttributedString(string: placeholder != nil ? placeholder! : "", attributes:[NSAttributedString.Key.foregroundColor: color])
}
func roundCorners(corners:UIRectCorner, radius:CGFloat) {
let bounds = self.bounds
let maskPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = maskPath.cgPath
self.layer.mask = maskLayer
let frameLayer = CAShapeLayer()
frameLayer.frame = bounds
frameLayer.path = maskPath.cgPath
frameLayer.strokeColor = UIColor.darkGray.cgColor
frameLayer.fillColor = UIColor.init(red: 247, green: 247, blue: 247, alpha: 0).cgColor
self.layer.addSublayer(frameLayer)
}
private var textPadding: UIEdgeInsets {
let p: CGFloat = leftPadding + gapPadding + (leftView?.frame.width ?? 0)
return UIEdgeInsets(top: 0, left: p, bottom: 0, right: 5)
}
override open func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: textPadding)
}
override open func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: textPadding)
}
override open func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.inset(by: textPadding)
}}
Related
I am dynamically adding labels to the screen. I add outline text to this label.
When I create 2 labels, when I zoom in the first label, the outline text is not deleted and regenerated. I give different tags to each label and outline text. Outline text is tag => label tags + 1
Why isn't this outline text deleted even though there are different tags?
Outline text extension
`
extension UILabel {
func addTextOutline(usingColor outlineColor: UIColor, outlineWidth: CGFloat, tagX: Int) {
let textOutline = OutlinedText()
let outlineTag = tagX + 1
print("label_tag_outline: \(outlineTag) ")
print("label_tag_outline_self: \(tag) ")
// label tag :955182
if let prevTextOutline = viewWithTag(outlineTag) {
prevTextOutline.removeFromSuperview()
print("label_tag_outline: \(outlineTag) delete **************")
}
textOutline.outlineColor = outlineColor
textOutline.outlineWidth = outlineWidth
textOutline.textColor = textColor
textOutline.font = font
textOutline.text = text
textOutline.tag = outlineTag
sizeToFit()
addSubview(textOutline)
textOutline.frame = CGRect(x: -(outlineWidth / 2), y: -(outlineWidth / 2),
width: bounds.width + outlineWidth,
height: bounds.height + outlineWidth)
// MARK: - - OutlinedText CLASSS
class OutlinedText: UILabel {
var outlineWidth: CGFloat = 0
var outlineColor: UIColor = .clear
override public func drawText(in rect: CGRect) {
let shadowOffset = self.shadowOffset
let textColor = self.textColor
let c = UIGraphicsGetCurrentContext()
c?.setLineWidth(outlineWidth)
c?.setLineJoin(.round)
c?.setTextDrawingMode(.stroke)
textAlignment = .center
self.textColor = outlineColor
super.drawText(in: rect)
c?.setTextDrawingMode(.fill)
self.textColor = textColor
self.shadowOffset = CGSize(width: 0, height: 0)
super.drawText(in: rect)
self.shadowOffset = shadowOffset
}
}
}
}
`
PINCH
`
#objc func pinchRecoginze(_ pinchGesture: UIPinchGestureRecognizer) {
guard pinchGesture.view != nil else { return }
let view = pinchGesture.view!
if pinchGesture.view is UILabel {
let textLabel = view as! UILabel
if pinchGesture.state == .began {
let font = textLabel.font
pointSize = font!.pointSize
pinchGesture.scale = textLabel.font!.pointSize * 0.1
}
if 1 <= pinchGesture.scale && pinchGesture.scale <= 10 {
textLabel.font = UIFont(name: textLabel.font!.fontName, size: pinchGesture.scale * 10)
print("label_tag_PINCH: \(textLabel.tag) ")
resizeLabelToText(textLabel: textLabel, tag: textLabel.tag)
}
}
}
`
func resizeLabelToText(textLabel: UILabel, tag: Int) {
let labelSize = textLabel.intrinsicContentSize
textLabel.bounds.size = labelSize
label.addTextOutline(usingColor: .red, outlineWidth: 10, tagX: tag)
}
label.addTextOutline(usingColor: .red, outlineWidth: 10, tagX: label.tag)
enter image description here
What I need:
What I have:
Here is my current code:
class SchedulerSummaryCell: UITableViewCell {
#IBOutlet weak var oneMileV: UIView! {
didSet {
oneMileV.backgroundColor = .clear
let blurEffect = UIBlurEffect(style: .light)
let blurView = UIVisualEffectView(effect: blurEffect)
blurView.translatesAutoresizingMaskIntoConstraints = false
oneMileV.insertSubview(blurView, at: 0)
makeCircular(oneMileV)
NSLayoutConstraint.activate([
blurView.heightAnchor.constraint(equalTo: oneMileV.heightAnchor),
blurView.widthAnchor.constraint(equalTo: oneMileV.widthAnchor),
blurView.leadingAnchor.constraint(equalTo: oneMileV.leadingAnchor),
blurView.topAnchor.constraint(equalTo: oneMileV.topAnchor)
])
oneMileV.layer.applySketchShadow(alpha: 0.5, y: 4, blur: 10)
}
}
}
(HELPER)
extension CALayer {
func applySketchShadow(
color: UIColor = .black,
alpha: Float = 0.16,
x: CGFloat = 0,
y: CGFloat = 3,
blur: CGFloat = 6,
spread: CGFloat = 0)
{
masksToBounds = false
shadowColor = color.cgColor
shadowOpacity = alpha
shadowOffset = CGSize(width: x, height: y)
shadowRadius = blur / 2.0
if spread == -1 {return}
if spread == 0 {
shadowPath = nil
} else {
let dx = -spread
let rect = bounds.insetBy(dx: dx, dy: dx)
shadowPath = UIBezierPath(rect: rect).cgPath
}
}
}
When I comment out the applySketchShadow code, I get this:
Questions
Why does the gaussian blur cancel out the rounded corners?
Is there a way for me to apply both rounded corners and gaussian blur?
How should I then add an image?
You have to add your UIImageView as a subview to a UIView. The UIView will be responsible for the shadow and the UIImageView is going to take care of the rounded corners. Unfortunately you cannot add a shadow directly to the UIImageView because you need to enable clipsToBounds = true which prevents the view to show content outside of its bounds. This however is necessary for the shadow to be shown.
This will do the work:
let shadowView = UIView()
shadowView.backgroundColor = .clear
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOffset = CGSize(width: 0.1, height: 1)
shadowView.layer.shadowRadius = 8
shadowView.layer.shadowOpacity = 0.14
shadowView.layer.masksToBounds = false
let imageView = UIImageView()
imageView.image = UIImage(named: "YourImage")
imageView.clipsToBounds = true
imageView.layer.cornerRadius = imageView.frame.width / 2
shadowView.addSubview(imageView)
For shadow effect use this extention it will help you.
extension UIImageView {
func addShadow() {
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 2, height: 5)
self.layer.shadowOpacity = 0.5
self.layer.shadowRadius = 1.0
self.clipsToBounds = false
}
}
I can't seem to increase the thickness of the track. Been trying other recommendations and looking for this option in the documentation but it doesn't seem to be working, anyone know why?:(
class factionButton: UISlider {
var factionSlider = UISlider()
func factionBalanceSlider(){
factionSlider.frame = CGRect(x: 15, y: 542, width: 386, height: 57)
factionSlider.minimumValueImage = #imageLiteral(resourceName: "Alliance Slider")
factionSlider.maximumValueImage = #imageLiteral(resourceName: "Horde Slider")
factionSlider.setThumbImage(#imageLiteral(resourceName: "Thumb Image"), for: .normal)
factionSlider.minimumTrackTintColor = UIColor(red:0.08, green:0.33, blue:0.69, alpha:0.8)
factionSlider.maximumTrackTintColor = UIColor(red:1.00, green:0.00, blue:0.00, alpha:0.59)
factionSlider.setValue(0.5, animated: true)
factionSlider.isContinuous = true
factionSlider.addTarget(self, action: #selector(recordFactionBalance(sender:)) , for: .valueChanged)
}
func getSlider() -> UISlider {
return factionSlider
}
override func trackRect(forBounds bounds: CGRect) -> CGRect {
let customBounds = CGRect(x: 16, y: 21, width: 343, height: 7)
super.trackRect(forBounds: customBounds)
return customBounds
}
As mentioned in many other answers, you can change the height by creating a custom slider as below,
class CustomSlider: UISlider {
override func trackRect(forBounds bounds: CGRect) -> CGRect {
var rect = super.trackRect(forBounds: bounds)
rect.size.height = 7
return rect
}
}
But in your particular case, you are not seeing the change because your implementation is not allowing the factionSlider to use overridden trackRect. To use that you need to change that to CustomSlider as below,
class FactionButton: UISlider {
var factionSlider = CustomSlider()
func factionBalanceSlider(){
factionSlider.frame = CGRect(x: 15, y: 542, width: 386, height: 57)
factionSlider.minimumValueImage = #imageLiteral(resourceName: "Alliance Slider")
factionSlider.maximumValueImage = #imageLiteral(resourceName: "Horde Slider")
factionSlider.setThumbImage(#imageLiteral(resourceName: "Thumb Image"), for: .normal)
factionSlider.minimumTrackTintColor = UIColor(red:0.08, green:0.33, blue:0.69, alpha:0.8)
factionSlider.maximumTrackTintColor = UIColor(red:1.00, green:0.00, blue:0.00, alpha:0.59)
factionSlider.setValue(0.5, animated: true)
factionSlider.isContinuous = true
factionSlider.addTarget(self, action: #selector(recordFactionBalance(sender:)) , for: .valueChanged)
}
func getSlider() -> UISlider {
return factionSlider
}
}
Note In Swift, class name should start with Capital as i updated above. Secondly, I think FactionButton should not be a subclass of UISlider.
You should get the current bounds from the super class first, then just change the height:
override func trackRect(forBounds bounds: CGRect) -> CGRect {
var customBounds = super.trackRect(forBounds: bounds)
customBounds.size.height = 7
return customBounds
}
Setting the rect size expands the slider to the bottom only. So the origin should be recalculated to keep the slider centered.
#IBDesignable
class CustomSlider: UISlider {
#IBInspectable var trackHeight: CGFloat = 6
override func trackRect(forBounds bounds: CGRect) -> CGRect {
var rect = super.trackRect(forBounds: bounds)
rect.size.height = trackHeight
rect.origin.y -= trackHeight / 2
return rect
}
}
I made this by adding this
1.
class CustomSlider: UISlider {
override func trackRect(forBounds bounds: CGRect) -> CGRect {
let point = CGPoint(x: bounds.minX, y: bounds.midY)
return CGRect(origin: point, size: CGSize(width: bounds.width, height: 10)) //this height is the thickness
}
}
storyboard - change UISlider class to my CustomSlider
FYI for newbie like me..
change color is here :)
I am changing the color of a UISlider by calling .thumbTintColor
#IBAction func slider1Master(sender: AnyObject) {
slider1.thumbTintColor = UIColor.orangeColor()}
It works, but I want the color to change back to it's original state when the touch ends (user lifts finger).
Does anyone have any suggestions? Thank you.
You can use "setThumbImage" instead.
Then you have the option of setting an image for a specific state of action.
For the image, just create a rounder image with the color you desire.
//Creating an Image with rounded corners:
extension UIImage {
class func createThumbImage(size: CGFloat, color: UIColor) -> UIImage {
let layerFrame = CGRectMake(0, 0, size, size)
let shapeLayer = CAShapeLayer()
shapeLayer.path = CGPathCreateWithEllipseInRect(layerFrame.insetBy(dx: 1, dy: 1), nil)
shapeLayer.fillColor = color.CGColor
shapeLayer.strokeColor = color.colorWithAlphaComponent(0.65).CGColor
let layer = CALayer.init()
layer.frame = layerFrame
layer.addSublayer(shapeLayer)
return self.imageFromLayer(layer)
}
class func imageFromLayer(layer: CALayer) -> UIImage {
UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, UIScreen.mainScreen().scale)
layer.renderInContext(UIGraphicsGetCurrentContext()!)
let outputImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return outputImage
}
}
//Setting the image for a selected state of UISlider:
func setupSlider() {
let size:CGFloat = 12
let highlightedStateOrangeColorImage = UIImage.createThumbImage(size, color: UIColor.orangeColor())
let defaultStateBlueColorImage = UIImage.createThumbImage(size, color: UIColor.blueColor())
self.slider.setThumbImage(highlightedStateOrangeColorImage, forState: UIControlState.Highlighted)
self.slider.setThumbImage(defaultStateBlueColorImage, forState: UIControlState.Normal)
}
You can safely accept McMatan’s solution as your answer. It is good for several reasons.
the colour changes back to its original state when the user lifts a finger, as you requested
using the extension to create a shape does away with image assets for the UISlider
it could also be used to draw images for circular UIButtons and circular UIViews.
it can also create a shape with colours matching other UISlider design elements (if so desired).
The code below does just that. I used McMatan’s UIImage extension with no changes other than translation to Swift 3. But I have split his function setUpSlider() into two, one for drawing the circular image in its default state, the other for drawing it in its highlighted state.
By accepting McMatan’s solution, you will encourage those who contribute their experience and free time to continue making this forum worthwhile for the rest of us. So please do.
class ViewController: UIViewController {
var slider: UISlider!
let defaultColour = UIColor.blue
let highlightedColour = UIColor.orange
let thumbSize = 20
override func viewDidLoad() {
super.viewDidLoad()
slider = UISlider(frame: CGRect(x: 0, y: 0, width: 200, height: 23))
slider.minimumValue = 0
slider.minimumTrackTintColor = defaultColour
slider.maximumValue = 100
slider.maximumTrackTintColor = highlightedColour
slider.center = view.center
slider.value = slider.maximumValue / 2.0
let highlightedImage = makeHighlightedImage()
let defaultImage = makeDefaultImage()
slider.setThumbImage(highlightedImage, for: UIControlState.highlighted)
slider.setThumbImage(defaultImage, for: UIControlState.normal)
slider.isContinuous = false
view.addSubview(slider)
slider.addTarget(self, action: #selector(sliderValueChanged), for: UIControlEvents.valueChanged)
}
func sliderValueChanged(sender: UISlider){
print(sender.value)
}
func makeHighlightedImage() -> (UIImage) {
let size = CGFloat(thumbSize)
let highlightedStateImage = UIImage.createThumbImage(size: size, color: highlightedColour)
return (highlightedStateImage)
}
func makeDefaultImage() -> (UIImage) {
let size = CGFloat(thumbSize)
let defaultStateImage = UIImage.createThumbImage(size: size, color: defaultColour)
return (defaultStateImage)
}
}
Extension translated to Swift 3
import UIKit
extension UIImage {
class func createThumbImage(size: CGFloat, color: UIColor) -> UIImage {
let layerFrame = CGRect(x: 0, y: 0, width: size, height: size)
let shapeLayer = CAShapeLayer()
shapeLayer.path = CGPath(ellipseIn: layerFrame.insetBy(dx: 1, dy: 1), transform: nil)
shapeLayer.fillColor = color.cgColor
shapeLayer.strokeColor = color.withAlphaComponent(0.65).cgColor
let layer = CALayer.init()
layer.frame = layerFrame
layer.addSublayer(shapeLayer)
return self.imageFromLayer(layer: layer)
}
class func imageFromLayer(layer: CALayer) -> UIImage {
UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, UIScreen.main.scale)
layer.render(in: UIGraphicsGetCurrentContext()!)
let outputImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return outputImage!
}
}
I came up with an answer similar to MCMatan's but without the need for a UIImage extension:
func setThumbnailImage(for slider: UISlider, thumbnailHeight: CGFloat, thumbnailColor: UIColor) {
let cornerRadius: CGFloat = 25
let rect = CGRect(x: 0, y: 0, width: thumbnailHeight, height: thumbnailHeight)
let size = CGSize(width: thumbnailHeight, height: thumbnailHeight)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
// this is what makes it round
UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip()
thumbnailColor.setFill()
UIRectFill(rect)
if let newImage = UIGraphicsGetImageFromCurrentImageContext() {
slider.setThumbImage(nil, for: .normal)
slider.setThumbImage(newImage, for: .normal)
}
UIGraphicsEndImageContext()
}
To use:
override func viewDidLoad() {
super.viewDidLoad()
setThumbnailImage(for: yourSlider, thumbnailHeight: 20.0, thumbnailColor: UIColor.red)
}
or
func someActionJustFinishedNowUpdateThumbnail() {
setThumbnailImage(for: yourSlider, thumbnailHeight: 40.0, thumbnailColor: UIColor.blue)
}
or
func setThumbnailToSliderHeight() {
let sliderHeight = yourSlider.frame.size.height
setThumbnailImage(for: yourSlider, thumbnailHeight: sliderHeight, thumbnailColor: UIColor.purple)
}
I am trying to display badge on my notification button, in app as displayed on AppIcon.
So far whatever i have researched is related to Obj. C, but nothing that specifically discussed way to implement that solution into Swift,
Please help to find a solution to add a custom class / code to achieve Badge on UiBarbutton and UiButton.
Researched so far:
https://github.com/Marxon13/M13BadgeView
along with MKBadge class etc.
There is a more elegant solution with an extension for UIButtonItem
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()
// 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 - 4, y: offset.y), size: CGSize(width: 8, 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 great code was created by Stefano Vettor and you can find all the details at:
https://gist.github.com/freedom27/c709923b163e26405f62b799437243f4
Working Solution :
Step 1:
Firstly create new swift file which is a subclass to UIButton as follows:
import UIKit
class BadgeButton: UIButton {
var badgeLabel = UILabel()
var badge: String? {
didSet {
addbadgetobutton(badge: badge)
}
}
public var badgeBackgroundColor = UIColor.red {
didSet {
badgeLabel.backgroundColor = badgeBackgroundColor
}
}
public var badgeTextColor = UIColor.white {
didSet {
badgeLabel.textColor = badgeTextColor
}
}
public var badgeFont = UIFont.systemFont(ofSize: 12.0) {
didSet {
badgeLabel.font = badgeFont
}
}
public var badgeEdgeInsets: UIEdgeInsets? {
didSet {
addbadgetobutton(badge: badge)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
addbadgetobutton(badge: nil)
}
func addbadgetobutton(badge: String?) {
badgeLabel.text = badge
badgeLabel.textColor = badgeTextColor
badgeLabel.backgroundColor = badgeBackgroundColor
badgeLabel.font = badgeFont
badgeLabel.sizeToFit()
badgeLabel.textAlignment = .center
let badgeSize = badgeLabel.frame.size
let height = max(18, Double(badgeSize.height) + 5.0)
let width = max(height, Double(badgeSize.width) + 10.0)
var vertical: Double?, horizontal: Double?
if let badgeInset = self.badgeEdgeInsets {
vertical = Double(badgeInset.top) - Double(badgeInset.bottom)
horizontal = Double(badgeInset.left) - Double(badgeInset.right)
let x = (Double(bounds.size.width) - 10 + horizontal!)
let y = -(Double(badgeSize.height) / 2) - 10 + vertical!
badgeLabel.frame = CGRect(x: x, y: y, width: width, height: height)
} else {
let x = self.frame.width - CGFloat((width / 2.0))
let y = CGFloat(-(height / 2.0))
badgeLabel.frame = CGRect(x: x, y: y, width: CGFloat(width), height: CGFloat(height))
}
badgeLabel.layer.cornerRadius = badgeLabel.frame.height/2
badgeLabel.layer.masksToBounds = true
addSubview(badgeLabel)
badgeLabel.isHidden = badge != nil ? false : true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addbadgetobutton(badge: nil)
fatalError("init(coder:) is not implemented")
}
}
Step 2:
Create a function in your base file which u can use in each View Controller :
func addBadge(itemvalue: String) {
let bagButton = BadgeButton()
bagButton.frame = CGRect(x: 0, y: 0, width: 44, height: 44)
bagButton.tintColor = UIColor.darkGray
bagButton.setImage(UIImage(named: "ShoppingBag")?.withRenderingMode(.alwaysTemplate), for: .normal)
bagButton.badgeEdgeInsets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 15)
bagButton.badge = itemvalue
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: bagButton)
}
Step 3 :
Use above function from any View Controller in this way :
self.addBadge(itemvalue: localStorage.string(forKey: "total_products_in_cart") ?? "0")
First create label, then right bar button. On right bar button add subview which will be badge count. Finally add navigation right bar button.
SWIFT 5
let badgeCount = UILabel(frame: CGRect(x: 22, y: -05, width: 20, height: 20))
badgeCount.layer.borderColor = UIColor.clear.cgColor
badgeCount.layer.borderWidth = 2
badgeCount.layer.cornerRadius = badgeCount.bounds.size.height / 2
badgeCount.textAlignment = .center
badgeCount.layer.masksToBounds = true
badgeCount.textColor = .white
badgeCount.font = badgeCount.font.withSize(12)
badgeCount.backgroundColor = .red
badgeCount.text = "4"
let rightBarButton = UIButton(frame: CGRect(x: 0, y: 0, width: 35, height: 35))
rightBarButton.setBackgroundImage(UIImage(named: "NotificationBell"), for: .normal)
rightBarButton.addTarget(self, action: #selector(self.onBtnNotification), for: .touchUpInside)
rightBarButton.addSubview(badgeCount)
let rightBarButtomItem = UIBarButtonItem(customView: rightBarButton)
navigationItem.rightBarButtonItem = rightBarButtomItem
I had the same task. I didn't want to use third-party libraries. Firstly, I tried Stefano's solution and it's great however I decided to implement my own way to solve it.
In my humble opinion, there are simple steps described below briefly:
Create UIView instance within .xib file and put necessary items like UILabel or UIImageView instance depending on your design requirements.
The final action I did in this step is putting invisible button in the top of view's hierarchy.
Create YourCustomView.swift and link all #IBOutlets from xib to current file inside your custom view class implementation.
Next, implement class function in YourCustomView class which will load custom view from xib and return it as YourCustomView instance.
Finally, add your custom badge to your custom view controller instance!
My result is..
P.S. If you need to implement #IBActions I recommend to link your custom view and custom view controller through the delegate pattern.
using M13BadgeView.. use this code
(im using fontawesome.swift for buttons :: https://github.com/thii/FontAwesome.swift)
let rightButton = UIButton(frame: CGRect(x:0,y:0,width:30,height:30))
rightButton.titleLabel?.font = UIFont.fontAwesome(ofSize: 22)
rightButton.setTitle(String.fontAwesomeIcon(name: .shoppingBasket), for: .normal)
let rightButtonItem : UIBarButtonItem = UIBarButtonItem(customView: rightButton)
let badgeView = M13BadgeView()
badgeView.text = "1"
badgeView.textColor = UIColor.white
badgeView.badgeBackgroundColor = UIColor.red
badgeView.borderWidth = 1.0
badgeView.borderColor = UIColor.white
badgeView.horizontalAlignment = M13BadgeViewHorizontalAlignmentLeft
badgeView.verticalAlignment = M13BadgeViewVerticalAlignmentTop
badgeView.hidesWhenZero = true
rightButton.addSubview(badgeView)
self.navigationItem.rightBarButtonItem = rightButtonItem
Good answer #Julio Bailon (https://stackoverflow.com/a/45948819/1898973)!
Here is the author's site with full explanation: http://www.stefanovettor.com/2016/04/30/adding-badge-uibarbuttonitem/.
It seems not to be working on iOS 11, maybe because the script try to access the "view" property of the UIBarButtonItem. I made it work:
By creating a UIButton and then creating the UIBarButtonItem using the UIButton as a customView:
navigationItem.rightBarButtonItem = UIBarButtonItem.init(
customView: shoppingCartButton)
By replacing the line in the UIBarButtonItem extension:
guard let view = self.value(forKey: "view") as? UIView else { return }
with the following:
guard let view = self.customView else { return }
Seems elegant to me and, best of all, it worked!
You can set below constraints to UILabel with respect to UIButton
align UILabel's top and trailing to UIButton
And when you need to show badge set text to UILabel and when you don't want to show badge then set empty string to UILabel
Download This
For BarButtonItem : Drag and Drop UIBarButtonItem+Badge.h and UIBarButtonItem+Badge.m class in project.
Write this code for set Badges:
self.navigationItem.rightBarButtonItem.badgeValue = "2"
self.navigationItem.rightBarButtonItem.badgeBGColor = UIColor.black
For UIButtton : Drag and Drop UIButton+Badge.h and UIButton+Badge.m class in project.
self.notificationBtn.badgeValue = "2"
self.notificationBtn.badgeBGColor = UIColor.black
Answer with extension from Julio will not work.
Starting from iOS 11 this code will not work cause line of code below will not cast UIView. Also it's counting as private API and seems to be will not pass AppStore review.
guard let view = self.value(forKey: "view") as? UIView else { return }
Thread on Apple Developer Forum
Second thing that this snippet always draws circle, so it can't fit numbers bigger than 9.
Here the simplified version by using custom view
Easy and clear solution if you are looking for only adding the red dot without the number;
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 + offset.x - 17
let y = view.frame.height + offset.y - 34
let badgeFrame = CGRect(origin: CGPoint(x: x, y: 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
}
}
The source of the code:
https://gist.github.com/freedom27/c709923b163e26405f62b799437243f4
I only made a few changes to eliminate the number.
The MIBadgeButton-Swift is working also on UIBarButtonItems.
Here is my code after the navigation bar is created:
let rightBarButtons = self.navigationItem.rightBarButtonItems
let alarmsBarButton = rightBarButtons?.last
let alarmsButton = alarmsBarButton.customView as! MIBadgeButton?
alarmsButton.badgeString = "10"
You can do it programmatically with
self.tabBarItem.badgeColor = .red
or use the storyboard. See: