123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512 |
- //
- // JXSegmentedListContainerView.swift
- // JXSegmentedView
- //
- // Created by jiaxin on 2018/12/26.
- // Copyright © 2018 jiaxin. All rights reserved.
- //
- import UIKit
- /// 列表容器视图的类型
- ///- ScrollView: UIScrollView。优势:没有其他副作用。劣势:视图内存占用相对大一点。因为所有的列表视图都在UIScrollView的视图层级里面。
- /// - CollectionView: 使用UICollectionView。优势:因为列表被添加到cell上,视图的内存占用更少,适合内存要求特别高的场景。劣势:因为cell重用机制的问题,导致列表下拉刷新视图(比如MJRefresh),会因为被removeFromSuperview而被隐藏。所以,列表有下拉刷新需求的,请使用scrollView type。
- public enum JXSegmentedListContainerType {
- case scrollView
- case collectionView
- }
- @objc
- public protocol JXSegmentedListContainerViewListDelegate {
- /// 如果列表是VC,就返回VC.view
- /// 如果列表是View,就返回View自己
- ///
- /// - Returns: 返回列表视图
- func listView() -> UIView
- @objc optional func listWillAppear()
- @objc optional func listDidAppear()
- @objc optional func listWillDisappear()
- @objc optional func listDidDisappear()
- }
- @objc
- public protocol JXSegmentedListContainerViewDataSource {
- /// 返回list的数量
- func numberOfLists(in listContainerView: JXSegmentedListContainerView) -> Int
- /// 根据index初始化一个对应列表实例,需要是遵从`JXSegmentedListContainerViewListDelegate`协议的对象。
- /// 如果列表是用自定义UIView封装的,就让自定义UIView遵从`JXSegmentedListContainerViewListDelegate`协议,该方法返回自定义UIView即可。
- /// 如果列表是用自定义UIViewController封装的,就让自定义UIViewController遵从`JXSegmentedListContainerViewListDelegate`协议,该方法返回自定义UIViewController即可。
- /// 注意:一定要是新生成的实例!!!
- ///
- /// - Parameters:
- /// - listContainerView: JXSegmentedListContainerView
- /// - index: 目标index
- /// - Returns: 遵从JXSegmentedListContainerViewListDelegate协议的实例
- func listContainerView(_ listContainerView: JXSegmentedListContainerView, initListAt index: Int) -> JXSegmentedListContainerViewListDelegate
- /// 控制能否初始化对应index的列表。有些业务需求,需要在某些情况才允许初始化某些列表,通过通过该代理实现控制。
- @objc optional func listContainerView(_ listContainerView: JXSegmentedListContainerView, canInitListAt index: Int) -> Bool
- /// 返回自定义UIScrollView或UICollectionView的Class
- /// 某些特殊情况需要自己处理UIScrollView内部逻辑。比如项目用了FDFullscreenPopGesture,需要处理手势相关代理。
- ///
- /// - Parameter listContainerView: JXSegmentedListContainerView
- /// - Returns: 自定义UIScrollView实例
- @objc optional func scrollViewClass(in listContainerView: JXSegmentedListContainerView) -> AnyClass
- }
- open class JXSegmentedListContainerView: UIView, JXSegmentedViewListContainer, JXSegmentedViewRTLCompatible {
- open private(set) var type: JXSegmentedListContainerType
- open private(set) weak var dataSource: JXSegmentedListContainerViewDataSource?
- open private(set) var scrollView: UIScrollView!
- /// 已经加载过的列表字典。key是index,value是对应的列表
- open private(set) var validListDict = [Int:JXSegmentedListContainerViewListDelegate]()
- /// 滚动切换的时候,滚动距离超过一页的多少百分比,就触发列表的初始化。默认0.01(即列表显示了一点就触发加载)。范围0~1,开区间不包括0和1
- open var initListPercent: CGFloat = 0.01 {
- didSet {
- if initListPercent <= 0 || initListPercent >= 1 {
- assertionFailure("initListPercent值范围为开区间(0,1),即不包括0和1")
- }
- }
- }
- open var listCellBackgroundColor: UIColor = .white
- /// 需要和segmentedView.defaultSelectedIndex保持一致,用于触发默认index列表的加载
- open var defaultSelectedIndex: Int = 0 {
- didSet {
- currentIndex = defaultSelectedIndex
- }
- }
- private var currentIndex: Int = 0
- private lazy var collectionView: UICollectionView = {
- let layout = UICollectionViewFlowLayout()
- layout.scrollDirection = .horizontal
- layout.minimumLineSpacing = 0
- layout.minimumInteritemSpacing = 0
- if let collectionViewClass = dataSource?.scrollViewClass?(in: self) as? UICollectionView.Type {
- return collectionViewClass.init(frame: CGRect.zero, collectionViewLayout: layout)
- }else {
- return UICollectionView.init(frame: CGRect.zero, collectionViewLayout: layout)
- }
- }()
- private lazy var containerVC = JXSegmentedListContainerViewController()
- private var willAppearIndex: Int = -1
- private var willDisappearIndex: Int = -1
- public init(dataSource: JXSegmentedListContainerViewDataSource, type: JXSegmentedListContainerType = .scrollView) {
- self.dataSource = dataSource
- self.type = type
- super.init(frame: CGRect.zero)
- commonInit()
- }
- required public init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
- open func commonInit() {
- containerVC.view.backgroundColor = .clear
- addSubview(containerVC.view)
- containerVC.viewWillAppearClosure = {[weak self] in
- self?.listWillAppear(at: self?.currentIndex ?? 0)
- }
- containerVC.viewDidAppearClosure = {[weak self] in
- self?.listDidAppear(at: self?.currentIndex ?? 0)
- }
- containerVC.viewWillDisappearClosure = {[weak self] in
- self?.listWillDisappear(at: self?.currentIndex ?? 0)
- }
- containerVC.viewDidDisappearClosure = {[weak self] in
- self?.listDidDisappear(at: self?.currentIndex ?? 0)
- }
- if type == .scrollView {
- if let scrollViewClass = dataSource?.scrollViewClass?(in: self) as? UIScrollView.Type {
- scrollView = scrollViewClass.init()
- }else {
- scrollView = UIScrollView.init()
- }
- scrollView.delegate = self
- scrollView.isPagingEnabled = true
- scrollView.showsVerticalScrollIndicator = false
- scrollView.showsHorizontalScrollIndicator = false
- scrollView.scrollsToTop = false
- scrollView.bounces = false
- if #available(iOS 11.0, *) {
- scrollView.contentInsetAdjustmentBehavior = .never
- }
- if segmentedViewShouldRTLLayout() {
- segmentedView(horizontalFlipForView: scrollView)
- }
- containerVC.view.addSubview(scrollView)
- }else if type == .collectionView {
- collectionView.isPagingEnabled = true
- collectionView.showsHorizontalScrollIndicator = false
- collectionView.showsVerticalScrollIndicator = false
- collectionView.scrollsToTop = false
- collectionView.bounces = false
- collectionView.dataSource = self
- collectionView.delegate = self
- collectionView.register(JXSegmentedRTLCollectionCell.self, forCellWithReuseIdentifier: "cell")
- if #available(iOS 10.0, *) {
- collectionView.isPrefetchingEnabled = false
- }
- if #available(iOS 11.0, *) {
- self.collectionView.contentInsetAdjustmentBehavior = .never
- }
- if segmentedViewShouldRTLLayout() {
- collectionView.semanticContentAttribute = .forceLeftToRight
- segmentedView(horizontalFlipForView: collectionView)
- }
- containerVC.view.addSubview(collectionView)
- //让外部统一访问scrollView
- scrollView = collectionView
- }
- }
- open override func willMove(toSuperview newSuperview: UIView?) {
- super.willMove(toSuperview: newSuperview)
- var next: UIResponder? = newSuperview
- while next != nil {
- if let vc = next as? UIViewController{
- vc.addChild(containerVC)
- break
- }
- next = next?.next
- }
- }
- open override func layoutSubviews() {
- super.layoutSubviews()
- containerVC.view.frame = bounds
- guard let count = dataSource?.numberOfLists(in: self) else {
- return
- }
- if type == .scrollView {
- if scrollView.frame == CGRect.zero || scrollView.bounds.size != bounds.size {
- scrollView.frame = bounds
- scrollView.contentSize = CGSize(width: scrollView.bounds.size.width*CGFloat(count), height: scrollView.bounds.size.height)
- for (index, list) in validListDict {
- list.listView().frame = CGRect(x: CGFloat(index)*scrollView.bounds.size.width, y: 0, width: scrollView.bounds.size.width, height: scrollView.bounds.size.height)
- }
- scrollView.contentOffset = CGPoint(x: CGFloat(currentIndex)*scrollView.bounds.size.width, y: 0)
- }else {
- scrollView.frame = bounds
- scrollView.contentSize = CGSize(width: scrollView.bounds.size.width*CGFloat(count), height: scrollView.bounds.size.height)
- }
- }else {
- if collectionView.frame == CGRect.zero || collectionView.bounds.size != bounds.size {
- collectionView.frame = bounds
- collectionView.collectionViewLayout.invalidateLayout()
- collectionView.setContentOffset(CGPoint(x: CGFloat(currentIndex)*collectionView.bounds.size.width, y: 0), animated: false)
- }else {
- collectionView.frame = bounds
- }
- }
- }
- //MARK: - JXSegmentedViewListContainer
- public func contentScrollView() -> UIScrollView {
- return scrollView
- }
- public func scrolling(from leftIndex: Int, to rightIndex: Int, percent: CGFloat, selectedIndex: Int) {
- }
- open func didClickSelectedItem(at index: Int) {
- guard checkIndexValid(index) else {
- return
- }
- willAppearIndex = -1
- willDisappearIndex = -1
- if currentIndex != index {
- listWillDisappear(at: currentIndex)
- listWillAppear(at: index)
- listDidDisappear(at: currentIndex)
- listDidAppear(at: index)
- }
- }
- open func reloadData() {
- guard let dataSource = dataSource else { return }
- if currentIndex < 0 || currentIndex >= dataSource.numberOfLists(in: self) {
- defaultSelectedIndex = 0
- currentIndex = 0
- }
- validListDict.values.forEach { (list) in
- if let listVC = list as? UIViewController {
- listVC.removeFromParent()
- }
- list.listView().removeFromSuperview()
- }
- validListDict.removeAll()
- if type == .scrollView {
- scrollView.contentSize = CGSize(width: scrollView.bounds.size.width*CGFloat(dataSource.numberOfLists(in: self)), height: scrollView.bounds.size.height)
- }else {
- collectionView.reloadData()
- }
- listWillAppear(at: currentIndex)
- listDidAppear(at: currentIndex)
- }
- //MARK: - Private
- func initListIfNeeded(at index: Int) {
- guard let dataSource = dataSource else { return }
- if dataSource.listContainerView?(self, canInitListAt: index) == false {
- return
- }
- var existedList = validListDict[index]
- if existedList != nil {
- //列表已经创建好了
- return
- }
- existedList = dataSource.listContainerView(self, initListAt: index)
- guard let list = existedList else {
- return
- }
- if let vc = list as? UIViewController {
- containerVC.addChild(vc)
- }
- validListDict[index] = list
- if type == .scrollView {
- list.listView().frame = CGRect(x: CGFloat(index)*scrollView.bounds.size.width, y: 0, width: scrollView.bounds.size.width, height: scrollView.bounds.size.height)
- scrollView.addSubview(list.listView())
-
- if segmentedViewShouldRTLLayout() {
- segmentedView(horizontalFlipForView: list.listView())
- }
- }else {
- let cell = collectionView.cellForItem(at: IndexPath(item: index, section: 0))
- cell?.contentView.subviews.forEach { $0.removeFromSuperview() }
- list.listView().frame = cell?.contentView.bounds ?? CGRect.zero
- cell?.contentView.addSubview(list.listView())
- }
- }
- private func listWillAppear(at index: Int) {
- guard let dataSource = dataSource else { return }
- guard checkIndexValid(index) else {
- return
- }
- var existedList = validListDict[index]
- if existedList != nil {
- existedList?.listWillAppear?()
- if let vc = existedList as? UIViewController {
- vc.beginAppearanceTransition(true, animated: false)
- }
- }else {
- //当前列表未被创建(页面初始化或通过点击触发的listWillAppear)
- guard dataSource.listContainerView?(self, canInitListAt: index) != false else {
- return
- }
- existedList = dataSource.listContainerView(self, initListAt: index)
- guard let list = existedList else {
- return
- }
- if let vc = list as? UIViewController {
- containerVC.addChild(vc)
- }
- validListDict[index] = list
- if type == .scrollView {
- if list.listView().superview == nil {
- list.listView().frame = CGRect(x: CGFloat(index)*scrollView.bounds.size.width, y: 0, width: scrollView.bounds.size.width, height: scrollView.bounds.size.height)
- scrollView.addSubview(list.listView())
-
- if segmentedViewShouldRTLLayout() {
- segmentedView(horizontalFlipForView: list.listView())
- }
- }
- list.listWillAppear?()
- if let vc = list as? UIViewController {
- vc.beginAppearanceTransition(true, animated: false)
- }
- }else {
- let cell = collectionView.cellForItem(at: IndexPath(item: index, section: 0))
- cell?.contentView.subviews.forEach { $0.removeFromSuperview() }
- list.listView().frame = cell?.contentView.bounds ?? CGRect.zero
- cell?.contentView.addSubview(list.listView())
- list.listWillAppear?()
- if let vc = list as? UIViewController {
- vc.beginAppearanceTransition(true, animated: false)
- }
- }
- }
- }
- private func listDidAppear(at index: Int) {
- guard checkIndexValid(index) else {
- return
- }
- currentIndex = index
- let list = validListDict[index]
- list?.listDidAppear?()
- if let vc = list as? UIViewController {
- vc.endAppearanceTransition()
- }
- }
- private func listWillDisappear(at index: Int) {
- guard checkIndexValid(index) else {
- return
- }
- let list = validListDict[index]
- list?.listWillDisappear?()
- if let vc = list as? UIViewController {
- vc.beginAppearanceTransition(false, animated: false)
- }
- }
- private func listDidDisappear(at index: Int) {
- guard checkIndexValid(index) else {
- return
- }
- let list = validListDict[index]
- list?.listDidDisappear?()
- if let vc = list as? UIViewController {
- vc.endAppearanceTransition()
- }
- }
- private func checkIndexValid(_ index: Int) -> Bool {
- guard let dataSource = dataSource else { return false }
- let count = dataSource.numberOfLists(in: self)
- if count <= 0 || index >= count {
- return false
- }
- return true
- }
- private func listDidAppearOrDisappear(scrollView: UIScrollView) {
- let currentIndexPercent = scrollView.contentOffset.x/scrollView.bounds.size.width
- if willAppearIndex != -1 || willDisappearIndex != -1 {
- let disappearIndex = willDisappearIndex
- let appearIndex = willAppearIndex
- if willAppearIndex > willDisappearIndex {
- //将要出现的列表在右边
- if currentIndexPercent >= CGFloat(willAppearIndex) {
- willDisappearIndex = -1
- willAppearIndex = -1
- listDidDisappear(at: disappearIndex)
- listDidAppear(at: appearIndex)
- }
- }else {
- //将要出现的列表在左边
- if currentIndexPercent <= CGFloat(willAppearIndex) {
- willDisappearIndex = -1
- willAppearIndex = -1
- listDidDisappear(at: disappearIndex)
- listDidAppear(at: appearIndex)
- }
- }
- }
- }
- }
- extension JXSegmentedListContainerView: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
- public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
- guard let dataSource = dataSource else { return 0 }
- return dataSource.numberOfLists(in: self)
- }
- public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
- let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
- cell.contentView.backgroundColor = listCellBackgroundColor
- cell.contentView.subviews.forEach { $0.removeFromSuperview() }
- let list = validListDict[indexPath.item]
- if list != nil {
- list?.listView().frame = cell.contentView.bounds
- cell.contentView.addSubview(list!.listView())
- }
- return cell
- }
- public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
- return bounds.size
- }
- public func scrollViewDidScroll(_ scrollView: UIScrollView) {
- guard scrollView.isTracking || scrollView.isDragging || scrollView.isDecelerating else {
- return
- }
- let percent = scrollView.contentOffset.x/scrollView.bounds.size.width
- let maxCount = Int(round(scrollView.contentSize.width/scrollView.bounds.size.width))
- var leftIndex = Int(floor(Double(percent)))
- leftIndex = max(0, min(maxCount - 1, leftIndex))
- let rightIndex = leftIndex + 1;
- if percent < 0 || rightIndex >= maxCount {
- listDidAppearOrDisappear(scrollView: scrollView)
- return
- }
- let remainderRatio = percent - CGFloat(leftIndex)
- if rightIndex == currentIndex {
- //当前选中的在右边,用户正在从右边往左边滑动
- if validListDict[leftIndex] == nil && remainderRatio < (1 - initListPercent) {
- initListIfNeeded(at: leftIndex)
- }else if validListDict[leftIndex] != nil {
- if willAppearIndex == -1 {
- willAppearIndex = leftIndex;
- listWillAppear(at: willAppearIndex)
- }
- }
- if willDisappearIndex == -1 {
- willDisappearIndex = rightIndex
- listWillDisappear(at: willDisappearIndex)
- }
- }else {
- //当前选中的在左边,用户正在从左边往右边滑动
- if validListDict[rightIndex] == nil && remainderRatio > initListPercent {
- initListIfNeeded(at: rightIndex)
- }else if validListDict[rightIndex] != nil {
- if willAppearIndex == -1 {
- willAppearIndex = rightIndex
- listWillAppear(at: willAppearIndex)
- }
- }
- if willDisappearIndex == -1 {
- willDisappearIndex = leftIndex
- listWillDisappear(at: willDisappearIndex)
- }
- }
- listDidAppearOrDisappear(scrollView: scrollView)
- }
- public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
- //滑动到一半又取消滑动处理
- if willAppearIndex != -1 && willDisappearIndex != -1 {
- listWillDisappear(at: willAppearIndex)
- listWillAppear(at: willDisappearIndex)
- listDidDisappear(at: willAppearIndex)
- listDidAppear(at: willDisappearIndex)
- willDisappearIndex = -1
- willAppearIndex = -1
- }
- }
- }
- class JXSegmentedListContainerViewController: UIViewController {
- var viewWillAppearClosure: (()->())?
- var viewDidAppearClosure: (()->())?
- var viewWillDisappearClosure: (()->())?
- var viewDidDisappearClosure: (()->())?
- override var shouldAutomaticallyForwardAppearanceMethods: Bool { return false }
- override func viewWillAppear(_ animated: Bool) {
- super.viewWillAppear(animated)
- viewWillAppearClosure?()
- }
- override func viewDidAppear(_ animated: Bool) {
- super.viewDidAppear(animated)
- viewDidAppearClosure?()
- }
- override func viewWillDisappear(_ animated: Bool) {
- super.viewWillDisappear(animated)
- viewWillDisappearClosure?()
- }
- override func viewDidDisappear(_ animated: Bool) {
- super.viewDidDisappear(animated)
- viewDidDisappearClosure?()
- }
- }
|