CHIPageControlJalapeno.swift 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. //
  2. // CHIPageControlJalapeno.swift.swift
  3. // CHIPageControl ( https://github.com/ChiliLabs/CHIPageControl )
  4. //
  5. // Copyright (c) 2017 Chili ( http://chi.lv )
  6. //
  7. //
  8. // Permission is hereby granted, free of charge, to any person obtaining a copy
  9. // of this software and associated documentation files (the "Software"), to deal
  10. // in the Software without restriction, including without limitation the rights
  11. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  12. // copies of the Software, and to permit persons to whom the Software is
  13. // furnished to do so, subject to the following conditions:
  14. //
  15. // The above copyright notice and this permission notice shall be included in
  16. // all copies or substantial portions of the Software.
  17. //
  18. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  24. // THE SOFTWARE.
  25. import UIKit
  26. open class CHIPageControlJalapeno: CHIBasePageControl {
  27. internal var lastPage:Int = 0
  28. fileprivate var diameter: CGFloat {
  29. return radius * 2
  30. }
  31. fileprivate var inactive = [CHILayer]()
  32. fileprivate var active: CHILayer = CHILayer()
  33. required public init?(coder aDecoder: NSCoder) {
  34. super.init(coder: aDecoder)
  35. }
  36. public override init(frame: CGRect) {
  37. super.init(frame: frame)
  38. }
  39. override func updateNumberOfPages(_ count: Int) {
  40. inactive.forEach { $0.removeFromSuperlayer() }
  41. inactive = [CHILayer]()
  42. inactive = (0..<count).map {_ in
  43. let layer = CHILayer()
  44. self.layer.addSublayer(layer)
  45. return layer
  46. }
  47. self.layer.addSublayer(active)
  48. setNeedsLayout()
  49. self.invalidateIntrinsicContentSize()
  50. }
  51. override func update(for progress: Double) {
  52. guard progress >= 0 && progress <= Double(numberOfPages - 1),
  53. let firstFrame = self.inactive.first?.frame,
  54. numberOfPages > 1 else {
  55. return
  56. }
  57. let left = firstFrame.origin.x
  58. let normalized = progress * Double(diameter + padding)
  59. let currentPage = Int(progress)
  60. let stepSize = (diameter + padding)
  61. var leftX = CGFloat(currentPage)*stepSize+left
  62. var rightX = CGFloat(normalized)+left
  63. let stepProgress = progress - Double(currentPage)
  64. if abs(self.lastPage - currentPage) > 1 {
  65. self.lastPage = currentPage + (self.lastPage > currentPage ? 1 : -1)
  66. }
  67. var middleX = CGFloat(normalized)
  68. if stepProgress > 0.5 {
  69. if self.lastPage > currentPage {
  70. rightX = CGFloat(self.lastPage)*stepSize + left
  71. leftX = leftX + ((CGFloat(stepProgress)-0.5)*stepSize*2)
  72. middleX = leftX
  73. } else {
  74. leftX = leftX + ((CGFloat(stepProgress)-0.5)*stepSize*2)
  75. rightX = CGFloat(self.currentPage)*stepSize + left
  76. middleX = rightX
  77. }
  78. } else if self.lastPage > currentPage {
  79. rightX = CGFloat(self.lastPage)*stepSize - ((0.5-CGFloat(stepProgress))*stepSize*2) + left
  80. middleX = leftX
  81. } else {
  82. rightX = rightX + (CGFloat(stepProgress)*stepSize)
  83. middleX = rightX
  84. }
  85. let top = (self.bounds.size.height - self.diameter)*0.5
  86. let points:[CGPoint] = [
  87. CGPoint(x:leftX, y:radius + top),
  88. CGPoint(x:middleX+radius, y:top),
  89. CGPoint(x:rightX+radius*2, y:radius + top),
  90. CGPoint(x:middleX+radius, y:radius*2 + top)
  91. ]
  92. let offset: CGFloat = radius*0.55
  93. let path = UIBezierPath()
  94. path.move(to: points[0])
  95. path.addCurve(to: points[1], controlPoint1: CGPoint(x:points[0].x, y: points[0].y-offset), controlPoint2: CGPoint(x:points[1].x-offset, y: points[1].y))
  96. path.addCurve(to: points[2], controlPoint1: CGPoint(x:points[1].x+offset, y: points[1].y), controlPoint2: CGPoint(x:points[2].x, y: points[2].y-offset))
  97. path.addCurve(to: points[3], controlPoint1: CGPoint(x:points[2].x, y: points[2].y+offset), controlPoint2: CGPoint(x:points[3].x+offset, y: points[3].y))
  98. path.addCurve(to: points[0], controlPoint1: CGPoint(x:points[3].x-offset, y: points[3].y), controlPoint2: CGPoint(x:points[0].x, y: points[0].y+offset))
  99. self.active.path = path.cgPath
  100. if progress.truncatingRemainder(dividingBy: 1) == 0 {
  101. self.lastPage = Int(progress)
  102. }
  103. }
  104. override open func layoutSubviews() {
  105. super.layoutSubviews()
  106. let floatCount = CGFloat(inactive.count)
  107. let x = (self.bounds.size.width - self.diameter*floatCount - self.padding*(floatCount-1))*0.5
  108. let y = (self.bounds.size.height - self.diameter)*0.5
  109. var frame = CGRect(x: x, y: y, width: self.diameter, height: self.diameter)
  110. inactive.enumerated().forEach() { index, layer in
  111. layer.backgroundColor = self.tintColor(position: index).withAlphaComponent(self.inactiveTransparency).cgColor
  112. if self.borderWidth > 0 {
  113. layer.borderWidth = self.borderWidth
  114. layer.borderColor = self.tintColor(position: index).cgColor
  115. }
  116. layer.cornerRadius = self.radius
  117. layer.frame = frame
  118. frame.origin.x += self.diameter + self.padding
  119. }
  120. self.active.fillColor = (self.currentPageTintColor ?? self.tintColor)?.cgColor
  121. update(for: progress)
  122. }
  123. override open var intrinsicContentSize: CGSize {
  124. return sizeThatFits(CGSize.zero)
  125. }
  126. override open func sizeThatFits(_ size: CGSize) -> CGSize {
  127. return CGSize(width: CGFloat(inactive.count) * self.diameter + CGFloat(inactive.count - 1) * self.padding,
  128. height: self.diameter)
  129. }
  130. override open func didTouch(gesture: UITapGestureRecognizer) {
  131. let point = gesture.location(ofTouch: 0, in: self)
  132. if let touchIndex = inactive.enumerated().first(where: { $0.element.hitTest(point) != nil })?.offset {
  133. delegate?.didTouch(pager: self, index: touchIndex)
  134. }
  135. }
  136. }