AtulParmar
AtulParmar

Reputation: 4570

Custom BarButtonItem Item: Add red dot on top right corner, Swift 4 IOS

I want to add/set unread flag as red dot top right corner on UIbarButtonItem, See attached image for this

enter image description here

What should i do to add/set red dot on Bar Button item? Once user tap on item then i want to remove red dot.

Upvotes: 3

Views: 3349

Answers (3)

novice
novice

Reputation: 451

    let dotView = UIView()

    let btnSize =yourBarbutton.frame.size
    let dotSize = 8
     dotView.backgroundColor = .red //Just change colors
    dotView.layer.cornerRadius = CGFloat(dotSize/2)
    dotView.layer.frame = CGRect(x: Int(btnSize.width)-dotSize/2 , y: 
    dotSize, width: dotSize, height: dotSize)
    yourBarbutton.addSubview(dotView)

Upvotes: 1

Robert Dresler
Robert Dresler

Reputation: 11210

UIButton Subclass

Okay, let's start with creating custom UIButton subclass

class ButtonWithBadge: UIButton {
}

now let's create UIView for repsenting red dot

let badgeView: UIView = {
    let view = UIView()
    view.layer.cornerRadius = 3
    view.backgroundColor = .red
    return view
}()

Then override init for this subclass and inside add this badgeView to top right corner of your button: set its constraints (right and top equal to button's anchors and width and height to double of badgeView's cornerRadius value)

override init(frame: CGRect) {
    super.init(frame: frame)

    addSubview(badgeView)
    badgeView.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        badgeView.rightAnchor.constraint(equalTo: rightAnchor, constant: 3),
        badgeView.topAnchor.constraint(equalTo: topAnchor, constant: 3),
        badgeView.heightAnchor.constraint(equalToConstant: badgeView.layer.cornerRadius*2),
        badgeView.widthAnchor.constraint(equalToConstant: badgeView.layer.cornerRadius*2)
    ])
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

Next create variable represeting current state of button:

var isRead: Bool = false

Now let's create some method which hide or unhide badgeView depending on isRead value

func setBadge() {
    badgeView.isHidden = isRead
}

Now we have function, right? So let's call this function at the end of init and in didSet of isRead variable

class ButtonWithProperty: UIButton {

var isRead: Bool = false {
    didSet {
        setBadge()
    }
}

override init(frame: CGRect) {
    ...
    setBadge()
}

Adding To ViewController

First create variables for button and view

lazy var barButton: ButtonWithProperty = {
    let button = ButtonWithProperty()
    ... // set color, title, target, etc.
    return button
}()

now for example in viewDidLoad add this barButton to UINavigationBar and position it how you want to:

override func viewDidLoad() {
    super.viewDidLoad()
    ...
    guard let navigationBar = self.navigationController?.navigationBar else { return }

    navigationBar.addSubview(barButton)
    barButton.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        barButton.rightAnchor.constraint(equalTo: navigationBar.rightAnchor, constant: -20),
        barButton.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: -6)
    ])

}

Now when you need to, you can just easily change barButton's isRead variable and red dot disappears or appears

barButton.isRead = true

enter image description here


class ButtonWithProperty: UIButton {

    var isRead: Bool = false {
        didSet {
            setBadge()
        }
    }

    lazy var badgeView: UIView = {
        let view = UIView()
        view.layer.cornerRadius = 3
        view.backgroundColor = .red
        return view
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)

        addSubview(badgeView)
        badgeView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            badgeView.rightAnchor.constraint(equalTo: rightAnchor, constant: 3),
            badgeView.topAnchor.constraint(equalTo: topAnchor, constant: 3),
            badgeView.heightAnchor.constraint(equalToConstant: badgeView.layer.cornerRadius*2),
            badgeView.widthAnchor.constraint(equalToConstant: badgeView.layer.cornerRadius*2)
        ])

        setBadge()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func setBadge() {
        badgeView.isHidden = isRead
    }

}

Inside ViewController:

class ViewController: UIViewController {

    lazy var barButton: ButtonWithProperty = {
        let button = ButtonWithProperty()
        ... // color, title, target, etc.
        return button
    }()

    ...

    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        guard let navigationBar = self.navigationController?.navigationBar else { return }

        navigationBar.addSubview(barButton)
        barButton.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            barButton.rightAnchor.constraint(equalTo: navigationBar.rightAnchor, constant: -20),
            barButton.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: -6)
        ])

    }
    ...
}

Upvotes: 5

Midhun MP
Midhun MP

Reputation: 107231

I've written a custom bar button class to handle this, where I'm using CAShapeLayer to draw a dot on top of the UIBarButtonItem.

// Custom Bar button
class CustomBarButton: UIBarButtonItem
{
    // Unread Mark
    private var unreadMark: CAShapeLayer?

    // Keep track of unread status
    var hasUnread: Bool = false
    {
        didSet
        {
            setUnread(hasUnread: hasUnread)
        }
    }

    // Toggles unread status
    private func setUnread(hasUnread: Bool)
    {
        if hasUnread
        {
            unreadMark            = CAShapeLayer();
            unreadMark?.path      = UIBezierPath(ovalIn: CGRect(x: (self.customView?.frame.width ?? 0) - 10, y: 5, width: 5, height: 5)).cgPath;
            unreadMark?.fillColor = UIColor.red.cgColor
            self.customView?.layer.addSublayer(unreadMark!)
        }
        else
        {
            unreadMark?.removeFromSuperlayer()
        }

    }
}

There is no layer property available for bar button item, so you need to create your UIBarButtonItem using a custom view:

// Bar button property
var barButton:CustomBarButton!

// Initialisation
button       = UIButton(type: .custom)
button.frame = CGRect(x: 0, y: 0, width: 70, height: 40)
button.setTitle("Right", for: .normal)
button.setTitleColor(UIColor.green, for: .normal)

// Bar button
barButton = CustomBarButton(customView: button)
button.addTarget(self, action: #selector(toggleStatus(sender:)), for: UIControl.Event.touchUpInside)

// Flexible space (Optional)
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
toolBar.items     = [flexibleSpace, barButton]

And in the IBAction you can toggle the status using the hasUnread property:

@objc func toggleStatus(sender: AnyObject)
{
    barButton.hasUnread = !barButton.hasUnread
}

And it will look like:

Unread Button

Upvotes: 2

Related Questions