143 lines
4.7 KiB
Swift
Executable File
143 lines
4.7 KiB
Swift
Executable File
//
|
|
// LLSnakePageControl.swift
|
|
// LL 使用备注
|
|
// https://github.com/popwarsweet/PageControls
|
|
//
|
|
// Created by Kyle Zaragoza on 8/5/16.
|
|
// Copyright © 2016 Kyle Zaragoza. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
|
|
open class LLSnakePageControl: UIView {
|
|
|
|
// MARK: - PageControl
|
|
|
|
open var pageCount: Int = 0 {
|
|
didSet {
|
|
updateNumberOfPages(pageCount)
|
|
}
|
|
}
|
|
open var progress: CGFloat = 0 {
|
|
didSet {
|
|
layoutActivePageIndicator(progress)
|
|
}
|
|
}
|
|
open var currentPage: Int {
|
|
return Int(round(progress))
|
|
}
|
|
|
|
|
|
// MARK: - Appearance
|
|
|
|
open var activeTint: UIColor = UIColor.white {
|
|
didSet {
|
|
activeLayer.backgroundColor = activeTint.cgColor
|
|
}
|
|
}
|
|
open var inactiveTint: UIColor = UIColor(white: 1, alpha: 0.3) {
|
|
didSet {
|
|
inactiveLayers.forEach() { $0.backgroundColor = inactiveTint.cgColor }
|
|
}
|
|
}
|
|
open var indicatorPadding: CGFloat = 10 {
|
|
didSet {
|
|
layoutInactivePageIndicators(inactiveLayers)
|
|
}
|
|
}
|
|
open var indicatorRadius: CGFloat = 5 {
|
|
didSet {
|
|
layoutInactivePageIndicators(inactiveLayers)
|
|
}
|
|
}
|
|
|
|
fileprivate var indicatorDiameter: CGFloat {
|
|
return indicatorRadius * 2
|
|
}
|
|
fileprivate var inactiveLayers = [CALayer]()
|
|
fileprivate lazy var activeLayer: CALayer = { [unowned self] in
|
|
let layer = CALayer()
|
|
layer.frame = CGRect(origin: CGPoint.zero,
|
|
size: CGSize(width: self.indicatorDiameter, height: self.indicatorDiameter))
|
|
layer.backgroundColor = self.activeTint.cgColor
|
|
layer.cornerRadius = self.indicatorRadius
|
|
layer.actions = [
|
|
"bounds": NSNull(),
|
|
"frame": NSNull(),
|
|
"position": NSNull()]
|
|
return layer
|
|
}()
|
|
|
|
override public init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
pageCount = 0
|
|
progress = 0
|
|
indicatorPadding = 8
|
|
indicatorRadius = 4
|
|
}
|
|
|
|
required public init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
// 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.inactiveTint.cgColor
|
|
self.layer.addSublayer(layer)
|
|
return layer
|
|
}
|
|
layoutInactivePageIndicators(inactiveLayers)
|
|
// ensure active page indicator is on top
|
|
self.layer.addSublayer(activeLayer)
|
|
layoutActivePageIndicator(progress)
|
|
self.invalidateIntrinsicContentSize()
|
|
}
|
|
|
|
|
|
// MARK: - Layout
|
|
|
|
fileprivate func layoutActivePageIndicator(_ progress: CGFloat) {
|
|
// ignore if progress is outside of page indicators' bounds
|
|
guard progress >= 0 && progress <= CGFloat(pageCount - 1) else { return }
|
|
let denormalizedProgress = progress * (indicatorDiameter + indicatorPadding)
|
|
let distanceFromPage = abs(round(progress) - progress)
|
|
var newFrame = activeLayer.frame
|
|
let widthMultiplier = (1 + distanceFromPage*2)
|
|
newFrame.origin.x = denormalizedProgress
|
|
newFrame.size.width = newFrame.height * widthMultiplier
|
|
activeLayer.frame = newFrame
|
|
}
|
|
|
|
fileprivate func layoutInactivePageIndicators(_ 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) * indicatorDiameter + 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 {
|
|
return CGSize(width: CGFloat(inactiveLayers.count) * indicatorDiameter + CGFloat(inactiveLayers.count - 1) * indicatorPadding,
|
|
height: indicatorDiameter)
|
|
}
|
|
}
|