|
//
// FFPageMenuView.swift
// FFPage
//
// Created by FFIB on 2017/10/17.
// Copyright © 2017年 FFIB. All rights reserved.
//
import UIKit
public protocol FFPageMenuViewDelegate {
func pageMenuView(pageMenuView: FFPageMenuView, didSelectItemAt index: Int)
}
public enum FFPageMenuContentMode {
case scaleAspectFill
case center(space: CGFloat)
}
@IBDesignable
open class FFPageMenuView: UIView {
private var needlayout = true
private var space: CGFloat = 0
private let baseTag = 55161750
private var selectedLablel = UILabel()
private var sliderConstriant: NSLayoutConstraint?
private var isTap = false
private var ffConstraints = [NSLayoutConstraint]()
//configuratable parameter
public var sliderView: UIView?
public var selectedColor = UIColor.blue
public var normalColor = UIColor.black
public var font = UIFont.systemFont(ofSize: 17)
public var pageDelegate: FFPageMenuViewDelegate?
public var selectIndex: Int = 0
public var pageMenuContentMode = FFPageMenuContentMode.scaleAspectFill
public var isBigger = true
public var titles = [String]() {
didSet {
needlayout = true
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
let tap = UITapGestureRecognizer(target: self, action: #selector(didTap(gesture:)))
addGestureRecognizer(tap)
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let tap = UITapGestureRecognizer(target: self, action: #selector(didTap(gesture:)))
addGestureRecognizer(tap)
}
@objc func didTap(gesture: UITapGestureRecognizer) {
let point = gesture.location(in: self)
let targetView = subviews.filter { $0.frame.contains(point) }.first
guard let target = targetView else { return }
scroll(at: target.tag - baseTag)
pageDelegate?.pageMenuView(pageMenuView: self, didSelectItemAt: target.tag - baseTag)
selectIndex = target.tag - baseTag
}
open override func layoutSubviews() {
super.layoutSubviews()
if needlayout {
reloadSubviews()
}
}
private func reloadSubviews() {
func clear() {
subviews.forEach { $0.removeFromSuperview() }
NSLayoutConstraint.deactivate(ffConstraints)
ffConstraints.removeAll()
needlayout = false
}
clear()
layoutIfNeeded()
configurationOfMenu()
configurationOfSlider()
NSLayoutConstraint.activate(ffConstraints)
}
}
// MARK: configuration
extension FFPageMenuView {
private func configurationOfSlider() {
guard let label = viewWithTag(baseTag) else {
return
}
if sliderView == nil {
sliderView = UIView(frame: CGRect(x: 0, y: 0, width: 15, height: 2))
sliderView?.backgroundColor = selectedColor
}
sliderView!.translatesAutoresizingMaskIntoConstraints = false
addSubview(sliderView!)
//init slide constraint
sliderConstriant = NSLayoutConstraint(item: sliderView!,
attribute: .centerX,
relatedBy: .equal,
toItem: label,
attribute: .centerX,
multiplier: 1,
constant: 0)
let sliderConstriants = [sliderConstriant!,
NSLayoutConstraint(item: sliderView!,
attribute: .height,
relatedBy: .equal,
toItem: nil,
attribute: .height,
multiplier: 1,
constant: sliderView!.bounds.height),
NSLayoutConstraint(item: sliderView!,
attribute: .top,
relatedBy: .equal,
toItem: label,
attribute: .bottom,
multiplier: 1,
constant: 5),
NSLayoutConstraint(item: sliderView!,
attribute: .width,
relatedBy: .equal,
toItem: nil,
attribute: .width,
multiplier: 1,
constant: sliderView!.bounds.width)]
ffConstraints += sliderConstriants
}
private func configurationOfMenu() {
var last: UILabel?
let labelWidth: CGFloat
switch pageMenuContentMode {
case .scaleAspectFill:
space = 0
labelWidth = bounds.width / CGFloat(titles.count)
case .center(let spacing):
space = spacing
labelWidth = 0
}
var contentWidth: CGFloat = 0
for (i, title) in titles.enumerated() {
//init label
let label = UILabel()
label.tag = baseTag + i
label.text = title
label.font = font
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
label.sizeToFit()
label.textColor = normalColor
addSubview(label)
if i == 0 {
selectedLablel = label
label.textColor = selectedColor
if isBigger {
label.font = UIFont.systemFont(ofSize: font.pointSize + 1)
}
}
//init label constraint
let leftConstraint: NSLayoutConstraint
if last == nil {
leftConstraint = NSLayoutConstraint(item: label,
attribute: .leading,
relatedBy: .equal,
toItem: self,
attribute: .leading,
multiplier: 1,
constant: space)
} else {
leftConstraint = NSLayoutConstraint(item: label,
attribute: .left,
relatedBy: .equal,
toItem: last,
attribute: .right,
multiplier: 1,
constant: space)
}
var labelConstraints = [NSLayoutConstraint(item: label,
attribute: .centerY,
relatedBy: .equal,
toItem: self,
attribute: .centerY,
multiplier: 1,
constant: 0),
leftConstraint]
if labelWidth != 0 {
labelConstraints.append(NSLayoutConstraint(item: label,
attribute: .width,
relatedBy: .equal,
toItem: nil,
attribute: .width,
multiplier: 1,
constant: labelWidth))
contentWidth += labelWidth
} else {
contentWidth += label.bounds.width + space
}
ffConstraints += labelConstraints
last = label
}
ffConstraints.append(NSLayoutConstraint(item: self,
attribute: .width,
relatedBy: .equal,
toItem: nil,
attribute: .width,
multiplier: 1,
constant: contentWidth))
}
}
//scroll function
extension FFPageMenuView {
private func translationSlider(percent: CGFloat) {
sliderConstriant?.isActive = false
sliderConstriant = NSLayoutConstraint(item: sliderView!,
attribute: .centerX,
relatedBy: .equal,
toItem: selectedLablel,
attribute: .centerX,
multiplier: 1,
constant: percent)
sliderConstriant?.isActive = true
}
func scroll(at index: Int, animated: Bool = true) {
guard let targetLabel = viewWithTag(index + baseTag) as? UILabel,
targetLabel.tag != selectedLablel.tag else { return }
selectedLablel.textColor = normalColor
selectedLablel.font = font
targetLabel.textColor = selectedColor
if isBigger {
targetLabel.font = UIFont.systemFont(ofSize: font.pointSize + 1)
}
if animated {
let animation = CAKeyframeAnimation(keyPath: "position.x")
animation.values = [sliderView!.frame.origin.x, sliderView!.frame.origin.x + targetLabel.frame.origin.x - selectedLablel.frame.origin.x]
animation.delegate = self
animation.isRemovedOnCompletion = false
animation.fillMode = kCAFillModeForwards
sliderView?.layer.add(animation, forKey: "FFPage.FFPageMenuView.sliderView.animation")
} else {
sliderConstriant?.isActive = false
sliderConstriant = NSLayoutConstraint(item: sliderView!,
attribute: .centerX,
relatedBy: .equal,
toItem: targetLabel,
attribute: .centerX,
multiplier: 1,
constant: 0)
sliderConstriant?.isActive = true
}
selectedLablel = targetLabel
}
func scroll(offset: CGFloat) {
let percent = offset - CGFloat(selectedLablel.tag - baseTag)
if percent > 0 && percent < 1 {
guard let nextLabel = viewWithTag(selectedLablel.tag + 1) as? UILabel else {
return
}
translationSlider(percent: percent * (space + nextLabel.frame.width / 2 + selectedLablel.frame.width / 2))
} else if percent < 0 && percent > -1 {
guard let nextLabel = viewWithTag(selectedLablel.tag - 1) as? UILabel else {
return
}
translationSlider(percent: percent * (space + nextLabel.frame.width / 2 + selectedLablel.frame.width / 2))
} else {
if percent != 0 {
scroll(at: Int(offset), animated: false)
}
}
}
}
//CAAnimationDelegate
extension FFPageMenuView: CAAnimationDelegate {
public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
//move slide
translationSlider(percent: 0)
sliderView!.layer.removeAnimation(forKey: "FFPage.FFPageMenuView.sliderView.animation")
}
}
|