|
@@ -8,46 +8,68 @@
|
|
|
|
|
|
import UIKit
|
|
|
|
|
|
+let verticalListCategoryViewHeight = 30; //悬浮categoryView的高度
|
|
|
+let verticalListPinSectionIndex = 1; //悬浮固定section的index
|
|
|
+
|
|
|
class ShoppingMallView: BaseView {
|
|
|
|
|
|
|
|
|
typealias CategoryActionBlock = () -> Void
|
|
|
var categoryActionBlock : CategoryActionBlock?
|
|
|
|
|
|
- let categoryArray = ["今日爆款","限量秒杀","萌娃专区萌娃专区","乳饮烘焙","新鲜水果","海鲜水产","方便食品","生活服务","休息食品","美妆日化","家用电器","萌娃专区萌娃专区","乳饮烘焙","新鲜水果","海鲜水产","方便食品","生活服务","休息食品","美妆日化","家用电器"]
|
|
|
+ var dataSource = Array<VerticalListSectionModel>()
|
|
|
+
|
|
|
+ var sectionHeaderAttributes = Array<UICollectionViewLayoutAttributes>()
|
|
|
|
|
|
- fileprivate var selectIndex = 0
|
|
|
- fileprivate var isScrollDown = true
|
|
|
- fileprivate var lastOffsetY : CGFloat = 0.0
|
|
|
+ let headerTitles = ["我的频道", "超级大IP", "热门HOT", "周边衍生", "影视综", "游戏集锦", "搞笑百事"];
|
|
|
+ var imageNames = ["boat", "crab", "lobster", "apple", "carrot", "grape", "watermelon"];
|
|
|
|
|
|
override func setupViews() {
|
|
|
super.setupViews()
|
|
|
+ for (index, title) in headerTitles.enumerated() {
|
|
|
+ let sectionModel = VerticalListSectionModel()
|
|
|
+ sectionModel.sectionTitle = title
|
|
|
+ let randomCount = arc4random()%10 + 5
|
|
|
+ var cellModels = Array<VerticalListCellModel>()
|
|
|
+
|
|
|
+ for _ in 0..<randomCount {
|
|
|
+ let cellModel = VerticalListCellModel()
|
|
|
+ cellModel.imageName = imageNames[index]
|
|
|
+ cellModel.itemName = title
|
|
|
+ cellModels.append(cellModel)
|
|
|
+ }
|
|
|
+ sectionModel.cellModels = cellModels
|
|
|
+ dataSource.append(sectionModel)
|
|
|
+ }
|
|
|
addSubview(categoryButton)
|
|
|
- addSubview(categorycollectionView)
|
|
|
+ addSubview(pinCategoryView)
|
|
|
addSubview(allCollectionView)
|
|
|
|
|
|
+
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+
|
|
|
override func setupLayouts() {
|
|
|
categoryButton.snp.makeConstraints { (make) in
|
|
|
make.top.equalToSuperview().offset(2)
|
|
|
make.right.equalToSuperview().offset(-10)
|
|
|
make.size.equalTo(30)
|
|
|
}
|
|
|
- categorycollectionView.snp.makeConstraints { (make) in
|
|
|
+ pinCategoryView.snp.makeConstraints { (make) in
|
|
|
make.top.equalToSuperview().offset(2)
|
|
|
make.right.equalTo(categoryButton.snp.left)
|
|
|
make.left.equalToSuperview().offset(10)
|
|
|
make.height.equalTo(30)
|
|
|
}
|
|
|
allCollectionView.snp.makeConstraints { (make) in
|
|
|
- make.top.equalTo(categorycollectionView.snp.bottom)
|
|
|
+ make.top.equalTo(pinCategoryView.snp.bottom)
|
|
|
make.left.right.bottom.equalToSuperview()
|
|
|
}
|
|
|
- categorycollectionView.selectItem(at: IndexPath(item: 0, section: 0), animated: false, scrollPosition: UICollectionView.ScrollPosition.top)
|
|
|
|
|
|
}
|
|
|
|
|
|
+
|
|
|
private lazy var categoryButton: UIButton = {
|
|
|
let categoryButton = UIButton(type: UIButton.ButtonType.custom)
|
|
|
categoryButton.setImage(kImage(name: "shop"), for: UIControl.State.normal)
|
|
@@ -60,14 +82,18 @@ class ShoppingMallView: BaseView {
|
|
|
return categoryButton
|
|
|
}()
|
|
|
|
|
|
- private lazy var categorycollectionView: UICollectionView = {
|
|
|
- let categorycollectionView = UICollectionView.init(frame: CGRect.zero, collectionViewLayout: categoryCollectionViewLayout)
|
|
|
- categorycollectionView.backgroundColor = UIColor.white;
|
|
|
- categorycollectionView.showsVerticalScrollIndicator = false
|
|
|
- categorycollectionView.showsHorizontalScrollIndicator = false
|
|
|
- categorycollectionView.delegate = self;
|
|
|
- categorycollectionView.dataSource = self;
|
|
|
- return categorycollectionView
|
|
|
+ private lazy var pinCategoryView: JXCategoryTitleView = {
|
|
|
+ let pinCategoryView = JXCategoryTitleView()
|
|
|
+ pinCategoryView.backgroundColor = UIColor.white
|
|
|
+ pinCategoryView.titles = headerTitles
|
|
|
+ pinCategoryView.titleColorGradientEnabled = true
|
|
|
+ pinCategoryView.titleLabelZoomEnabled = true
|
|
|
+ pinCategoryView.titleLabelZoomScale = 1.2
|
|
|
+ let lineView = JXCategoryIndicatorLineView()
|
|
|
+ lineView.lineStyle = .lengthen
|
|
|
+ pinCategoryView.indicators = [lineView];
|
|
|
+ pinCategoryView.delegate = self
|
|
|
+ return pinCategoryView
|
|
|
}()
|
|
|
|
|
|
private lazy var categoryCollectionViewLayout: UICollectionViewFlowLayout = {
|
|
@@ -89,133 +115,145 @@ class ShoppingMallView: BaseView {
|
|
|
let allCollectionViewLayout = UICollectionViewFlowLayout.init()
|
|
|
allCollectionViewLayout.scrollDirection = UICollectionView.ScrollDirection.vertical
|
|
|
//分组头悬停
|
|
|
- allCollectionViewLayout.sectionHeadersPinToVisibleBounds = true
|
|
|
+// allCollectionViewLayout.sectionHeadersPinToVisibleBounds = true
|
|
|
allCollectionViewLayout.itemSize = CGSize(width: kScreenWidth, height:30)
|
|
|
return allCollectionViewLayout
|
|
|
}()
|
|
|
|
|
|
}
|
|
|
|
|
|
-extension ShoppingMallView : UICollectionViewDelegateFlowLayout,UICollectionViewDataSource {
|
|
|
+extension ShoppingMallView : UICollectionViewDelegate,UICollectionViewDataSource {
|
|
|
+
|
|
|
func numberOfSections(in collectionView: UICollectionView) -> Int {
|
|
|
- if collectionView == categorycollectionView {
|
|
|
- return 1
|
|
|
- }else {
|
|
|
- return categoryArray.count
|
|
|
- }
|
|
|
+ return dataSource.count
|
|
|
}
|
|
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
|
|
-
|
|
|
- if collectionView == categorycollectionView {
|
|
|
- return categoryArray.count
|
|
|
- }else {
|
|
|
- return categoryArray.count
|
|
|
- }
|
|
|
+ return self.dataSource[section].cellModels!.count;
|
|
|
}
|
|
|
|
|
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
|
|
- if collectionView == categorycollectionView {
|
|
|
- let cell = ShoppingMallCategoryCollectionViewCell.cellWith(collectionView: collectionView, indexPath: indexPath)
|
|
|
- cell.categoryArray = categoryArray
|
|
|
- return cell
|
|
|
- }else {
|
|
|
- let cell = ShoppingMallCategoryCollectionViewCell.cellWith(collectionView: collectionView, indexPath: indexPath)
|
|
|
- cell.categoryArray = categoryArray
|
|
|
- return cell
|
|
|
- }
|
|
|
+ let cell = VerticalListCellCollectionViewCell.cellWith(collectionView: collectionView, indexPath: indexPath)
|
|
|
+ let sectionModel = dataSource[indexPath.section]
|
|
|
+ let cellModel: VerticalListCellModel? = sectionModel.cellModels![indexPath.row]
|
|
|
+ cell.itemImageView.image = UIImage(named: cellModel?.imageName ?? "")
|
|
|
+ cell.titleLabel.text = cellModel?.itemName
|
|
|
+ return cell
|
|
|
}
|
|
|
|
|
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
|
|
- if collectionView == categorycollectionView {
|
|
|
- selectIndex = indexPath.row
|
|
|
- //右侧collection自动滚动到对应的分区
|
|
|
- collectionViewScrollToTop(section: selectIndex, animated: true)
|
|
|
- //左侧tableView将该单元格滚动到顶部
|
|
|
- categorycollectionView.scrollToItem(at: IndexPath(row: selectIndex, section: 0), at: .centeredHorizontally, animated: true)
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
- //将右侧colletionView的指定分区自动滚动到最顶端
|
|
|
- func collectionViewScrollToTop(section: Int, animated: Bool) {
|
|
|
- let headerRect = collectionViewHeaderFrame(section: section)
|
|
|
- let topOfHeader = CGPoint(x: 0, y: headerRect.origin.y
|
|
|
- - allCollectionView.contentInset.top)
|
|
|
- allCollectionView.setContentOffset(topOfHeader, animated: animated)
|
|
|
}
|
|
|
|
|
|
- //后获colletionView的指定分区头的高度
|
|
|
- func collectionViewHeaderFrame(section: Int) -> CGRect {
|
|
|
- let indexPath = IndexPath(item: 0, section: section)
|
|
|
- let attributes = allCollectionView.collectionViewLayout
|
|
|
- .layoutAttributesForSupplementaryView(ofKind:
|
|
|
- UICollectionView.elementKindSectionHeader, at: indexPath)
|
|
|
- guard let frameForFirstCell = attributes?.frame else {
|
|
|
- return .zero
|
|
|
+ func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
|
|
|
+ if kind == UICollectionView.elementKindSectionHeader {
|
|
|
+
|
|
|
+ let headerView = VerticalSectionHeaderView.headerWith(collectionView: collectionView, kind: UICollectionView.elementKindSectionHeader, indexPath: indexPath)
|
|
|
+ let sectionModel = dataSource[indexPath.section]
|
|
|
+ headerView.titleLabel.text = sectionModel.sectionTitle
|
|
|
+ return headerView
|
|
|
}
|
|
|
- return frameForFirstCell;
|
|
|
+ return UICollectionReusableView()
|
|
|
}
|
|
|
|
|
|
+ // 标记一下 CollectionView 的滚动方向,是向上还是向下
|
|
|
+ func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
|
|
|
|
|
- func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
|
|
|
- if collectionView == categorycollectionView {
|
|
|
- let shoppingMallHeaderCollectionReusableView = ShoppingMallHeaderCollectionReusableView.headerWith(collectionView: collectionView, kind: UICollectionView.elementKindSectionHeader, indexPath: indexPath)
|
|
|
- shoppingMallHeaderCollectionReusableView.categoryArray = categoryArray
|
|
|
- return shoppingMallHeaderCollectionReusableView
|
|
|
- }else {
|
|
|
- if kind == UICollectionView.elementKindSectionHeader {
|
|
|
- let shoppingMallHeaderCollectionReusableView = ShoppingMallHeaderCollectionReusableView.headerWith(collectionView: collectionView, kind: UICollectionView.elementKindSectionHeader, indexPath: indexPath)
|
|
|
- shoppingMallHeaderCollectionReusableView.categoryArray = categoryArray
|
|
|
- return shoppingMallHeaderCollectionReusableView
|
|
|
- }else {
|
|
|
- return UICollectionReusableView()
|
|
|
+ if sectionHeaderAttributes.count <= 0 {
|
|
|
+ var attributes: [AnyHashable] = []
|
|
|
+ var lastHeaderAttri: UICollectionViewLayoutAttributes? = nil
|
|
|
+ for i in 0..<headerTitles.count {
|
|
|
+ let attri: UICollectionViewLayoutAttributes? = allCollectionView.collectionViewLayout.layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: i))
|
|
|
+ if let attri = attri {
|
|
|
+ attributes.append(attri)
|
|
|
+ }
|
|
|
+ if i == headerTitles.count - 1 {
|
|
|
+ lastHeaderAttri = attri
|
|
|
+ }
|
|
|
+ }
|
|
|
+ sectionHeaderAttributes = attributes as! [UICollectionViewLayoutAttributes]
|
|
|
+
|
|
|
+ //如果最后一个section条目太少了,会导致滚动最底部,但是却不能触发categoryView选中最后一个item。而且点击最后一个滚动的contentOffset.y也不要弄。所以添加contentInset,让最后一个section滚到最下面能显示完整个屏幕,
|
|
|
+
|
|
|
+ let lastCellAttri: UICollectionViewLayoutAttributes? = allCollectionView.collectionViewLayout.layoutAttributesForItem(at: IndexPath(item: (dataSource[headerTitles.count - 1]).cellModels!.count - 1, section: headerTitles.count - 1))
|
|
|
+ let lastSectionHeight: CGFloat = (lastCellAttri?.frame.maxY)! - (lastHeaderAttri?.frame.minY)!
|
|
|
+
|
|
|
+ let value = CGFloat(bounds.size.height - CGFloat(verticalListCategoryViewHeight)) - lastSectionHeight
|
|
|
+ if value > 0 {
|
|
|
+ allCollectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: value, right: 0)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- }
|
|
|
|
|
|
- func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
|
|
|
- if collectionView == categorycollectionView {
|
|
|
- return CGSize(width: 0, height: 0)
|
|
|
- }else {
|
|
|
- return CGSize(width: kScreenWidth, height: 30)
|
|
|
+ if (!(scrollView.isTracking || scrollView.isDecelerating)) {
|
|
|
+ //不是用户滚动的,比如setContentOffset等方法,引起的滚动不需要处理。
|
|
|
+ return;
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- // CollectionView 分区标题即将展示
|
|
|
- func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath) {
|
|
|
- // 当前 CollectionView 滚动的方向向上,CollectionView 是用户拖拽而产生滚动的(主要是判断 CollectionView 是用户拖拽而滚动的,还是点击 TableView 而滚动的)
|
|
|
- if collectionView == allCollectionView {
|
|
|
- if !isScrollDown && (collectionView.isDragging || collectionView.isDecelerating) {
|
|
|
- selectRow(index: indexPath.section)
|
|
|
+ //用户滚动的才处理
|
|
|
+ //获取categoryView下面一点的所有布局信息,用于知道,当前最上方是显示的哪个section
|
|
|
+ let topRect = CGRect(x: 0, y: scrollView.contentOffset.y + 1, width: bounds.size.width, height: 1)
|
|
|
+ let topAttributes: UICollectionViewLayoutAttributes? = allCollectionView.collectionViewLayout.layoutAttributesForElements(in: topRect)?.first
|
|
|
+ let topSection: Int? = topAttributes?.indexPath.section
|
|
|
+ if topAttributes != nil && topSection! >= 0 {
|
|
|
+ if pinCategoryView.selectedIndex != topSection! {
|
|
|
+ //不相同才切换
|
|
|
+ pinCategoryView.selectItem(at: topSection!)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- //分区头即将要消失时调用
|
|
|
- func collectionView(_ collectionView: UICollectionView,
|
|
|
- didEndDisplayingSupplementaryView view: UICollectionReusableView,
|
|
|
- forElementOfKind elementKind: String, at indexPath: IndexPath) {
|
|
|
- //如果是由用户手动滑动屏幕造成的向下滚动,那么左侧表格自动选中该分区对应的下一个分区的分类
|
|
|
- if collectionView == allCollectionView {
|
|
|
- if isScrollDown && (collectionView.isDragging || collectionView.isDecelerating) {
|
|
|
- selectRow(index: indexPath.section + 1)
|
|
|
- }
|
|
|
- }
|
|
|
+extension ShoppingMallView:UICollectionViewDelegateFlowLayout {
|
|
|
+
|
|
|
+ func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
|
|
|
+ return CGSize(width: self.bounds.size.width, height: 40)
|
|
|
}
|
|
|
|
|
|
|
|
|
- // 当拖动 CollectionView 的时候,处理 categorycollectionView
|
|
|
- private func selectRow(index : Int) {
|
|
|
- categorycollectionView.selectItem(at: IndexPath(row: index, section: 0), animated: true, scrollPosition: UICollectionView.ScrollPosition.centeredHorizontally)
|
|
|
+ func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
|
|
+ return CGSize(width: 100, height: 100)
|
|
|
+ }
|
|
|
|
|
|
+ func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
|
|
|
+ return 10
|
|
|
}
|
|
|
|
|
|
- // 标记一下 CollectionView 的滚动方向,是向上还是向下
|
|
|
- func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
|
|
+ func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
|
|
|
+ return (self.bounds.size.width - 100 * 3) / 4
|
|
|
+ }
|
|
|
+
|
|
|
+ func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
|
|
|
+ let margin: CGFloat = (self.bounds.size.width - 100 * 3) / 4
|
|
|
+ return UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+extension ShoppingMallView: JXCategoryViewDelegate{
|
|
|
+
|
|
|
+ func categoryView(_ categoryView: JXCategoryBaseView!, didClickSelectedItemAt index: Int) {
|
|
|
+ self.collectionViewScrollToTop(section: index, animated: true)
|
|
|
+ }
|
|
|
+
|
|
|
+ //将右侧colletionView的指定分区自动滚动到最顶端
|
|
|
+ func collectionViewScrollToTop(section: Int, animated: Bool) {
|
|
|
+ let headerRect = collectionViewHeaderFrame(section: section)
|
|
|
+ let topOfHeader = CGPoint(x: 0, y: headerRect.origin.y
|
|
|
+ - allCollectionView.contentInset.top)
|
|
|
+ allCollectionView.setContentOffset(topOfHeader, animated: animated)
|
|
|
+ }
|
|
|
|
|
|
- if allCollectionView == scrollView {
|
|
|
- isScrollDown = lastOffsetY < scrollView.contentOffset.y
|
|
|
- lastOffsetY = scrollView.contentOffset.y
|
|
|
+ //后获colletionView的指定分区头的高度
|
|
|
+ func collectionViewHeaderFrame(section: Int) -> CGRect {
|
|
|
+ let indexPath = IndexPath(item: 0, section: section)
|
|
|
+ let attributes = allCollectionView.collectionViewLayout
|
|
|
+ .layoutAttributesForSupplementaryView(ofKind:
|
|
|
+ UICollectionView.elementKindSectionHeader, at: indexPath)
|
|
|
+ guard let frameForFirstCell = attributes?.frame else {
|
|
|
+ return .zero
|
|
|
}
|
|
|
+ return frameForFirstCell;
|
|
|
}
|
|
|
+
|
|
|
}
|