|
//
// SeMobSlideTabView.swift
// PaiAi
//
// Created by mac on 16/7/21.
// Copyright © 2016年 FFIB. All rights reserved.
//
import UIKit
private let firstTabTag = 1231115
private let firstLayoutViewTag = 131115
protocol SeMobSlideTabViewDelegate {
func slideTabViewDidSelect(_ index: Int)
}
enum SeMobSlideTabAlignType {
case alignCenter
case alignFixMargin(left : CGFloat?, mid : CGFloat?, right : CGFloat?)
}
@IBDesignable
class SeMobSlideTabView: UIView {
// MARK: - -delegate--
var delegate: SeMobSlideTabViewDelegate?
var alignType: SeMobSlideTabAlignType = .alignCenter
// MARK: - -class members--
//绑定的scrollview,随着scrollview滑动改变slider滑块的位置
var scrollview: UIScrollView? {
didSet {
if scrollview != oldValue {
oldValue?.removeObserver(self, forKeyPath: "contentOffset")
scrollview?.addObserver(self, forKeyPath: "contentOffset", options: .new, context: nil)
if scrollview != nil {
setPortionWithScrollViewWithOffset(scrollview!.contentOffset)
}
}
}
}
//标题
@IBInspectable var titles: [String] = [] {
didSet {
while badgeTypes.count < titles.count {
badgeTypes.append(.number)
}
badgeNumbers = [Int](repeating: 0, count: titles.count)
needsRelayout = true
}
}
//标题字体
@IBInspectable var titleFont = UIFont.systemFont(ofSize: 13) {
didSet {
self.subviews.forEach {
if let label = $0 as? UILabel {
label.font = titleFont
}
}
}
}
//竖条分隔符,预留,暂不支持
var seperaterLineView: UIView? {
didSet {
needsRelayout = true
}
}
//滑块自定义view
var slideLineView: UIView? {
didSet {
needsRelayout = true
}
}
//单色滑块颜色,优先使用slideLineView,没有则使用该颜色创建一个纯色滑块
@IBInspectable var slideLineColor: UIColor? {
didSet {
needsRelayout = true
}
}
//滑块大小(宽、高)
@IBInspectable var slideLineSize = CGSize.zero {
didSet {
needsRelayout = true
}
}
//滑块距离底部的间距
@IBInspectable var slideLineBottom: CGFloat = 0 {
didSet {
needsRelayout = true
}
}
//选中的title颜色
@IBInspectable var selectedTitleTextColor: UIColor! {
didSet {
needsRelayout = true
}
}
//未选中的title颜色
@IBInspectable var titleTextColor: UIColor = UIColor.black {
didSet {
needsRelayout = true
}
}
@IBInspectable var selectedIndex: Int = 0 {
didSet {
let value = min(titles.count, max(0, selectedIndex))
selectedIndex = value
if scrollview == nil {
sliderPortion = Double(value)
}
for i in 0 ..< self.titles.count {
if let label = self.viewWithTag(firstTabTag + i) as? UILabel {
if i != selectedIndex {
label.textColor = self.titleTextColor
} else {
label.textColor = self.selectedTitleTextColor
}
}
}
if selectedIndex != oldValue {
self.delegate?.slideTabViewDidSelect(selectedIndex)
}
}
}
fileprivate var sliderPortion = 0.0 {
didSet {
if sliderPortion != oldValue {
moveSlider(sliderPortion < oldValue ? true : false)
if (sliderPortion - Double(Int(sliderPortion))) < 0.00001 {
selectedIndex = Int(sliderPortion)
}
}
}
}
//-- badge --
enum BadgeType {
case none
case dot
case number
//使用自定义的block添加badge,UIView参数为当前tab文字view,使用该参数作为layout依据
case customView((_ targetView:UIView, _ badgeNumber:Int, _ viewTag:Int)->Void)
}
var badgeTypes: [BadgeType] = [.none, .dot, .number]
var badgeNumbers: [Int] = []
//-- end badge --
fileprivate var needsRelayout = true {
didSet {
if needsRelayout {
self.sliderView = nil
self.setNeedsLayout()
}
}
}
fileprivate var sliderView: UIView?
fileprivate var sliderPosConstraint: NSLayoutConstraint?
// MARK: - - init methods --
override init(frame: CGRect) {
sliderPortion = 0
super.init(frame:frame)
let gr = UITapGestureRecognizer(target: self, action: #selector(SeMobSlideTabView.didTap(_:)))
self.addGestureRecognizer(gr)
}
required init?(coder aDecoder: NSCoder) {
sliderPortion = 0
super.init(coder:aDecoder)
let gr = UITapGestureRecognizer(target: self, action: #selector(SeMobSlideTabView.didTap(_:)))
self.addGestureRecognizer(gr)
}
deinit {
scrollview?.removeObserver(self, forKeyPath: "contentOffset")
}
// MARK: - - class methods --
@objc func didTap(_ gr: UITapGestureRecognizer) {
let pt = gr.location(in: self)
let index = pt.x / (self.bounds.width/CGFloat(self.titles.count))
self.selectedIndex = Int(index)
}
override func layoutSubviews() {
if needsRelayout {
self.relayout()
}
super.layoutSubviews()
}
func relayout() {
func relayoutTitleForAlignCenter() {
for i in 0 ..< self.titles.count {
let label = UILabel()
label.tag = firstTabTag + i
label.text = self.titles[i]
label.font = self.titleFont
label.textAlignment = .center
label.textColor = self.titleTextColor
if (self.selectedTitleTextColor != nil && i == selectedIndex) {
label.textColor = self.selectedTitleTextColor
}
self.addSubview(label)
label.useAutoLayout()
let multiplierBase = CGFloat(1.0) / CGFloat(self.titles.count)
let multiplierCenterX = (multiplierBase * CGFloat(i) + multiplierBase / CGFloat(2)) * CGFloat(2)
NSLayoutConstraint(item: label, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: multiplierCenterX, constant: 0).active()
NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[label]-0-|", options: [], metrics: nil, views: ["label": label]).autolayoutInstall()
buildBadgeNumber(atIndex: i)
}
}
func relayoutTitleForAlignFixMargin(_ left: CGFloat?, _ mid: CGFloat?, _ right: CGFloat?) {
for i in 0 ..< self.titles.count {
// --label
let label = UILabel()
label.tag = firstTabTag + i
label.text = self.titles[i]
label.font = self.titleFont
label.textAlignment = .center
label.textColor = self.titleTextColor
if (self.selectedTitleTextColor != nil && i == selectedIndex) {
label.textColor = self.selectedTitleTextColor
}
self.addSubview(label)
label.useAutoLayout()
NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[label]-0-|", options: [], metrics: nil, views: ["label": label]).autolayoutInstall()
//--- margin
let leftMarginView = UIView().useAutoLayout()
leftMarginView.tag = firstLayoutViewTag + i
self.addSubview(leftMarginView)
leftMarginView.backgroundColor = UIColor.clear
NSLayoutConstraint.horizontalSpace(leftMarginView, secondView: label)
NSLayoutConstraint.centerY(leftMarginView, secondView: label)
NSLayoutConstraint.equalHeight(firstView: leftMarginView, secondView: label)
if i == 0 {
NSLayoutConstraint.pinLeading(self, secondView: leftMarginView)
if let left = left {
leftMarginView.setLayoutWidth(left)
}
} else if i == 1 {
if let mid = mid {
leftMarginView.setLayoutWidth(mid)
}
if let lastLabel = self.viewWithTag(firstTabTag + i - 1) {
NSLayoutConstraint.horizontalSpace(lastLabel, secondView: leftMarginView)
}
} else {
if let lastMarginView = self.viewWithTag(firstLayoutViewTag + i - 1) {
NSLayoutConstraint.equalWidth(firstView: lastMarginView, secondView: leftMarginView)
}
if let lastLabel = self.viewWithTag(firstTabTag + i - 1) {
NSLayoutConstraint.horizontalSpace(lastLabel, secondView: leftMarginView)
}
}
if i == self.titles.count - 1 {
let rightEdgeView = UIView().useAutoLayout()
rightEdgeView.tag = firstLayoutViewTag + i + 1
self.addSubview(rightEdgeView)
rightEdgeView.backgroundColor = UIColor.clear
NSLayoutConstraint.horizontalSpace(label, secondView: rightEdgeView)
NSLayoutConstraint.centerY(rightEdgeView, secondView: label)
NSLayoutConstraint.equalHeight(firstView: rightEdgeView, secondView: label)
NSLayoutConstraint.pinTrailing(rightEdgeView, secondView: self)
let leftEdgeView = self.viewWithTag(firstLayoutViewTag)!
if let right = right {
//规定了右侧的距离
rightEdgeView.setLayoutWidth(right)
if let _ = left {
//规定了左侧距离,已经在i=0时处理
} else {
//没有规定左侧距离
NSLayoutConstraint.equalWidth(firstView: leftEdgeView, secondView: leftMarginView)
}
} else if let _ = left {
NSLayoutConstraint.equalWidth(firstView: leftMarginView, secondView: rightEdgeView)
} else {
NSLayoutConstraint.equalWidth(firstView: leftEdgeView, secondView: rightEdgeView)
NSLayoutConstraint.equalWidth(firstView: leftEdgeView, secondView: leftMarginView)
}
}
buildBadgeNumber(atIndex: i)
}
}
self.subviews.forEach {$0.removeFromSuperview()}
//titles
switch alignType {
case .alignCenter:
relayoutTitleForAlignCenter()
case .alignFixMargin(let left, let mid, let right) :
relayoutTitleForAlignFixMargin(left, mid, right)
}
// slider
self.createSlider()
//text color
for i in 0 ..< self.titles.count {
if let label = self.viewWithTag(firstTabTag + i) as? UILabel {
if i != selectedIndex {
label.textColor = self.titleTextColor
} else {
label.textColor = self.selectedTitleTextColor
}
}
}
self.needsRelayout = false
}
func createSlider() {
let targetLabel = self.viewWithTag(firstTabTag+selectedIndex)
if slideLineView != nil {
sliderView = slideLineView
} else if slideLineColor != nil {
sliderView = UIImageView(image: UIImage.imageWithColor(slideLineColor!))
}
if sliderView != nil {
self.addSubview(sliderView!)
sliderView!.useAutoLayout()
NSLayoutConstraint.constraints(withVisualFormat: "H:[slider(width)]", options: [], metrics: ["width": slideLineSize.width], views: ["slider": sliderView!]).autolayoutInstall()
NSLayoutConstraint.constraints(withVisualFormat: "V:[slider(height)]-bottom-|", options: [], metrics: ["bottom": slideLineBottom, "height": slideLineSize.height], views: ["slider": sliderView!]).autolayoutInstall()
self.sliderPosConstraint = NSLayoutConstraint(item: sliderView!, attribute: .centerX, relatedBy: .equal, toItem: targetLabel!, attribute: .centerX, multiplier: 1, constant: 0)
self.sliderPosConstraint!.active()
}
}
func moveSlider(_ left: Bool = false) {
if sliderView != nil {
self.sliderPosConstraint?.deActive()
switch alignType {
case .alignCenter:
let multiplierBase = CGFloat(1.0) / CGFloat(self.titles.count)
let multiplierCenterX = (multiplierBase * CGFloat(self.sliderPortion) + multiplierBase / CGFloat(2)) * CGFloat(2)
sliderPosConstraint = NSLayoutConstraint(item: sliderView!, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: multiplierCenterX, constant: 0)
case .alignFixMargin:
let portion = sliderPortion - Double(Int(sliderPortion))
if abs(portion) < 0.00001 {
if let targetLable = self.viewWithTag(Int(sliderPortion) + firstTabTag) {
sliderPosConstraint = NSLayoutConstraint.centerX(sliderView!, secondView: targetLable)
}
} else {
if let leftLable = self.viewWithTag(Int(sliderPortion) + firstTabTag), let rightLabel = self.viewWithTag(Int(sliderPortion) + firstTabTag + 1) {
let totalLength = abs(leftLable.center.x - rightLabel.center.x)
sliderPosConstraint = NSLayoutConstraint.centerX(sliderView!, secondView: leftLable, constant: CGFloat(portion) * totalLength )
}
}
}
sliderPosConstraint!.active()
UIView.animate(withDuration: 0.2, animations: { () -> Void in
self.sliderView!.superview?.layoutIfNeeded()
})
}
}
static let badgeTagStart = 8090
func updateBadgeNumber(index: Int, number: Int) {
if index < badgeNumbers.count {
badgeNumbers[index] = number
buildBadgeNumber(atIndex: index)
}
}
fileprivate func buildBadgeNumber(atIndex index: Int) {
if index >= titles.count {
return
}
let badgeType = badgeTypes[index]
let number = badgeNumbers[index]
if number == 0 {
let badge = self.viewWithTag(SeMobSlideTabView.badgeTagStart+index)
badge?.removeFromSuperview()
} else {
if let titleLabel = self.viewWithTag(firstTabTag+index) as? UILabel {
let badge = self.viewWithTag(SeMobSlideTabView.badgeTagStart+index)
badge?.removeFromSuperview()
switch badgeType {
case .none :
break
case .dot :
let badgeDot = UIView()
badgeDot.backgroundColor = UIColor.red
badgeDot.cornerRadius = 2
badgeDot.tag = SeMobSlideTabView.badgeTagStart+index
self.addSubview(badgeDot)
badgeDot.useAutoLayout()
NSLayoutConstraint.constraints(withVisualFormat: "H:[title]-1-[badge(==4)]", options: [], metrics: nil, views: ["title": titleLabel, "badge": badgeDot]).autolayoutInstall()
NSLayoutConstraint(item: badgeDot, attribute: .top, relatedBy: .equal, toItem: titleLabel, attribute: .top, multiplier: 1, constant: 8).active()
NSLayoutConstraint(item: badgeDot, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 4).active()
case .number:
let badgeLabel = UILabel()
badgeLabel.backgroundColor = UIColor.red
badgeLabel.cornerRadius = 6
badgeLabel.font = UIFont.systemFont(ofSize: 10)
badgeLabel.textColor = UIColor.white
badgeLabel.tag = SeMobSlideTabView.badgeTagStart+index
self.addSubview(badgeLabel)
badgeLabel.useAutoLayout()
badgeLabel.textAlignment = .center
badgeLabel.text = "\(number)"
let metrics = ["width": 13]
NSLayoutConstraint.constraints(withVisualFormat: "H:[title]-1-[badge(>=width)]", options: [], metrics: metrics, views: ["title": titleLabel, "badge": badgeLabel]).autolayoutInstall()
NSLayoutConstraint(item: badgeLabel, attribute: .top, relatedBy: .equal, toItem: titleLabel, attribute: .top, multiplier: 1, constant: 7).active()
NSLayoutConstraint(item: badgeLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 12).active()
case .customView(let block):
block(titleLabel, number, SeMobSlideTabView.badgeTagStart+index)
}
}
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let _ = object as? UIScrollView {
if let point: NSValue = change?[NSKeyValueChangeKey.newKey] as! NSValue? {
var pt: CGPoint = CGPoint.zero
point.getValue(&pt)
self.setPortionWithScrollViewWithOffset(pt)
}
}
}
fileprivate func setPortionWithScrollViewWithOffset(_ point: CGPoint) {
if let sv = scrollview {
if sv.contentSize.width > 0 {
let portion = (point.x / sv.contentSize.width) * CGFloat(titles.count)
sliderPortion = Double(portion)
}
}
}
}
|