123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- import UIKit
- open class LLFilledPageControl: UIView {
-
- open var pageCount: Int = 0 {
- didSet {
- updateNumberOfPages(pageCount)
- }
- }
- open var progress: CGFloat = 0 {
- didSet {
- updateActivePageIndicatorMasks(forProgress: progress)
- }
- }
- open var currentPage: Int {
- return Int(round(progress))
- }
-
- override open var tintColor: UIColor! {
- didSet {
- inactiveLayers.forEach { $0.backgroundColor = tintColor.cgColor }
- }
- }
- open var inactiveRingWidth: CGFloat = 1 {
- didSet {
- updateActivePageIndicatorMasks(forProgress: progress)
- }
- }
- open var indicatorPadding: CGFloat = 8 {
- didSet {
- layoutPageIndicators(inactiveLayers)
- }
- }
- open var indicatorRadius: CGFloat = 4 {
- didSet {
- layoutPageIndicators(inactiveLayers)
- }
- }
- fileprivate var indicatorDiameter: CGFloat {
- return indicatorRadius * 2
- }
- fileprivate var inactiveLayers = [CALayer]()
- override public init(frame: CGRect) {
- super.init(frame: frame)
- pageCount = 0
- progress = 0
- inactiveRingWidth = 1
- indicatorPadding = 8
- indicatorRadius = 4
- }
- required public init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- fileprivate func updateNumberOfPages(_ count: Int) {
-
- guard count != inactiveLayers.count else { return }
-
- inactiveLayers.forEach { $0.removeFromSuperlayer() }
- inactiveLayers = [CALayer]()
-
- inactiveLayers = stride(from: 0, to:count, by:1).map { _ in
- let layer = CALayer()
- layer.backgroundColor = self.tintColor.cgColor
- self.layer.addSublayer(layer)
- return layer
- }
- layoutPageIndicators(inactiveLayers)
- updateActivePageIndicatorMasks(forProgress: progress)
- self.invalidateIntrinsicContentSize()
- }
-
- fileprivate func updateActivePageIndicatorMasks(forProgress progress: CGFloat) {
-
- guard progress >= 0 && progress <= CGFloat(pageCount - 1) else { return }
-
- let insetRect = CGRect(x: 0, y: 0, width: indicatorDiameter, height: indicatorDiameter).insetBy(dx: inactiveRingWidth, dy: inactiveRingWidth)
- let leftPageFloat = trunc(progress)
- let leftPageInt = Int(progress)
-
- let spaceToMove = insetRect.width / 2
- let percentPastLeftIndicator = progress - leftPageFloat
- let additionalSpaceToInsetRight = spaceToMove * percentPastLeftIndicator
- let closestRightInsetRect = insetRect.insetBy(dx: additionalSpaceToInsetRight, dy: additionalSpaceToInsetRight)
-
- let additionalSpaceToInsetLeft = (1 - percentPastLeftIndicator) * spaceToMove
- let closestLeftInsetRect = insetRect.insetBy(dx: additionalSpaceToInsetLeft, dy: additionalSpaceToInsetLeft)
-
- for (idx, layer) in inactiveLayers.enumerated() {
- let maskLayer = CAShapeLayer()
- maskLayer.fillRule = kCAFillRuleEvenOdd
- let boundsPath = UIBezierPath(rect: layer.bounds)
- let circlePath: UIBezierPath
- if leftPageInt == idx {
- circlePath = UIBezierPath(ovalIn: closestLeftInsetRect)
- } else if leftPageInt + 1 == idx {
- circlePath = UIBezierPath(ovalIn: closestRightInsetRect)
- } else {
- circlePath = UIBezierPath(ovalIn: insetRect)
- }
- boundsPath.append(circlePath)
- maskLayer.path = boundsPath.cgPath
- layer.mask = maskLayer
- }
- }
- fileprivate func layoutPageIndicators(_ layers: [CALayer]) {
- let layerDiameter = indicatorRadius * 2
- var layerFrame = CGRect(x: 0, y: 0, width: layerDiameter, height: layerDiameter)
- layers.forEach { layer in
- layer.cornerRadius = self.indicatorRadius
- layer.frame = layerFrame
- layerFrame.origin.x += layerDiameter + indicatorPadding
- }
-
- let oldFrame = self.frame
- let width = CGFloat(inactiveLayers.count) * layerDiameter + CGFloat(inactiveLayers.count - 1) * indicatorPadding
- self.frame = CGRect.init(x: UIScreen.main.bounds.width / 2 - width / 2, y: oldFrame.origin.y, width: width, height: oldFrame.size.height)
- }
- override open var intrinsicContentSize: CGSize {
- return sizeThatFits(CGSize.zero)
- }
- override open func sizeThatFits(_ size: CGSize) -> CGSize {
- let layerDiameter = indicatorRadius * 2
- return CGSize(width: CGFloat(inactiveLayers.count) * layerDiameter + CGFloat(inactiveLayers.count - 1) * indicatorPadding,
- height: layerDiameter)
- }
- }
|