我有一个标签,应该是未读通知计数的徽章,但由于重构为使用 UIButton.Configuration,无论如何,标签总是呈现在按钮标签和图像后面。
这是所需的布局:
代码如下:
UIButton 的配置
private func getNotificationButtonConfiguration() -> UIButton.Configuration {
var container = AttributeContainer()
container.font = .sansSemiBold(size: 14)
container.foregroundColor = .warmGray
var configuration = UIButton.Configuration.plain()
configuration.contentInsets = .zero
configuration.attributedTitle = AttributedString("Notifications".localize, attributes: container)
configuration.imagePadding = 3
return configuration
}
然后在成功获取未读通知的数量后,我调用此块来显示徽章
private func showBadge(withCount count: Int) {
let badge = UILabel.badgeLabel(withCount: count)
badge.tag = 9830384
notificationButton.addSubview(badge)
let size: CGSize = (badge.text! as NSString).size(withAttributes: [NSAttributedString.Key.font: UIFont.sansBold(size: 12)])
badge.snp.makeConstraints { make in
make.trailing.equalTo(notificationButton.snp.leading).offset(25)
make.top.equalTo(notificationButton.snp.top)
make.width.equalTo(size.width + 10)
make.height.equalTo(16)
}
}
生成徽章标签的函数是
static func badgeLabel(withCount count: Int) -> UILabel {
let badgeCount = UILabel(frame: CGRect(x: 0, y: 0, width: 16, height: 16))
badgeCount.translatesAutoresizingMaskIntoConstraints = false
badgeCount.layer.cornerRadius = badgeCount.bounds.size.height / 2
badgeCount.textAlignment = .center
badgeCount.layer.masksToBounds = true
badgeCount.textColor = .white
badgeCount.font = .sansBold(size: 12)
badgeCount.backgroundColor = .heartRed
badgeCount.text = String(count)
return badgeCount
}
我尝试在
notificationButton.bringSubviewToFront(badge)
函数中调用 showBadge
,但它对布局没有任何影响。
任何想法都将不胜感激!
在 UIButton 上添加子视图会始终将该子视图发送到后面,而不是在按钮标签和图像上渲染。
按钮初始化:
let notificationButton = UIButton(type: .custom)
notificationButton.setImage(UIImage(named: "icon_bell_filled"), for: .normal)
notificationButton.configuration = getNotificationButtonConfiguration()
notificationButton.titleLabel?.tag = 200
notificationButton.addTarget(self, action: #selector(openNotificationsButtonAction), for: .touchUpInside)
正如您在评论中提到的,您可以将按钮和徽章包装在
UIView
中
或者,您可以子类化
UIButton
并添加附加/分离方法。
简单示例:
class BadgedButton: UIButton {
public weak var theBadgeView: UIView!
public func attachBadge(_ v: UIView) -> Bool {
// we must have a superview!
guard let sv = self.superview else { return false }
if let curBadge = theBadgeView {
curBadge.removeFromSuperview()
}
v.translatesAutoresizingMaskIntoConstraints = false
sv.addSubview(v)
let sz: CGSize = v.intrinsicContentSize
NSLayoutConstraint.activate([
v.trailingAnchor.constraint(equalTo: self.leadingAnchor, constant: 25.0),
v.topAnchor.constraint(equalTo: self.topAnchor, constant: 0.0),
v.widthAnchor.constraint(equalToConstant: sz.width + 10.0),
v.heightAnchor.constraint(equalToConstant: 16.0),
])
self.theBadgeView = v
return true;
}
public func detachBadge() {
if let curBadge = self.theBadgeView {
curBadge.removeFromSuperview()
}
}
override func didMoveToSuperview() {
// remove the badge view when self is removed from superview
if nil == self.superview,
let curBadge = self.theBadgeView
{
curBadge.removeFromSuperview()
}
}
}
class BadgeTestVC: UIViewController {
var btn: BadgedButton!
var badgeCount: Int = 0
let incLabel = UILabel()
let decLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
var cfg = getNotificationButtonConfiguration()
btn = BadgedButton(configuration: cfg)
btn.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(btn)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
btn.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
btn.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
])
[incLabel, decLabel].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
v.numberOfLines = 0
v.textAlignment = .center
}
incLabel.text = "Tap here to\nIncrement\nBadge Count"
decLabel.text = "Tap here to\nDecrement\nBadge Count"
incLabel.backgroundColor = .cyan
decLabel.backgroundColor = .yellow
NSLayoutConstraint.activate([
decLabel.topAnchor.constraint(equalTo: g.centerYAnchor, constant: 0.0),
decLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
decLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
incLabel.topAnchor.constraint(equalTo: g.centerYAnchor, constant: 0.0),
incLabel.leadingAnchor.constraint(equalTo: decLabel.trailingAnchor, constant: 20.0),
incLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
incLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
incLabel.widthAnchor.constraint(equalTo: decLabel.widthAnchor),
])
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let t = touches.first else { return }
let p = t.location(in: self.view)
if decLabel.frame.contains(p) {
badgeCount -= 1
}
if incLabel.frame.contains(p) {
badgeCount += 1
}
badgeCount = max(badgeCount, 0)
showBadge(withCount: badgeCount)
}
private func getNotificationButtonConfiguration() -> UIButton.Configuration {
var container = AttributeContainer()
container.font = .systemFont(ofSize: 14.0, weight: .bold) //.sansSemiBold(size: 14)
container.foregroundColor = .gray // .warmGray
var configuration = UIButton.Configuration.plain()
configuration.contentInsets = .zero
configuration.attributedTitle = AttributedString("Notifications", attributes: container)
configuration.imagePadding = 3
if let img = UIImage(systemName: "bell.fill") {
configuration.image = img
}
return configuration
}
private func showBadge(withCount count: Int) {
if count == 0 {
btn.detachBadge()
return
}
let badge = UILabel.badgeLabel(withCount: count)
badge.tag = 9830384
if !btn.attachBadge(badge) {
print("Attach failed!")
}
}
}
extension UILabel {
static func badgeLabel(withCount count: Int) -> UILabel {
let badgeCount = UILabel(frame: CGRect(x: 0, y: 0, width: 16, height: 16))
badgeCount.translatesAutoresizingMaskIntoConstraints = false
badgeCount.layer.cornerRadius = badgeCount.bounds.size.height / 2
badgeCount.textAlignment = .center
badgeCount.layer.masksToBounds = true
badgeCount.textColor = .white
badgeCount.font = .systemFont(ofSize: 12.0, weight: .bold) //.sansBold(size: 12)
badgeCount.backgroundColor = .red // .heartRed
badgeCount.text = String(count)
return badgeCount
}
}
运行时看起来像这样:
点击增量一次:
并点击 12 次:
attachBadge(...)
方法会在添加新视图(标签)之前删除“已附加”视图(标签)。
如果需要,可以轻松优化以“更新”标签文本。