UIView+PPBadgeView.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. //
  2. // UIView+PPBadgeView.swift
  3. // PPBadgeViewSwift
  4. //
  5. // Created by AndyPang on 2017/6/19.
  6. // Copyright © 2017年 AndyPang. All rights reserved.
  7. //
  8. /*
  9. *********************************************************************************
  10. *
  11. * Weibo : jkpang-庞 ( http://weibo.com/jkpang )
  12. * Email : jkpang@outlook.com
  13. * QQ 群 : 323408051
  14. * GitHub: https://github.com/jkpang
  15. *
  16. *********************************************************************************
  17. */
  18. import UIKit
  19. private var kBadgeView = "kBadgeView"
  20. // MARK: - add Badge
  21. public extension PP where Base: UIView {
  22. var badgeView: PPBadgeControl {
  23. return base.badgeView
  24. }
  25. /// 添加带文本内容的Badge, 默认右上角, 红色, 18pts
  26. ///
  27. /// Add Badge with text content, the default upper right corner, red backgroundColor, 18pts
  28. ///
  29. /// - Parameter text: 文本字符串
  30. func addBadge(text: String?) {
  31. showBadge()
  32. base.badgeView.text = text
  33. setBadge(flexMode: base.badgeView.flexMode)
  34. if text == nil {
  35. if base.badgeView.widthConstraint()?.relation == .equal { return }
  36. base.badgeView.widthConstraint()?.isActive = false
  37. let constraint = NSLayoutConstraint(item: base.badgeView, attribute: .width, relatedBy: .equal, toItem: base.badgeView, attribute: .height, multiplier: 1.0, constant: 0)
  38. base.badgeView.addConstraint(constraint)
  39. } else {
  40. if base.badgeView.widthConstraint()?.relation == .greaterThanOrEqual { return }
  41. base.badgeView.widthConstraint()?.isActive = false
  42. let constraint = NSLayoutConstraint(item: base.badgeView, attribute: .width, relatedBy: .greaterThanOrEqual, toItem: base.badgeView, attribute: .height, multiplier: 1.0, constant: 0)
  43. base.badgeView.addConstraint(constraint)
  44. }
  45. }
  46. /// 添加带数字的Badge, 默认右上角,红色,18pts
  47. ///
  48. /// Add the Badge with numbers, the default upper right corner, red backgroundColor, 18pts
  49. ///
  50. /// - Parameter number: 整形数字
  51. func addBadge(number: Int) {
  52. if number <= 0 {
  53. addBadge(text: "0")
  54. hiddenBadge()
  55. return
  56. }
  57. addBadge(text: "\(number)")
  58. }
  59. /// 添加带颜色的小圆点, 默认右上角, 红色, 8pts
  60. ///
  61. /// Add small dots with color, the default upper right corner, red backgroundColor, 8pts
  62. ///
  63. /// - Parameter color: 颜色
  64. func addDot(color: UIColor? = .red) {
  65. addBadge(text: nil)
  66. setBadge(height: 8.0)
  67. base.badgeView.backgroundColor = color
  68. }
  69. /// 设置Badge的偏移量, Badge中心点默认为其父视图的右上角
  70. ///
  71. /// Set Badge offset, Badge center point defaults to the top right corner of its parent view
  72. ///
  73. /// - Parameters:
  74. /// - x: X轴偏移量 (x<0: 左移, x>0: 右移) axis offset (x <0: left, x> 0: right)
  75. /// - y: Y轴偏移量 (y<0: 上移, y>0: 下移) axis offset (Y <0: up, y> 0: down)
  76. func moveBadge(x: CGFloat, y: CGFloat) {
  77. base.badgeView.offset = CGPoint(x: x, y: y)
  78. base.centerYConstraint(with: base.badgeView)?.constant = y
  79. let badgeHeight = base.badgeView.heightConstraint()?.constant ?? 0
  80. switch base.badgeView.flexMode {
  81. case .head:
  82. base.centerXConstraint(with: base.badgeView)?.isActive = false
  83. base.leadingConstraint(with: base.badgeView)?.isActive = false
  84. if let constraint = base.trailingConstraint(with: base.badgeView) {
  85. constraint.constant = badgeHeight * 0.5 + x
  86. return
  87. }
  88. let trailingConstraint = NSLayoutConstraint(item: base.badgeView, attribute: .trailing, relatedBy: .equal, toItem: base, attribute: .trailing, multiplier: 1.0, constant: badgeHeight * 0.5 + x)
  89. base.addConstraint(trailingConstraint)
  90. case .tail:
  91. base.centerXConstraint(with: base.badgeView)?.isActive = false
  92. base.trailingConstraint(with: base.badgeView)?.isActive = false
  93. if let constraint = base.leadingConstraint(with: base.badgeView) {
  94. constraint.constant = x - badgeHeight * 0.5
  95. return
  96. }
  97. let leadingConstraint = NSLayoutConstraint(item: base.badgeView, attribute: .leading, relatedBy: .equal, toItem: base, attribute: .trailing, multiplier: 1.0, constant: x - badgeHeight * 0.5)
  98. base.addConstraint(leadingConstraint)
  99. case .middle:
  100. base.leadingConstraint(with: base.badgeView)?.isActive = false
  101. base.trailingConstraint(with: base.badgeView)?.isActive = false
  102. base.centerXConstraint(with: base.badgeView)?.constant = x
  103. if let constraint = base.centerXConstraint(with: base.badgeView) {
  104. constraint.constant = x
  105. return
  106. }
  107. let centerXConstraint = NSLayoutConstraint(item: base.badgeView, attribute: .centerX, relatedBy: .equal, toItem: base, attribute: .centerX, multiplier: 1.0, constant: x)
  108. base.addConstraint(centerXConstraint)
  109. }
  110. }
  111. /// 设置Badge伸缩的方向
  112. ///
  113. /// Setting the direction of Badge expansion
  114. ///
  115. /// PPBadgeViewFlexModeHead, 左伸缩 Head Flex : <==●
  116. /// PPBadgeViewFlexModeTail, 右伸缩 Tail Flex : ●==>
  117. /// PPBadgeViewFlexModeMiddle 左右伸缩 Middle Flex : <=●=>
  118. /// - Parameter flexMode : Default is PPBadgeViewFlexModeTail
  119. func setBadge(flexMode: PPBadgeViewFlexMode = .tail) {
  120. base.badgeView.flexMode = flexMode
  121. moveBadge(x: base.badgeView.offset.x, y: base.badgeView.offset.y)
  122. }
  123. /// 设置Badge的高度,因为Badge宽度是动态可变的,通过改变Badge高度,其宽度也按比例变化,方便布局
  124. ///
  125. /// (注意: 此方法需要将Badge添加到控件上后再调用!!!)
  126. ///
  127. /// Set the height of Badge, because the Badge width is dynamically and  variable.By changing the Badge height in proportion to facilitate the layout.
  128. ///
  129. /// (Note: this method needs to add Badge to the controls and then use it !!!)
  130. ///
  131. /// - Parameter height: 高度大小
  132. func setBadge(height: CGFloat) {
  133. base.badgeView.layer.cornerRadius = height * 0.5
  134. base.badgeView.heightConstraint()?.constant = height
  135. moveBadge(x: base.badgeView.offset.x, y: base.badgeView.offset.y)
  136. }
  137. /// 显示Badge
  138. func showBadge() {
  139. base.badgeView.isHidden = false
  140. }
  141. /// 隐藏Badge
  142. func hiddenBadge() {
  143. base.badgeView.isHidden = true
  144. }
  145. // MARK: - 数字增加/减少, 注意:以下方法只适用于Badge内容为纯数字的情况
  146. // MARK: - Digital increase /decrease, note: the following method applies only to cases where the Badge content is purely numeric
  147. /// badge数字加1
  148. func increase() {
  149. increaseBy(number: 1)
  150. }
  151. /// badge数字加number
  152. func increaseBy(number: Int) {
  153. let label = base.badgeView
  154. let result = (Int(label.text ?? "0") ?? 0) + number
  155. if result > 0 {
  156. showBadge()
  157. }
  158. label.text = "\(result)"
  159. }
  160. /// badge数字加1
  161. func decrease() {
  162. decreaseBy(number: 1)
  163. }
  164. /// badge数字减number
  165. func decreaseBy(number: Int) {
  166. let label = base.badgeView
  167. let result = (Int(label.text ?? "0") ?? 0) - number
  168. if (result <= 0) {
  169. hiddenBadge()
  170. label.text = "0"
  171. return
  172. }
  173. label.text = "\(result)"
  174. }
  175. }
  176. extension UIView {
  177. private func addBadgeViewLayoutConstraint() {
  178. translatesAutoresizingMaskIntoConstraints = false
  179. badgeView.translatesAutoresizingMaskIntoConstraints = false
  180. let centerXConstraint = NSLayoutConstraint(item: badgeView, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: 0)
  181. let centerYConstraint = NSLayoutConstraint(item: badgeView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0)
  182. let widthConstraint = NSLayoutConstraint(item: badgeView, attribute: .width, relatedBy: .greaterThanOrEqual, toItem: badgeView, attribute: .height, multiplier: 1.0, constant: 0)
  183. let heightConstraint = NSLayoutConstraint(item: badgeView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 18)
  184. addConstraints([centerXConstraint, centerYConstraint])
  185. badgeView.addConstraints([widthConstraint, heightConstraint])
  186. }
  187. }
  188. // MARK: - getter/setter
  189. extension UIView {
  190. public var badgeView: PPBadgeControl {
  191. get {
  192. if let aValue = objc_getAssociatedObject(self, &kBadgeView) as? PPBadgeControl {
  193. return aValue
  194. }
  195. else {
  196. let badgeControl = PPBadgeControl.default()
  197. self.addSubview(badgeControl)
  198. self.bringSubviewToFront(badgeControl)
  199. self.badgeView = badgeControl
  200. self.addBadgeViewLayoutConstraint()
  201. return badgeControl
  202. }
  203. }
  204. set {
  205. objc_setAssociatedObject(self, &kBadgeView, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  206. }
  207. }
  208. internal func topConstraint(with item: AnyObject?) -> NSLayoutConstraint? {
  209. return constraint(with: item, attribute: .top)
  210. }
  211. internal func leadingConstraint(with item: AnyObject?) -> NSLayoutConstraint? {
  212. return constraint(with: item, attribute: .leading)
  213. }
  214. internal func bottomConstraint(with item: AnyObject?) -> NSLayoutConstraint? {
  215. return constraint(with: item, attribute: .bottom)
  216. }
  217. internal func trailingConstraint(with item: AnyObject?) -> NSLayoutConstraint? {
  218. return constraint(with: item, attribute: .trailing)
  219. }
  220. internal func widthConstraint() -> NSLayoutConstraint? {
  221. return constraint(with: self, attribute: .width)
  222. }
  223. internal func heightConstraint() -> NSLayoutConstraint? {
  224. return constraint(with: self, attribute: .height)
  225. }
  226. internal func centerXConstraint(with item: AnyObject?) -> NSLayoutConstraint? {
  227. return constraint(with: item, attribute: .centerX)
  228. }
  229. internal func centerYConstraint(with item: AnyObject?) -> NSLayoutConstraint? {
  230. return constraint(with: item, attribute: .centerY)
  231. }
  232. private func constraint(with item: AnyObject?, attribute: NSLayoutConstraint.Attribute) -> NSLayoutConstraint? {
  233. for constraint in constraints {
  234. if let isSame = constraint.firstItem?.isEqual(item), isSame, constraint.firstAttribute == attribute {
  235. return constraint
  236. }
  237. }
  238. return nil
  239. }
  240. }