Last active
March 8, 2017 21:46
-
-
Save gravicle/aeadc3acc9f516fd78a4cbdf47d399b3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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