JXSegmentedTitleCell.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. //
  2. // JXSegmentedTitleCell.swift
  3. // JXSegmentedView
  4. //
  5. // Created by jiaxin on 2018/12/26.
  6. // Copyright © 2018 jiaxin. All rights reserved.
  7. //
  8. import UIKit
  9. open class JXSegmentedTitleCell: JXSegmentedBaseCell {
  10. public let titleLabel = UILabel()
  11. public let maskTitleLabel = UILabel()
  12. public let titleMaskLayer = CALayer()
  13. public let maskTitleMaskLayer = CALayer()
  14. open override func commonInit() {
  15. super.commonInit()
  16. titleLabel.textAlignment = .center
  17. contentView.addSubview(titleLabel)
  18. maskTitleLabel.textAlignment = .center
  19. maskTitleLabel.isHidden = true
  20. contentView.addSubview(maskTitleLabel)
  21. titleMaskLayer.backgroundColor = UIColor.red.cgColor
  22. maskTitleMaskLayer.backgroundColor = UIColor.red.cgColor
  23. maskTitleLabel.layer.mask = maskTitleMaskLayer
  24. }
  25. open override func layoutSubviews() {
  26. super.layoutSubviews()
  27. //为什么使用`sizeThatFits`,而不用`sizeToFit`呢?在numberOfLines大于0的时候,cell进行重用的时候通过`sizeToFit`,label设置成错误的size。至于原因我用尽毕生所学,没有找到为什么。但是用`sizeThatFits`可以规避掉这个问题。
  28. let labelSize = titleLabel.sizeThatFits(self.contentView.bounds.size)
  29. let labelBounds = CGRect(x: 0, y: 0, width: labelSize.width, height: labelSize.height)
  30. titleLabel.bounds = labelBounds
  31. titleLabel.center = contentView.center
  32. maskTitleLabel.bounds = labelBounds
  33. maskTitleLabel.center = contentView.center
  34. }
  35. open override func reloadData(itemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) {
  36. super.reloadData(itemModel: itemModel, selectedType: selectedType )
  37. guard let myItemModel = itemModel as? JXSegmentedTitleItemModel else {
  38. return
  39. }
  40. titleLabel.numberOfLines = myItemModel.titleNumberOfLines
  41. maskTitleLabel.numberOfLines = myItemModel.titleNumberOfLines
  42. if myItemModel.isTitleZoomEnabled {
  43. //先把font设置为缩放的最大值,再缩小到最小值,最后根据当前的titleCurrentZoomScale值,进行缩放更新。这样就能避免transform从小到大时字体模糊
  44. let maxScaleFont = UIFont(descriptor: myItemModel.titleNormalFont.fontDescriptor, size: myItemModel.titleNormalFont.pointSize*CGFloat(myItemModel.titleSelectedZoomScale))
  45. let baseScale = myItemModel.titleNormalFont.lineHeight/maxScaleFont.lineHeight
  46. if myItemModel.isSelectedAnimable && canStartSelectedAnimation(itemModel: itemModel, selectedType: selectedType) {
  47. //允许动画且当前是点击的
  48. let titleZoomClosure = preferredTitleZoomAnimateClosure(itemModel: myItemModel, baseScale: baseScale)
  49. appendSelectedAnimationClosure(closure: titleZoomClosure)
  50. }else {
  51. titleLabel.font = maxScaleFont
  52. maskTitleLabel.font = maxScaleFont
  53. let currentTransform = CGAffineTransform(scaleX: baseScale*CGFloat(myItemModel.titleCurrentZoomScale), y: baseScale*CGFloat(myItemModel.titleCurrentZoomScale))
  54. titleLabel.transform = currentTransform
  55. maskTitleLabel.transform = currentTransform
  56. }
  57. }else {
  58. if myItemModel.isSelected {
  59. titleLabel.font = myItemModel.titleSelectedFont
  60. maskTitleLabel.font = myItemModel.titleSelectedFont
  61. }else {
  62. titleLabel.font = myItemModel.titleNormalFont
  63. maskTitleLabel.font = myItemModel.titleNormalFont
  64. }
  65. }
  66. let title = myItemModel.title ?? ""
  67. let attriText = NSMutableAttributedString(string: title)
  68. if myItemModel.isTitleStrokeWidthEnabled {
  69. if myItemModel.isSelectedAnimable && canStartSelectedAnimation(itemModel: itemModel, selectedType: selectedType) {
  70. //允许动画且当前是点击的
  71. let titleStrokeWidthClosure = preferredTitleStrokeWidthAnimateClosure(itemModel: myItemModel, attriText: attriText)
  72. appendSelectedAnimationClosure(closure: titleStrokeWidthClosure)
  73. }else {
  74. attriText.addAttributes([NSAttributedString.Key.strokeWidth: myItemModel.titleCurrentStrokeWidth], range: NSRange(location: 0, length: title.count))
  75. titleLabel.attributedText = attriText
  76. maskTitleLabel.attributedText = attriText
  77. }
  78. }else {
  79. titleLabel.attributedText = attriText
  80. maskTitleLabel.attributedText = attriText
  81. }
  82. if myItemModel.isTitleMaskEnabled {
  83. //允许mask,maskTitleLabel在titleLabel上面,maskTitleLabel设置为titleSelectedColor。titleLabel设置为titleNormalColor
  84. //为了显示效果,使用了双遮罩。即titleMaskLayer遮罩titleLabel,maskTitleMaskLayer遮罩maskTitleLabel
  85. maskTitleLabel.isHidden = false
  86. maskTitleLabel.layer.mask = maskTitleMaskLayer
  87. titleLabel.textColor = myItemModel.titleNormalColor
  88. maskTitleLabel.textColor = myItemModel.titleSelectedColor
  89. let labelSize = maskTitleLabel.sizeThatFits(self.contentView.bounds.size)
  90. let labelBounds = CGRect(x: 0, y: 0, width: labelSize.width, height: labelSize.height)
  91. maskTitleLabel.bounds = labelBounds
  92. var topMaskFrame = myItemModel.indicatorConvertToItemFrame
  93. topMaskFrame.origin.y = 0
  94. var bottomMaskFrame = topMaskFrame
  95. var maskStartX: CGFloat = 0
  96. if maskTitleLabel.bounds.size.width >= bounds.size.width {
  97. topMaskFrame.origin.x -= (maskTitleLabel.bounds.size.width - bounds.size.width)/2
  98. bottomMaskFrame.size.width = maskTitleLabel.bounds.size.width
  99. maskStartX = -(maskTitleLabel.bounds.size.width - bounds.size.width)/2
  100. }else {
  101. topMaskFrame.origin.x -= (bounds.size.width - maskTitleLabel.bounds.size.width)/2
  102. bottomMaskFrame.size.width = bounds.size.width
  103. maskStartX = 0
  104. }
  105. bottomMaskFrame.origin.x = topMaskFrame.origin.x
  106. if topMaskFrame.origin.x > maskStartX {
  107. bottomMaskFrame.origin.x = topMaskFrame.origin.x - bottomMaskFrame.size.width
  108. }else {
  109. bottomMaskFrame.origin.x = topMaskFrame.maxX
  110. }
  111. CATransaction.begin()
  112. CATransaction.setDisableActions(true)
  113. if topMaskFrame.size.width > 0 && topMaskFrame.intersects(maskTitleLabel.frame) {
  114. titleLabel.layer.mask = titleMaskLayer
  115. titleMaskLayer.frame = bottomMaskFrame
  116. maskTitleMaskLayer.frame = topMaskFrame
  117. }else {
  118. titleLabel.layer.mask = nil
  119. maskTitleMaskLayer.frame = topMaskFrame
  120. }
  121. CATransaction.commit()
  122. }else {
  123. maskTitleLabel.isHidden = true
  124. maskTitleLabel.layer.mask = nil
  125. titleLabel.layer.mask = nil
  126. if myItemModel.isSelectedAnimable && canStartSelectedAnimation(itemModel: itemModel, selectedType: selectedType) {
  127. //允许动画且当前是点击的
  128. let titleColorClosure = preferredTitleColorAnimateClosure(itemModel: myItemModel)
  129. appendSelectedAnimationClosure(closure: titleColorClosure)
  130. }else {
  131. titleLabel.textColor = myItemModel.titleCurrentColor
  132. }
  133. }
  134. startSelectedAnimationIfNeeded(itemModel: itemModel, selectedType: selectedType)
  135. setNeedsLayout()
  136. }
  137. open func preferredTitleZoomAnimateClosure(itemModel: JXSegmentedTitleItemModel, baseScale: CGFloat) -> JXSegmentedCellSelectedAnimationClosure {
  138. return {[weak self] (percnet) in
  139. if itemModel.isSelected {
  140. //将要选中,scale从小到大插值渐变
  141. itemModel.titleCurrentZoomScale = JXSegmentedViewTool.interpolate(from: itemModel.titleNormalZoomScale, to: itemModel.titleSelectedZoomScale, percent: percnet)
  142. }else {
  143. //将要取消选中,scale从大到小插值渐变
  144. itemModel.titleCurrentZoomScale = JXSegmentedViewTool.interpolate(from: itemModel.titleSelectedZoomScale, to:itemModel.titleNormalZoomScale , percent: percnet)
  145. }
  146. let currentTransform = CGAffineTransform(scaleX: baseScale*itemModel.titleCurrentZoomScale, y: baseScale*itemModel.titleCurrentZoomScale)
  147. self?.titleLabel.transform = currentTransform
  148. self?.maskTitleLabel.transform = currentTransform
  149. }
  150. }
  151. open func preferredTitleStrokeWidthAnimateClosure(itemModel: JXSegmentedTitleItemModel, attriText: NSMutableAttributedString) -> JXSegmentedCellSelectedAnimationClosure{
  152. return {[weak self] (percent) in
  153. if itemModel.isSelected {
  154. //将要选中,StrokeWidth从小到大插值渐变
  155. itemModel.titleCurrentStrokeWidth = JXSegmentedViewTool.interpolate(from: itemModel.titleNormalStrokeWidth, to: itemModel.titleSelectedStrokeWidth, percent: percent)
  156. }else {
  157. //将要取消选中,StrokeWidth从大到小插值渐变
  158. itemModel.titleCurrentStrokeWidth = JXSegmentedViewTool.interpolate(from: itemModel.titleSelectedStrokeWidth, to:itemModel.titleNormalStrokeWidth , percent: percent)
  159. }
  160. attriText.addAttributes([NSAttributedString.Key.strokeWidth: itemModel.titleCurrentStrokeWidth], range: NSRange(location: 0, length: attriText.string.count))
  161. self?.titleLabel.attributedText = attriText
  162. self?.maskTitleLabel.attributedText = attriText
  163. }
  164. }
  165. open func preferredTitleColorAnimateClosure(itemModel: JXSegmentedTitleItemModel) -> JXSegmentedCellSelectedAnimationClosure {
  166. return {[weak self] (percent) in
  167. if itemModel.isSelected {
  168. //将要选中,textColor从titleNormalColor到titleSelectedColor插值渐变
  169. itemModel.titleCurrentColor = JXSegmentedViewTool.interpolateThemeColor(from: itemModel.titleNormalColor, to: itemModel.titleSelectedColor, percent: percent)
  170. }else {
  171. //将要取消选中,textColor从titleSelectedColor到titleNormalColor插值渐变
  172. itemModel.titleCurrentColor = JXSegmentedViewTool.interpolateThemeColor(from: itemModel.titleSelectedColor, to: itemModel.titleNormalColor, percent: percent)
  173. }
  174. self?.titleLabel.textColor = itemModel.titleCurrentColor
  175. }
  176. }
  177. override func setSelectedStyle(isSelected: Bool) {
  178. if isSelected {
  179. self.titleLabel.textColor = (self.itemModel as? JXSegmentedTitleItemModel)?.titleSelectedColor
  180. } else {
  181. self.titleLabel.textColor = (self.itemModel as? JXSegmentedTitleItemModel)?.titleNormalColor
  182. }
  183. }
  184. }