No Description

LLFilledPageControl.swift 5.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. //
  2. // LLFilledPageControl.swift
  3. // LL 使用备注
  4. // https://github.com/popwarsweet/PageControls
  5. //
  6. // Created by Kyle Zaragoza on 8/6/16.
  7. // Copyright © 2016 Kyle Zaragoza. All rights reserved.
  8. //
  9. import UIKit
  10. open class LLFilledPageControl: UIView {
  11. // MARK: - PageControl
  12. open var pageCount: Int = 0 {
  13. didSet {
  14. updateNumberOfPages(pageCount)
  15. }
  16. }
  17. open var progress: CGFloat = 0 {
  18. didSet {
  19. updateActivePageIndicatorMasks(forProgress: progress)
  20. }
  21. }
  22. open var currentPage: Int {
  23. return Int(round(progress))
  24. }
  25. // MARK: - Appearance
  26. override open var tintColor: UIColor! {
  27. didSet {
  28. inactiveLayers.forEach { $0.backgroundColor = tintColor.cgColor }
  29. }
  30. }
  31. open var inactiveRingWidth: CGFloat = 1 {
  32. didSet {
  33. updateActivePageIndicatorMasks(forProgress: progress)
  34. }
  35. }
  36. open var indicatorPadding: CGFloat = 8 {
  37. didSet {
  38. layoutPageIndicators(inactiveLayers)
  39. }
  40. }
  41. open var indicatorRadius: CGFloat = 4 {
  42. didSet {
  43. layoutPageIndicators(inactiveLayers)
  44. }
  45. }
  46. fileprivate var indicatorDiameter: CGFloat {
  47. return indicatorRadius * 2
  48. }
  49. fileprivate var inactiveLayers = [CALayer]()
  50. override public init(frame: CGRect) {
  51. super.init(frame: frame)
  52. pageCount = 0
  53. progress = 0
  54. inactiveRingWidth = 1
  55. indicatorPadding = 8
  56. indicatorRadius = 4
  57. }
  58. required public init?(coder aDecoder: NSCoder) {
  59. fatalError("init(coder:) has not been implemented")
  60. }
  61. // MARK: - State Update
  62. fileprivate func updateNumberOfPages(_ count: Int) {
  63. // no need to update
  64. guard count != inactiveLayers.count else { return }
  65. // reset current layout
  66. inactiveLayers.forEach { $0.removeFromSuperlayer() }
  67. inactiveLayers = [CALayer]()
  68. // add layers for new page count
  69. inactiveLayers = stride(from: 0, to:count, by:1).map { _ in
  70. let layer = CALayer()
  71. layer.backgroundColor = self.tintColor.cgColor
  72. self.layer.addSublayer(layer)
  73. return layer
  74. }
  75. layoutPageIndicators(inactiveLayers)
  76. updateActivePageIndicatorMasks(forProgress: progress)
  77. self.invalidateIntrinsicContentSize()
  78. }
  79. // MARK: - Layout
  80. fileprivate func updateActivePageIndicatorMasks(forProgress progress: CGFloat) {
  81. // ignore if progress is outside of page indicators' bounds
  82. guard progress >= 0 && progress <= CGFloat(pageCount - 1) else { return }
  83. // mask rect w/ default stroke width
  84. let insetRect = CGRect(x: 0, y: 0, width: indicatorDiameter, height: indicatorDiameter).insetBy(dx: inactiveRingWidth, dy: inactiveRingWidth)
  85. let leftPageFloat = trunc(progress)
  86. let leftPageInt = Int(progress)
  87. // inset right moving page indicator
  88. let spaceToMove = insetRect.width / 2
  89. let percentPastLeftIndicator = progress - leftPageFloat
  90. let additionalSpaceToInsetRight = spaceToMove * percentPastLeftIndicator
  91. let closestRightInsetRect = insetRect.insetBy(dx: additionalSpaceToInsetRight, dy: additionalSpaceToInsetRight)
  92. // inset left moving page indicator
  93. let additionalSpaceToInsetLeft = (1 - percentPastLeftIndicator) * spaceToMove
  94. let closestLeftInsetRect = insetRect.insetBy(dx: additionalSpaceToInsetLeft, dy: additionalSpaceToInsetLeft)
  95. // adjust masks
  96. for (idx, layer) in inactiveLayers.enumerated() {
  97. let maskLayer = CAShapeLayer()
  98. maskLayer.fillRule = kCAFillRuleEvenOdd
  99. let boundsPath = UIBezierPath(rect: layer.bounds)
  100. let circlePath: UIBezierPath
  101. if leftPageInt == idx {
  102. circlePath = UIBezierPath(ovalIn: closestLeftInsetRect)
  103. } else if leftPageInt + 1 == idx {
  104. circlePath = UIBezierPath(ovalIn: closestRightInsetRect)
  105. } else {
  106. circlePath = UIBezierPath(ovalIn: insetRect)
  107. }
  108. boundsPath.append(circlePath)
  109. maskLayer.path = boundsPath.cgPath
  110. layer.mask = maskLayer
  111. }
  112. }
  113. fileprivate func layoutPageIndicators(_ layers: [CALayer]) {
  114. let layerDiameter = indicatorRadius * 2
  115. var layerFrame = CGRect(x: 0, y: 0, width: layerDiameter, height: layerDiameter)
  116. layers.forEach { layer in
  117. layer.cornerRadius = self.indicatorRadius
  118. layer.frame = layerFrame
  119. layerFrame.origin.x += layerDiameter + indicatorPadding
  120. }
  121. // 布局
  122. let oldFrame = self.frame
  123. let width = CGFloat(inactiveLayers.count) * layerDiameter + CGFloat(inactiveLayers.count - 1) * indicatorPadding
  124. self.frame = CGRect.init(x: UIScreen.main.bounds.width / 2 - width / 2, y: oldFrame.origin.y, width: width, height: oldFrame.size.height)
  125. }
  126. override open var intrinsicContentSize: CGSize {
  127. return sizeThatFits(CGSize.zero)
  128. }
  129. override open func sizeThatFits(_ size: CGSize) -> CGSize {
  130. let layerDiameter = indicatorRadius * 2
  131. return CGSize(width: CGFloat(inactiveLayers.count) * layerDiameter + CGFloat(inactiveLayers.count - 1) * indicatorPadding,
  132. height: layerDiameter)
  133. }
  134. }