// // SlideSegmentView.swift // swift翻译 // // Created by zhongyuan on 2018/3/20. // Copyright © 2018年 zhongyuan. All rights reserved. // import UIKit import SnapKit @IBDesignable class SlideSegmentView: UIView, UIScrollViewDelegate { /// MARK: -- 属性 @IBOutlet weak var titleScrollViews: UIScrollView! @IBOutlet weak var titleStackViews: UIStackView! @IBOutlet weak var contentScrollViews: UIScrollView! @IBOutlet weak var contentStackViews: UIStackView! @IBOutlet weak var titleStackViewWidthConstraints: NSLayoutConstraint! @IBOutlet weak var titleStackViewLeftConstraints: NSLayoutConstraint! @IBOutlet weak var titleStackViewRightConstraints: NSLayoutConstraint! @IBOutlet weak var UnderlineView: UIView! @IBOutlet weak var contentView: UIView! @IBOutlet weak var contentStackWidth: NSLayoutConstraint! var titleColorNormal:UIColor = UIColor.orange//Color_SlideSegmentView_titleColorNormal var titleColorSelected:UIColor = UIColor.orange//Color_SlideSegmentView_titleColorSelected var titleFont:UIFont = UIFont.systemFont(ofSize: 15) var underlineViewBgColor:UIColor = UIColor.orange //Color_family_normal var titleViews:[UIView] = [UIView]() var curPageIndex = 0 var titles:Array = [] /**< 标签标题 */ { didSet{ for subview:UIView in titleStackViews.arrangedSubviews { titleStackViews.removeArrangedSubview(subview) } for subview:UIView in contentStackViews.arrangedSubviews { contentStackViews.removeArrangedSubview(subview) } titleScrollViews.contentOffset = CGPoint.zero contentScrollViews.contentOffset = CGPoint.zero for title in titles { let btn:UIButton = UIButton.init(type: .custom) btn.titleLabel?.font = titleFont btn.setTitleColor(titleColorNormal, for: .normal) btn.setTitleColor(titleColorSelected, for: .selected) btn.setTitle(title, for: .normal) btn.sizeToFit() btn.addTarget(self, action:#selector(onTitleButtomClick( sender: )), for: .touchUpInside) titleStackViews.addArrangedSubview(btn) } self.refreshTitleSpace() contentScrollViews.contentSize = CGSize(width: width * CGFloat(titles.count), height: contentScrollViews.superview!.height - contentScrollViews.y) contentStackWidth.constant = CGFloat(titles.count) * width for _ in 0.. = [] { didSet{ let count = contentViews.count if count != viewControllers.count { viewControllers = [] }else { for i in 0.. = [] { didSet{ if(viewControllers.count > 0){ var views:Array = [] for vc in viewControllers { views.append(vc.view) } self.contentViews = views } } } var didScrollToIndex:((Int)->())? /**< 滚动到第index页时回调 */ var space:CGFloat = 30 /**< 各标题的间隔 */ var pageIndex:Int /**< 当前页index */ { set{ self.setPageIndex(pageIndex: newValue , animated: false) } get{ for titleBtn in titleStackViews.arrangedSubviews { if (titleBtn as! UIButton).isSelected { return titleStackViews.arrangedSubviews.firstIndex(of: titleBtn)! } } return 0 } } func setPageIndex(pageIndex:Int,animated:Bool){ self.layoutIfNeeded() if titleStackViews.arrangedSubviews.count <= pageIndex { return } for titleBtn in titleStackViews.arrangedSubviews { (titleBtn as! UIButton).isSelected = false } let sender:UIButton = titleStackViews.arrangedSubviews[pageIndex] as! UIButton sender.isSelected = true // 滚动标题栏、内容栏 let count = self.titleViews.count let startX = self.titleViews[pageIndex].frame.origin.x let isRight = (pageIndex - self.curPageIndex) > 0 let isLeft = (pageIndex - self.curPageIndex) < 0 let width = self.titleViews[pageIndex].width + self.space let endX = startX + width let x = self.width * CGFloat(pageIndex) let _animated = abs(contentScrollViews.contentOffset.x - x) <= self.width contentScrollViews.setContentOffset(CGPoint(x: x, y: 0), animated: animated && _animated) //向左滑,且左边有未显示的部分 if isLeft,self.titleScrollViews.contentOffset.x > 0 { //若当前单元格的前一个单元格已到最左边或之前,则向右滑 if (startX - self.titleScrollViews.contentOffset.x - width) < 10 { var offectX = (startX - width) offectX = max(offectX, 0) self.titleScrollViews.setContentOffset(CGPoint.init(x: offectX, y: 0), animated: animated) } } //向右滑,且右边有未显示的部分 if isRight,(self.titleScrollViews.contentOffset.x + self.width) < self.titleViews[count - 1].right{ //若当前单元格的后一个单元格已到最右边,则向左滑 if (endX + width) > (self.width - 10) { var offectX = (endX + width - self.width) offectX = min(offectX, self.titleViews[count - 1].right - self.width + space) self.titleScrollViews.setContentOffset(CGPoint.init(x: offectX, y: 0), animated: animated) } } titleScrollViews.layoutIfNeeded() UIView.animate(withDuration: 0.25) { let strW:CGFloat = (self.titleStackViews.subviews[pageIndex] as! UIButton).width self.UnderlineView.snp.remakeConstraints({ (mark) in mark.bottom.equalTo(self.titleScrollViews) mark.centerX.equalTo(sender) mark.width.equalTo(strW) mark.height.equalTo(3) }) self.titleScrollViews.layoutIfNeeded() } if didScrollToIndex != nil{ didScrollToIndex!(pageIndex) } curPageIndex = pageIndex } override func awakeFromNib() { super.awakeFromNib() contentScrollViews.delegate = self UnderlineView.backgroundColor = underlineViewBgColor } override func layoutSubviews() { super.layoutSubviews() self.refreshTitleSpace() contentScrollViews.contentSize = CGSize.init(width: self.width * CGFloat(titles.count), height: contentScrollViews.superview!.height - contentScrollViews.y) contentStackWidth.constant = CGFloat(titles.count) * self.width } @objc func onTitleButtomClick(sender:UIButton) -> Void { self.setPageIndex(pageIndex: titleStackViews.arrangedSubviews.firstIndex(of: sender)!, animated: true) } func refreshTitleSpace() { if titles.count == 0 { return } let calculativeWidth: (String) -> CGFloat = { str in return str.boundingRect(with: CGSize.init(width: 999999, height: self.titleFont.pointSize), options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedString.Key.font: self.titleFont], context: nil).size.width //ld?? } var len = calculativeWidth({ () -> String in var str = String() for title in self.titles { str.append(title) } return str }()) // dPrint("总的宽度: \(len)") len += CGFloat(titles.count) // 这里每个title加上1是因为button的宽度和string的宽度有不到一的差距 var flag = true let averageWith = (width - space * CGFloat(titles.count)) / CGFloat(titles.count) // 平均宽度 for title in self.titles { let length = calculativeWidth(title) if length > averageWith { flag = false break } } if len + space * CGFloat(titles.count) < self.width && flag { titleStackViewLeftConstraints.constant = space/2 titleStackViewRightConstraints.constant = space/2 titleStackViewWidthConstraints.constant = self.width - space titleStackViews.distribution = .fillEqually titleStackViews.spacing = space } else { titleStackViews.distribution = .fill titleStackViewWidthConstraints.constant = len + space * CGFloat((titles.count - 1)) titleStackViewLeftConstraints.constant = space/2 titleStackViewRightConstraints.constant = space/2 titleStackViews.spacing = space } } //MARK: - UIScrollViewDelegate func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { if (scrollView == contentScrollViews) { let i = (Int)(targetContentOffset.pointee.x / scrollView.width) self.setPageIndex(pageIndex: i, animated: true) } } func scrollViewDidScroll(_ scrollView: UIScrollView) { } override init(frame: CGRect) { super.init(frame: frame) initialFromXib() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) initialFromXib() } func initialFromXib() { let bundle = Bundle.init(for: self.classForCoder) let nib = UINib(nibName: "SlideSegmentView", bundle: bundle) contentView = nib.instantiate(withOwner: self, options: nil)[0] as? UIView contentView.frame = bounds addSubview(contentView) } } //MARK: -String 扩展 extension String{ func width() -> (UIFont,CGFloat)->CGFloat { return { (font:UIFont,height:CGFloat) -> CGFloat in if (self as NSString).length == 0 { return 0 } return self.boundingRect(with: CGSize.init(width: 0, height: height), options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [NSAttributedString.Key.font:font], context: nil).size.width } } }