ESTabBarItemContentView.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. //
  2. // ESTabBarContentView.swift
  3. //
  4. // Created by Vincent Li on 2017/2/8.
  5. // Copyright (c) 2013-2018 ESTabBarController (https://github.com/eggswift/ESTabBarController)
  6. //
  7. // Permission is hereby granted, free of charge, to any person obtaining a copy
  8. // of this software and associated documentation files (the "Software"), to deal
  9. // in the Software without restriction, including without limitation the rights
  10. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. // copies of the Software, and to permit persons to whom the Software is
  12. // furnished to do so, subject to the following conditions:
  13. //
  14. // The above copyright notice and this permission notice shall be included in
  15. // all copies or substantial portions of the Software.
  16. //
  17. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. // THE SOFTWARE.
  24. //
  25. import UIKit
  26. public enum ESTabBarItemContentMode : Int {
  27. case alwaysOriginal // Always set the original image size
  28. case alwaysTemplate // Always set the image as a template image size
  29. }
  30. open class ESTabBarItemContentView: UIView {
  31. // MARK: - PROPERTY SETTING
  32. /// 设置contentView的偏移
  33. open var insets = UIEdgeInsets.zero
  34. /// 是否被选中
  35. open var selected = false
  36. /// 是否处于高亮状态
  37. open var highlighted = false
  38. /// 是否支持高亮
  39. open var highlightEnabled = true
  40. /// 文字颜色
  41. open var textColor = UIColor(white: 0.57254902, alpha: 1.0) {
  42. didSet {
  43. if !selected { titleLabel.textColor = textColor }
  44. }
  45. }
  46. /// 高亮时文字颜色
  47. open var highlightTextColor = UIColor(red: 0.0, green: 0.47843137, blue: 1.0, alpha: 1.0) {
  48. didSet {
  49. if selected { titleLabel.textColor = highlightIconColor }
  50. }
  51. }
  52. /// icon颜色
  53. open var iconColor = UIColor(white: 0.57254902, alpha: 1.0) {
  54. didSet {
  55. if !selected { imageView.tintColor = iconColor }
  56. }
  57. }
  58. /// 高亮时icon颜色
  59. open var highlightIconColor = UIColor(red: 0.0, green: 0.47843137, blue: 1.0, alpha: 1.0) {
  60. didSet {
  61. if selected { imageView.tintColor = highlightIconColor }
  62. }
  63. }
  64. /// 背景颜色
  65. open var backdropColor = UIColor.clear {
  66. didSet {
  67. if !selected { backgroundColor = backdropColor }
  68. }
  69. }
  70. /// 高亮时背景颜色
  71. open var highlightBackdropColor = UIColor.clear {
  72. didSet {
  73. if selected { backgroundColor = highlightBackdropColor }
  74. }
  75. }
  76. open var title: String? {
  77. didSet {
  78. self.titleLabel.text = title
  79. self.updateLayout()
  80. }
  81. }
  82. /// Icon imageView renderingMode, default is .alwaysTemplate like UITabBarItem
  83. open var renderingMode: UIImage.RenderingMode = .alwaysTemplate {
  84. didSet {
  85. self.updateDisplay()
  86. }
  87. }
  88. /// Item content mode, default is .alwaysTemplate like UITabBarItem
  89. open var itemContentMode: ESTabBarItemContentMode = .alwaysTemplate {
  90. didSet {
  91. self.updateDisplay()
  92. }
  93. }
  94. /// Icon imageView's image
  95. open var image: UIImage? {
  96. didSet {
  97. if !selected { self.updateDisplay() }
  98. }
  99. }
  100. open var selectedImage: UIImage? {
  101. didSet {
  102. if selected { self.updateDisplay() }
  103. }
  104. }
  105. open var imageView: UIImageView = {
  106. let imageView = UIImageView.init(frame: CGRect.zero)
  107. imageView.backgroundColor = .clear
  108. return imageView
  109. }()
  110. open var titleLabel: UILabel = {
  111. let titleLabel = UILabel.init(frame: CGRect.zero)
  112. titleLabel.backgroundColor = .clear
  113. titleLabel.textColor = .clear
  114. titleLabel.textAlignment = .center
  115. return titleLabel
  116. }()
  117. /// Badge value
  118. open var badgeValue: String? {
  119. didSet {
  120. if let _ = badgeValue {
  121. self.badgeView.badgeValue = badgeValue
  122. self.addSubview(badgeView)
  123. self.updateLayout()
  124. } else {
  125. // Remove when nil.
  126. self.badgeView.removeFromSuperview()
  127. }
  128. badgeChanged(animated: true, completion: nil)
  129. }
  130. }
  131. open var badgeColor: UIColor? {
  132. didSet {
  133. if let _ = badgeColor {
  134. self.badgeView.badgeColor = badgeColor
  135. } else {
  136. self.badgeView.badgeColor = ESTabBarItemBadgeView.defaultBadgeColor
  137. }
  138. }
  139. }
  140. open var badgeView: ESTabBarItemBadgeView = ESTabBarItemBadgeView() {
  141. willSet {
  142. if let _ = badgeView.superview {
  143. badgeView.removeFromSuperview()
  144. }
  145. }
  146. didSet {
  147. if let _ = badgeView.superview {
  148. self.updateLayout()
  149. }
  150. }
  151. }
  152. open var badgeOffset: UIOffset = UIOffset.init(horizontal: 6.0, vertical: -22.0) {
  153. didSet {
  154. if badgeOffset != oldValue {
  155. self.updateLayout()
  156. }
  157. }
  158. }
  159. // MARK: -
  160. public override init(frame: CGRect) {
  161. super.init(frame: frame)
  162. self.isUserInteractionEnabled = false
  163. addSubview(imageView)
  164. addSubview(titleLabel)
  165. titleLabel.textColor = textColor
  166. imageView.tintColor = iconColor
  167. backgroundColor = backdropColor
  168. }
  169. public required init?(coder aDecoder: NSCoder) {
  170. fatalError("init(coder:) has not been implemented")
  171. }
  172. open func updateDisplay() {
  173. imageView.image = (selected ? (selectedImage ?? image) : image)?.withRenderingMode(renderingMode)
  174. imageView.tintColor = selected ? highlightIconColor : iconColor
  175. titleLabel.textColor = selected ? highlightTextColor : textColor
  176. backgroundColor = selected ? highlightBackdropColor : backdropColor
  177. }
  178. open func updateLayout() {
  179. let w = self.bounds.size.width
  180. let h = self.bounds.size.height
  181. imageView.isHidden = (imageView.image == nil)
  182. titleLabel.isHidden = (titleLabel.text == nil)
  183. if self.itemContentMode == .alwaysTemplate {
  184. var s: CGFloat = 0.0 // image size
  185. var f: CGFloat = 0.0 // font
  186. var isLandscape = false
  187. if let keyWindow = UIApplication.shared.keyWindow {
  188. isLandscape = keyWindow.bounds.width > keyWindow.bounds.height
  189. }
  190. let isWide = isLandscape || traitCollection.horizontalSizeClass == .regular // is landscape or regular
  191. if #available(iOS 11.0, *), isWide {
  192. s = UIScreen.main.scale == 3.0 ? 23.0 : 20.0
  193. f = UIScreen.main.scale == 3.0 ? 13.0 : 12.0
  194. } else {
  195. s = 23.0
  196. f = 10.0
  197. }
  198. if !imageView.isHidden && !titleLabel.isHidden {
  199. titleLabel.font = UIFont.systemFont(ofSize: f)
  200. titleLabel.sizeToFit()
  201. if #available(iOS 11.0, *), isWide {
  202. titleLabel.frame = CGRect.init(x: (w - titleLabel.bounds.size.width) / 2.0 + (UIScreen.main.scale == 3.0 ? 14.25 : 12.25),
  203. y: (h - titleLabel.bounds.size.height) / 2.0,
  204. width: titleLabel.bounds.size.width,
  205. height: titleLabel.bounds.size.height)
  206. imageView.frame = CGRect.init(x: titleLabel.frame.origin.x - s - (UIScreen.main.scale == 3.0 ? 6.0 : 5.0),
  207. y: (h - s) / 2.0,
  208. width: s,
  209. height: s)
  210. } else {
  211. titleLabel.frame = CGRect.init(x: (w - titleLabel.bounds.size.width) / 2.0,
  212. y: h - titleLabel.bounds.size.height - 1.0,
  213. width: titleLabel.bounds.size.width,
  214. height: titleLabel.bounds.size.height)
  215. imageView.frame = CGRect.init(x: (w - s) / 2.0,
  216. y: (h - s) / 2.0 - 6.0,
  217. width: s,
  218. height: s)
  219. }
  220. } else if !imageView.isHidden {
  221. imageView.frame = CGRect.init(x: (w - s) / 2.0,
  222. y: (h - s) / 2.0,
  223. width: s,
  224. height: s)
  225. } else if !titleLabel.isHidden {
  226. titleLabel.font = UIFont.systemFont(ofSize: f)
  227. titleLabel.sizeToFit()
  228. titleLabel.frame = CGRect.init(x: (w - titleLabel.bounds.size.width) / 2.0,
  229. y: (h - titleLabel.bounds.size.height) / 2.0,
  230. width: titleLabel.bounds.size.width,
  231. height: titleLabel.bounds.size.height)
  232. }
  233. if let _ = badgeView.superview {
  234. let size = badgeView.sizeThatFits(self.frame.size)
  235. if #available(iOS 11.0, *), isWide {
  236. badgeView.frame = CGRect.init(origin: CGPoint.init(x: imageView.frame.midX - 3 + badgeOffset.horizontal, y: imageView.frame.midY + 3 + badgeOffset.vertical), size: size)
  237. } else {
  238. badgeView.frame = CGRect.init(origin: CGPoint.init(x: w / 2.0 + badgeOffset.horizontal, y: h / 2.0 + badgeOffset.vertical), size: size)
  239. }
  240. badgeView.setNeedsLayout()
  241. }
  242. } else {
  243. if !imageView.isHidden && !titleLabel.isHidden {
  244. titleLabel.sizeToFit()
  245. imageView.sizeToFit()
  246. titleLabel.frame = CGRect.init(x: (w - titleLabel.bounds.size.width) / 2.0,
  247. y: h - titleLabel.bounds.size.height - 1.0,
  248. width: titleLabel.bounds.size.width,
  249. height: titleLabel.bounds.size.height)
  250. imageView.frame = CGRect.init(x: (w - imageView.bounds.size.width) / 2.0,
  251. y: (h - imageView.bounds.size.height) / 2.0 - 6.0,
  252. width: imageView.bounds.size.width,
  253. height: imageView.bounds.size.height)
  254. } else if !imageView.isHidden {
  255. imageView.sizeToFit()
  256. imageView.center = CGPoint.init(x: w / 2.0, y: h / 2.0)
  257. } else if !titleLabel.isHidden {
  258. titleLabel.sizeToFit()
  259. titleLabel.center = CGPoint.init(x: w / 2.0, y: h / 2.0)
  260. }
  261. if let _ = badgeView.superview {
  262. let size = badgeView.sizeThatFits(self.frame.size)
  263. badgeView.frame = CGRect.init(origin: CGPoint.init(x: w / 2.0 + badgeOffset.horizontal, y: h / 2.0 + badgeOffset.vertical), size: size)
  264. badgeView.setNeedsLayout()
  265. }
  266. }
  267. }
  268. // MARK: - INTERNAL METHODS
  269. internal final func select(animated: Bool, completion: (() -> ())?) {
  270. selected = true
  271. if highlightEnabled && highlighted {
  272. highlighted = false
  273. dehighlightAnimation(animated: animated, completion: { [weak self] in
  274. self?.updateDisplay()
  275. self?.selectAnimation(animated: animated, completion: completion)
  276. })
  277. } else {
  278. updateDisplay()
  279. selectAnimation(animated: animated, completion: completion)
  280. }
  281. }
  282. internal final func deselect(animated: Bool, completion: (() -> ())?) {
  283. selected = false
  284. updateDisplay()
  285. self.deselectAnimation(animated: animated, completion: completion)
  286. }
  287. internal final func reselect(animated: Bool, completion: (() -> ())?) {
  288. if selected == false {
  289. select(animated: animated, completion: completion)
  290. } else {
  291. if highlightEnabled && highlighted {
  292. highlighted = false
  293. dehighlightAnimation(animated: animated, completion: { [weak self] in
  294. self?.reselectAnimation(animated: animated, completion: completion)
  295. })
  296. } else {
  297. reselectAnimation(animated: animated, completion: completion)
  298. }
  299. }
  300. }
  301. internal final func highlight(animated: Bool, completion: (() -> ())?) {
  302. if !highlightEnabled {
  303. return
  304. }
  305. if highlighted == true {
  306. return
  307. }
  308. highlighted = true
  309. self.highlightAnimation(animated: animated, completion: completion)
  310. }
  311. internal final func dehighlight(animated: Bool, completion: (() -> ())?) {
  312. if !highlightEnabled {
  313. return
  314. }
  315. if !highlighted {
  316. return
  317. }
  318. highlighted = false
  319. self.dehighlightAnimation(animated: animated, completion: completion)
  320. }
  321. internal func badgeChanged(animated: Bool, completion: (() -> ())?) {
  322. self.badgeChangedAnimation(animated: animated, completion: completion)
  323. }
  324. // MARK: - ANIMATION METHODS
  325. open func selectAnimation(animated: Bool, completion: (() -> ())?) {
  326. completion?()
  327. }
  328. open func deselectAnimation(animated: Bool, completion: (() -> ())?) {
  329. completion?()
  330. }
  331. open func reselectAnimation(animated: Bool, completion: (() -> ())?) {
  332. completion?()
  333. }
  334. open func highlightAnimation(animated: Bool, completion: (() -> ())?) {
  335. completion?()
  336. }
  337. open func dehighlightAnimation(animated: Bool, completion: (() -> ())?) {
  338. completion?()
  339. }
  340. open func badgeChangedAnimation(animated: Bool, completion: (() -> ())?) {
  341. completion?()
  342. }
  343. }