SlideSegmentView.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. //
  2. // SlideSegmentView.swift
  3. // swift翻译
  4. //
  5. // Created by zhongyuan on 2018/3/20.
  6. // Copyright © 2018年 zhongyuan. All rights reserved.
  7. //
  8. import UIKit
  9. import SnapKit
  10. @IBDesignable
  11. class SlideSegmentView: UIView, UIScrollViewDelegate {
  12. /// MARK: -- 属性
  13. @IBOutlet weak var titleScrollViews: UIScrollView!
  14. @IBOutlet weak var titleStackViews: UIStackView!
  15. @IBOutlet weak var contentScrollViews: UIScrollView!
  16. @IBOutlet weak var contentStackViews: UIStackView!
  17. @IBOutlet weak var titleStackViewWidthConstraints: NSLayoutConstraint!
  18. @IBOutlet weak var titleStackViewLeftConstraints: NSLayoutConstraint!
  19. @IBOutlet weak var titleStackViewRightConstraints: NSLayoutConstraint!
  20. @IBOutlet weak var UnderlineView: UIView!
  21. @IBOutlet weak var contentView: UIView!
  22. @IBOutlet weak var contentStackWidth: NSLayoutConstraint!
  23. var titleColorNormal:UIColor = UIColor.orange//Color_SlideSegmentView_titleColorNormal
  24. var titleColorSelected:UIColor = UIColor.orange//Color_SlideSegmentView_titleColorSelected
  25. var titleFont:UIFont = UIFont.systemFont(ofSize: 15)
  26. var underlineViewBgColor:UIColor = UIColor.orange //Color_family_normal
  27. var titleViews:[UIView] = [UIView]()
  28. var curPageIndex = 0
  29. var titles:Array<String> = [] /**< 标签标题 */
  30. {
  31. didSet{
  32. for subview:UIView in titleStackViews.arrangedSubviews {
  33. titleStackViews.removeArrangedSubview(subview)
  34. }
  35. for subview:UIView in contentStackViews.arrangedSubviews {
  36. contentStackViews.removeArrangedSubview(subview)
  37. }
  38. titleScrollViews.contentOffset = CGPoint.zero
  39. contentScrollViews.contentOffset = CGPoint.zero
  40. for title in titles {
  41. let btn:UIButton = UIButton.init(type: .custom)
  42. btn.titleLabel?.font = titleFont
  43. btn.setTitleColor(titleColorNormal, for: .normal)
  44. btn.setTitleColor(titleColorSelected, for: .selected)
  45. btn.setTitle(title, for: .normal)
  46. btn.sizeToFit()
  47. btn.addTarget(self, action:#selector(onTitleButtomClick( sender: )), for: .touchUpInside)
  48. titleStackViews.addArrangedSubview(btn)
  49. }
  50. self.refreshTitleSpace()
  51. contentScrollViews.contentSize = CGSize(width: width * CGFloat(titles.count), height: contentScrollViews.superview!.height - contentScrollViews.y)
  52. contentStackWidth.constant = CGFloat(titles.count) * width
  53. for _ in 0..<titles.count {
  54. let subView = UIView()
  55. subView.backgroundColor = UIColor.clear
  56. contentStackViews.addArrangedSubview(subView)
  57. }
  58. titleViews = []
  59. for btn in titleStackViews.arrangedSubviews {
  60. titleViews.append(btn)
  61. }
  62. pageIndex = 0
  63. }
  64. }
  65. /**< 配置内容视图 */
  66. var contentViews:Array<UIView> = []
  67. {
  68. didSet{
  69. let count = contentViews.count
  70. if count != viewControllers.count {
  71. viewControllers = []
  72. }else {
  73. for i in 0..<count{
  74. if viewControllers[i].view != contentViews[i]{
  75. viewControllers = []
  76. }
  77. }
  78. }
  79. for i in 0..<count {
  80. let contentView:UIView = contentViews[i]
  81. let superview:UIView = contentStackViews.arrangedSubviews[i]
  82. superview.addSubview(contentView)
  83. contentView.snp.makeConstraints({ (mark) in
  84. mark.edges.equalTo(superview)
  85. })
  86. }
  87. }
  88. }
  89. /**< 配置内容视图控制器 */
  90. var viewControllers: Array<UIViewController> = []
  91. {
  92. didSet{
  93. if(viewControllers.count > 0){
  94. var views:Array<UIView> = []
  95. for vc in viewControllers {
  96. views.append(vc.view)
  97. }
  98. self.contentViews = views
  99. }
  100. }
  101. }
  102. var didScrollToIndex:((Int)->())? /**< 滚动到第index页时回调 */
  103. var space:CGFloat = 30 /**< 各标题的间隔 */
  104. var pageIndex:Int /**< 当前页index */
  105. {
  106. set{
  107. self.setPageIndex(pageIndex: newValue , animated: false)
  108. }
  109. get{
  110. for titleBtn in titleStackViews.arrangedSubviews
  111. {
  112. if (titleBtn as! UIButton).isSelected
  113. {
  114. return titleStackViews.arrangedSubviews.firstIndex(of: titleBtn)!
  115. }
  116. }
  117. return 0
  118. }
  119. }
  120. func setPageIndex(pageIndex:Int,animated:Bool){
  121. self.layoutIfNeeded()
  122. if titleStackViews.arrangedSubviews.count <= pageIndex {
  123. return
  124. }
  125. for titleBtn in titleStackViews.arrangedSubviews {
  126. (titleBtn as! UIButton).isSelected = false
  127. }
  128. let sender:UIButton = titleStackViews.arrangedSubviews[pageIndex] as! UIButton
  129. sender.isSelected = true
  130. // 滚动标题栏、内容栏
  131. let count = self.titleViews.count
  132. let startX = self.titleViews[pageIndex].frame.origin.x
  133. let isRight = (pageIndex - self.curPageIndex) > 0
  134. let isLeft = (pageIndex - self.curPageIndex) < 0
  135. let width = self.titleViews[pageIndex].width + self.space
  136. let endX = startX + width
  137. let x = self.width * CGFloat(pageIndex)
  138. let _animated = abs(contentScrollViews.contentOffset.x - x) <= self.width
  139. contentScrollViews.setContentOffset(CGPoint(x: x, y: 0), animated: animated && _animated)
  140. //向左滑,且左边有未显示的部分
  141. if isLeft,self.titleScrollViews.contentOffset.x > 0 {
  142. //若当前单元格的前一个单元格已到最左边或之前,则向右滑
  143. if (startX - self.titleScrollViews.contentOffset.x - width) < 10 {
  144. var offectX = (startX - width)
  145. offectX = max(offectX, 0)
  146. self.titleScrollViews.setContentOffset(CGPoint.init(x: offectX, y: 0), animated: animated)
  147. }
  148. }
  149. //向右滑,且右边有未显示的部分
  150. if isRight,(self.titleScrollViews.contentOffset.x + self.width) < self.titleViews[count - 1].right{
  151. //若当前单元格的后一个单元格已到最右边,则向左滑
  152. if (endX + width) > (self.width - 10) {
  153. var offectX = (endX + width - self.width)
  154. offectX = min(offectX, self.titleViews[count - 1].right - self.width + space)
  155. self.titleScrollViews.setContentOffset(CGPoint.init(x: offectX, y: 0), animated: animated)
  156. }
  157. }
  158. titleScrollViews.layoutIfNeeded()
  159. UIView.animate(withDuration: 0.25) {
  160. let strW:CGFloat = (self.titleStackViews.subviews[pageIndex] as! UIButton).width
  161. self.UnderlineView.snp.remakeConstraints({ (mark) in
  162. mark.bottom.equalTo(self.titleScrollViews)
  163. mark.centerX.equalTo(sender)
  164. mark.width.equalTo(strW)
  165. mark.height.equalTo(3)
  166. })
  167. self.titleScrollViews.layoutIfNeeded()
  168. }
  169. if didScrollToIndex != nil{
  170. didScrollToIndex!(pageIndex)
  171. }
  172. curPageIndex = pageIndex
  173. }
  174. override func awakeFromNib() {
  175. super.awakeFromNib()
  176. contentScrollViews.delegate = self
  177. UnderlineView.backgroundColor = underlineViewBgColor
  178. }
  179. override func layoutSubviews() {
  180. super.layoutSubviews()
  181. self.refreshTitleSpace()
  182. contentScrollViews.contentSize = CGSize.init(width: self.width * CGFloat(titles.count), height: contentScrollViews.superview!.height - contentScrollViews.y)
  183. contentStackWidth.constant = CGFloat(titles.count) * self.width
  184. }
  185. @objc func onTitleButtomClick(sender:UIButton) -> Void {
  186. self.setPageIndex(pageIndex: titleStackViews.arrangedSubviews.firstIndex(of: sender)!, animated: true)
  187. }
  188. func refreshTitleSpace() {
  189. if titles.count == 0 {
  190. return
  191. }
  192. let calculativeWidth: (String) -> CGFloat = { str in
  193. return str.boundingRect(with: CGSize.init(width: 999999,
  194. height: self.titleFont.pointSize),
  195. options: [.usesLineFragmentOrigin,
  196. .usesFontLeading],
  197. attributes: [NSAttributedString.Key.font: self.titleFont],
  198. context: nil).size.width //ld??
  199. }
  200. var len = calculativeWidth({ () -> String in
  201. var str = String()
  202. for title in self.titles {
  203. str.append(title)
  204. }
  205. return str
  206. }())
  207. // dPrint("总的宽度: \(len)")
  208. len += CGFloat(titles.count) // 这里每个title加上1是因为button的宽度和string的宽度有不到一的差距
  209. var flag = true
  210. let averageWith = (width - space * CGFloat(titles.count)) / CGFloat(titles.count) // 平均宽度
  211. for title in self.titles {
  212. let length = calculativeWidth(title)
  213. if length > averageWith {
  214. flag = false
  215. break
  216. }
  217. }
  218. if len + space * CGFloat(titles.count) < self.width && flag {
  219. titleStackViewLeftConstraints.constant = space/2
  220. titleStackViewRightConstraints.constant = space/2
  221. titleStackViewWidthConstraints.constant = self.width - space
  222. titleStackViews.distribution = .fillEqually
  223. titleStackViews.spacing = space
  224. } else {
  225. titleStackViews.distribution = .fill
  226. titleStackViewWidthConstraints.constant = len + space * CGFloat((titles.count - 1))
  227. titleStackViewLeftConstraints.constant = space/2
  228. titleStackViewRightConstraints.constant = space/2
  229. titleStackViews.spacing = space
  230. }
  231. }
  232. //MARK: - UIScrollViewDelegate
  233. func scrollViewWillEndDragging(_ scrollView: UIScrollView,
  234. withVelocity velocity: CGPoint,
  235. targetContentOffset: UnsafeMutablePointer<CGPoint>) {
  236. if (scrollView == contentScrollViews) {
  237. let i = (Int)(targetContentOffset.pointee.x / scrollView.width)
  238. self.setPageIndex(pageIndex: i, animated: true)
  239. }
  240. }
  241. func scrollViewDidScroll(_ scrollView: UIScrollView) {
  242. }
  243. override init(frame: CGRect) {
  244. super.init(frame: frame)
  245. initialFromXib()
  246. }
  247. required init?(coder aDecoder: NSCoder) {
  248. super.init(coder: aDecoder)
  249. initialFromXib()
  250. }
  251. func initialFromXib() {
  252. let bundle = Bundle.init(for: self.classForCoder)
  253. let nib = UINib(nibName: "SlideSegmentView", bundle: bundle)
  254. contentView = nib.instantiate(withOwner: self, options: nil)[0] as? UIView
  255. contentView.frame = bounds
  256. addSubview(contentView)
  257. }
  258. }
  259. //MARK: -String 扩展
  260. extension String{
  261. func width() -> (UIFont,CGFloat)->CGFloat {
  262. return { (font:UIFont,height:CGFloat) -> CGFloat in
  263. if (self as NSString).length == 0 {
  264. return 0
  265. }
  266. return self.boundingRect(with: CGSize.init(width: 0,
  267. height: height),
  268. options: [.usesLineFragmentOrigin,
  269. .usesFontLeading],
  270. attributes: [NSAttributedString.Key.font:font],
  271. context: nil).size.width
  272. }
  273. }
  274. }