Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
firefox-merge-…
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
executable file 137 lines (115 sloc) 4.58 KB
// FilledPageControl
//
// Copyright (c) 2016 Kyle Zaragoza <popwarsweet@gmail.com>
// MIT License
import UIKit
@IBDesignable open class FilledPageControl: UIView {
// MARK: - PageControl
@IBInspectable open var pageCount: Int = 0 {
didSet {
updateNumberOfPages(pageCount)
}
}
@IBInspectable open var progress: CGFloat = 0 {
didSet {
updateActivePageIndicatorMasks(progress)
}
}
open var currentPage: Int {
return Int(round(progress))
}
// MARK: - Appearance
override open var tintColor: UIColor! {
didSet {
inactiveLayers.forEach() { $0.backgroundColor = tintColor.cgColor }
}
}
@IBInspectable open var inactiveRingWidth: CGFloat = 1 {
didSet {
updateActivePageIndicatorMasks(progress)
}
}
@IBInspectable open var indicatorPadding: CGFloat = 10 {
didSet {
layoutPageIndicators(inactiveLayers)
}
}
@IBInspectable open var indicatorRadius: CGFloat = 5 {
didSet {
layoutPageIndicators(inactiveLayers)
}
}
fileprivate var indicatorDiameter: CGFloat {
return indicatorRadius * 2
}
fileprivate var inactiveLayers = [CALayer]()
// MARK: - State Update
fileprivate func updateNumberOfPages(_ count: Int) {
// no need to update
guard count != inactiveLayers.count else { return }
// reset current layout
inactiveLayers.forEach() { $0.removeFromSuperlayer() }
inactiveLayers = [CALayer]()
// add layers for new page count
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(progress)
self.invalidateIntrinsicContentSize()
}
// MARK: - Layout
fileprivate func updateActivePageIndicatorMasks(_ progress: CGFloat) {
// ignore if progress is outside of page indicators' bounds
guard progress >= 0 && progress <= CGFloat(pageCount - 1) else { return }
// mask rect w/ default stroke width
let insetRect = CGRect(width: indicatorDiameter, height: indicatorDiameter).insetBy(dx: inactiveRingWidth, dy: inactiveRingWidth)
let leftPageFloat = trunc(progress)
let leftPageInt = Int(progress)
// inset right moving page indicator
let spaceToMove = insetRect.width / 2
let percentPastLeftIndicator = progress - leftPageFloat
let additionalSpaceToInsetRight = spaceToMove * percentPastLeftIndicator
let closestRightInsetRect = insetRect.insetBy(dx: additionalSpaceToInsetRight, dy: additionalSpaceToInsetRight)
// inset left moving page indicator
let additionalSpaceToInsetLeft = (1 - percentPastLeftIndicator) * spaceToMove
let closestLeftInsetRect = insetRect.insetBy(dx: additionalSpaceToInsetLeft, dy: additionalSpaceToInsetLeft)
// adjust masks
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(width: layerDiameter, height: layerDiameter)
layers.forEach() { layer in
layer.cornerRadius = self.indicatorRadius
layer.frame = layerFrame
layerFrame.origin.x += layerDiameter + indicatorPadding
}
}
override open var intrinsicContentSize: CGSize {
return sizeThatFits(.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)
}
}