JXSegmentedTitleCell.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  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. titleLabel.textColor = myItemModel.titleNormalColor
  87. maskTitleLabel.textColor = myItemModel.titleSelectedColor
  88. let labelSize = maskTitleLabel.sizeThatFits(self.contentView.bounds.size)
  89. let labelBounds = CGRect(x: 0, y: 0, width: labelSize.width, height: labelSize.height)
  90. maskTitleLabel.bounds = labelBounds
  91. var topMaskFrame = myItemModel.indicatorConvertToItemFrame
  92. topMaskFrame.origin.y = 0
  93. var bottomMaskFrame = topMaskFrame
  94. var maskStartX: CGFloat = 0
  95. if maskTitleLabel.bounds.size.width >= bounds.size.width {
  96. topMaskFrame.origin.x -= (maskTitleLabel.bounds.size.width - bounds.size.width)/2
  97. bottomMaskFrame.size.width = maskTitleLabel.bounds.size.width
  98. maskStartX = -(maskTitleLabel.bounds.size.width - bounds.size.width)/2
  99. }else {
  100. topMaskFrame.origin.x -= (bounds.size.width - maskTitleLabel.bounds.size.width)/2
  101. bottomMaskFrame.size.width = bounds.size.width
  102. maskStartX = 0
  103. }
  104. bottomMaskFrame.origin.x = topMaskFrame.origin.x
  105. if topMaskFrame.origin.x > maskStartX {
  106. bottomMaskFrame.origin.x = topMaskFrame.origin.x - bottomMaskFrame.size.width
  107. }else {
  108. bottomMaskFrame.origin.x = topMaskFrame.maxX
  109. }
  110. CATransaction.begin()
  111. CATransaction.setDisableActions(true)
  112. if topMaskFrame.size.width > 0 && topMaskFrame.intersects(maskTitleLabel.frame) {
  113. titleLabel.layer.mask = titleMaskLayer
  114. titleMaskLayer.frame = bottomMaskFrame
  115. maskTitleMaskLayer.frame = topMaskFrame
  116. }else {
  117. titleLabel.layer.mask = nil
  118. maskTitleMaskLayer.frame = topMaskFrame
  119. }
  120. CATransaction.commit()
  121. }else {
  122. maskTitleLabel.isHidden = true
  123. titleLabel.layer.mask = nil
  124. if myItemModel.isSelectedAnimable && canStartSelectedAnimation(itemModel: itemModel, selectedType: selectedType) {
  125. //允许动画且当前是点击的
  126. let titleColorClosure = preferredTitleColorAnimateClosure(itemModel: myItemModel)
  127. appendSelectedAnimationClosure(closure: titleColorClosure)
  128. }else {
  129. titleLabel.textColor = myItemModel.titleCurrentColor
  130. }
  131. }
  132. startSelectedAnimationIfNeeded(itemModel: itemModel, selectedType: selectedType)
  133. setNeedsLayout()
  134. }
  135. open func preferredTitleZoomAnimateClosure(itemModel: JXSegmentedTitleItemModel, baseScale: CGFloat) -> JXSegmentedCellSelectedAnimationClosure {
  136. return {[weak self] (percnet) in
  137. if itemModel.isSelected {
  138. //将要选中,scale从小到大插值渐变
  139. itemModel.titleCurrentZoomScale = JXSegmentedViewTool.interpolate(from: itemModel.titleNormalZoomScale, to: itemModel.titleSelectedZoomScale, percent: percnet)
  140. }else {
  141. //将要取消选中,scale从大到小插值渐变
  142. itemModel.titleCurrentZoomScale = JXSegmentedViewTool.interpolate(from: itemModel.titleSelectedZoomScale, to:itemModel.titleNormalZoomScale , percent: percnet)
  143. }
  144. let currentTransform = CGAffineTransform(scaleX: baseScale*itemModel.titleCurrentZoomScale, y: baseScale*itemModel.titleCurrentZoomScale)
  145. self?.titleLabel.transform = currentTransform
  146. self?.maskTitleLabel.transform = currentTransform
  147. }
  148. }
  149. open func preferredTitleStrokeWidthAnimateClosure(itemModel: JXSegmentedTitleItemModel, attriText: NSMutableAttributedString) -> JXSegmentedCellSelectedAnimationClosure{
  150. return {[weak self] (percent) in
  151. if itemModel.isSelected {
  152. //将要选中,StrokeWidth从小到大插值渐变
  153. itemModel.titleCurrentStrokeWidth = JXSegmentedViewTool.interpolate(from: itemModel.titleNormalStrokeWidth, to: itemModel.titleSelectedStrokeWidth, percent: percent)
  154. }else {
  155. //将要取消选中,StrokeWidth从大到小插值渐变
  156. itemModel.titleCurrentStrokeWidth = JXSegmentedViewTool.interpolate(from: itemModel.titleSelectedStrokeWidth, to:itemModel.titleNormalStrokeWidth , percent: percent)
  157. }
  158. attriText.addAttributes([NSAttributedString.Key.strokeWidth: itemModel.titleCurrentStrokeWidth], range: NSRange(location: 0, length: attriText.string.count))
  159. self?.titleLabel.attributedText = attriText
  160. self?.maskTitleLabel.attributedText = attriText
  161. }
  162. }
  163. open func preferredTitleColorAnimateClosure(itemModel: JXSegmentedTitleItemModel) -> JXSegmentedCellSelectedAnimationClosure {
  164. return {[weak self] (percent) in
  165. if itemModel.isSelected {
  166. //将要选中,textColor从titleNormalColor到titleSelectedColor插值渐变
  167. itemModel.titleCurrentColor = JXSegmentedViewTool.interpolateColor(from: itemModel.titleNormalColor, to: itemModel.titleSelectedColor, percent: percent)
  168. }else {
  169. //将要取消选中,textColor从titleSelectedColor到titleNormalColor插值渐变
  170. itemModel.titleCurrentColor = JXSegmentedViewTool.interpolateColor(from: itemModel.titleSelectedColor, to: itemModel.titleNormalColor, percent: percent)
  171. }
  172. self?.titleLabel.textColor = itemModel.titleCurrentColor
  173. }
  174. }
  175. }