Skip to content

Instantly share code, notes, and snippets.

@gravicle
Last active March 8, 2017 21:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gravicle/aeadc3acc9f516fd78a4cbdf47d399b3 to your computer and use it in GitHub Desktop.
Save gravicle/aeadc3acc9f516fd78a4cbdf47d399b3 to your computer and use it in GitHub Desktop.
import UIKit
@IBDesignable
final class StatusDot: UIView {
convenience init() {
self.init(frame: .zero)
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
}
// MARK: - View Lifecylce
extension StatusDot {
override func layoutSubviews() {
super.layoutSubviews()
cornerRadius = bounds.width / 2.0
}
}
// MARK: - Setup
fileprivate extension StatusDot {
func setup() {
backgroundColor = .radicalRed
forceLayoutUpdate()
}
}
import UIKit
extension UITabBarController {
func showBadge(_ show: Bool = true, atIndex index: Int) {
switch (isShowingBadge(atIndex: index), show) {
case (true, true): return
case (true, false): removeBadge(fromIndex: index)
case (false, true): addBadge(atIndex: index)
case (false, false): return
}
}
}
// MARK: - ViewController Lifecycle
extension UITabBarController {
open override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tabBar.forceLayoutUpdate()
layoutBadges()
}
}
// MARK: - Layout
fileprivate extension UITabBarController {
var barButtonFrames: [Int : CGRect] {
let barButtonClassName = "UITabBarButton"
let imageClassName = "UITabBarSwappableImageView"
return tabBar.subviews
.filter { String(describing: type(of: $0)) == barButtonClassName }
.sorted { (prev, next) in
return prev.frame.origin.x < next.frame.origin.x
}
.map { (button) -> CGRect in
let imageViewFrame = button.subviews
.filter { String(describing: type(of: $0)) == imageClassName }
.first!.frame
return button.convert(imageViewFrame, to: tabBar)
}
.enumerated()
.reduce([:]) { (dict, enumerate) -> [Int : CGRect] in
var dict = dict
dict[enumerate.offset] = enumerate.element
return dict
}
}
var badges: [Int : StatusDot] {
return tabBar.subviews
.flatMap { $0 as? StatusDot }
.reduce([:]) { (dict, dot) -> [Int : StatusDot] in
var dict = dict
dict[dot.tag] = dot
return dict
}
}
func isShowingBadge(atIndex index: Int) -> Bool {
return badges[index].exists
}
func addBadge(atIndex index: Int) {
let badge = StatusDot(frame: CGRect(x: 0, y: 0, width: 16, height: 16))
badge.tag = index
tabBar.addSubview(badge)
layout(badge, atIndex: index)
animateVisibilityOfBadge(badge, toVisible: true)
}
func removeBadge(fromIndex index: Int) {
guard let badge = badges[index] else { return }
animateVisibilityOfBadge(badge, toVisible: false)
}
func layoutBadges() {
badges.forEach { layout($0.1, atIndex: $0.0) }
}
func layout(_ badge: StatusDot, atIndex index: Int) {
guard let frame = barButtonFrames[index] else {
removeBadge(fromIndex: index)
return
}
let correction = CGPoint(x: -2, y: 4)
let center = CGPoint(
x: frame.maxX + correction.x,
y: frame.origin.y + correction.y
)
badge.center = center
}
func animateVisibilityOfBadge(_ badge: UIView, toVisible isVisible: Bool) {
let zeroScale = CATransform3DMakeScale(0, 0, 1)
let from = isVisible ? zeroScale : CATransform3DIdentity
let to = isVisible ? CATransform3DIdentity : zeroScale
badge.layer.transform = from
badge.layer.animate(
.transform(from: from, to: to),
with: SpringTraits(damping: 14, stiffness: 150),
completion: {
if !isVisible { badge.removeFromSuperview() }
}
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment