「Swift」类淘宝商品瀑布流展示

前言:需要做一个类似于淘宝商品页面的瀑布流展示
结构分析:


ps:图片来源

思路分析:

该瀑布流主要还是基于UICollectionView进行展示,只是在cell展示的UICollectionViewFlowLayout需要进行相应调整和自定义,所以需要自己创建一个FlowLayout,该FlowLayout是基于UICollectionViewFlowLayout实现的,所以该FlowLayout的初始化调用流程于系统没有区别,只需遵循WaterfallMutiSectionDelegate代理。

1.初始化:
let layout = ZUPowerShopProductLayout()
layout.delegate = self
myCollectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
2.Cell代理:
1.必须实现的代理方法
/// collectionItem高度
func heightForRowAtIndexPath(collectionView collection: UICollectionView, layout: ZUPowerShopProductLayout, indexPath: IndexPath, itemWidth: CGFloat) -> CGFloat
2.可选择的代理方法
/// 每个section 列数(默认2列)
@objc optional func columnNumber(collectionView collection: UICollectionView, layout: ZUPowerShopProductLayout, section: Int) -> Int

/// header高度(默认为0)
@objc optional func referenceSizeForHeader(collectionView collection: UICollectionView, layout: ZUPowerShopProductLayout, section: Int) -> CGSize

/// footer高度(默认为0)
@objc optional func referenceSizeForFooter(collectionView collection: UICollectionView, layout: ZUPowerShopProductLayout, section: Int) -> CGSize

/// 每个section 边距(默认为0)
@objc optional func insetForSection(collectionView collection: UICollectionView, layout: ZUPowerShopProductLayout, section: Int) -> UIEdgeInsets

/// 每个section item上下间距(默认为0)
@objc optional func lineSpacing(collectionView collection: UICollectionView, layout: ZUPowerShopProductLayout, section: Int) -> CGFloat

/// 每个section item左右间距(默认为0)
@objc optional func interitemSpacing(collectionView collection: UICollectionView, layout: ZUPowerShopProductLayout, section: Int) -> CGFloat

/// section头部header与上个section尾部footer间距(默认为0)
@objc optional func spacingWithLastSection(collectionView collection: UICollectionView, layout: ZUPowerShopProductLayout, section: Int) -> CGFloat
3.自定义的FlowLayout文件
import UIKit

@objc protocol WaterfallMutiSectionDelegate: NSObjectProtocol {
  // 必选delegate实现
  /// collectionItem高度
  func heightForRowAtIndexPath(collectionView collection: UICollectionView, layout: WaterfallMutiSectionFlowLayout, indexPath: IndexPath, itemWidth: CGFloat) -> CGFloat
  
  // 可选delegate实现
  /// 每个section 列数(默认2列)
  @objc optional func columnNumber(collectionView collection: UICollectionView, layout: WaterfallMutiSectionFlowLayout, section: Int) -> Int
  
  /// header高度(默认为0)
  @objc optional func referenceSizeForHeader(collectionView collection: UICollectionView, layout: WaterfallMutiSectionFlowLayout, section: Int) -> CGSize
  
  /// footer高度(默认为0)
  @objc optional func referenceSizeForFooter(collectionView collection: UICollectionView, layout: WaterfallMutiSectionFlowLayout, section: Int) -> CGSize
  
  /// 每个section 边距(默认为0)
  @objc optional func insetForSection(collectionView collection: UICollectionView, layout: WaterfallMutiSectionFlowLayout, section: Int) -> UIEdgeInsets
  
  /// 每个section item上下间距(默认为0)
  @objc optional func lineSpacing(collectionView collection: UICollectionView, layout: WaterfallMutiSectionFlowLayout, section: Int) -> CGFloat
  
  /// 每个section item左右间距(默认为0)
  @objc optional func interitemSpacing(collectionView collection: UICollectionView, layout: WaterfallMutiSectionFlowLayout, section: Int) -> CGFloat
  
  /// section头部header与上个section尾部footer间距(默认为0)
  @objc optional func spacingWithLastSection(collectionView collection: UICollectionView, layout: WaterfallMutiSectionFlowLayout, section: Int) -> CGFloat
}

class WaterfallMutiSectionFlowLayout: UICollectionViewFlowLayout {
  weak var delegate: WaterfallMutiSectionDelegate?
  
  private var sectionInsets: UIEdgeInsets = .zero
  private var columnCount: Int = 2
  private var lineSpacing: CGFloat = 0
  private var interitemSpacing: CGFloat = 0
  private var headerSize: CGSize = .zero
  private var footerSize: CGSize = .zero
  
  //存放attribute的数组
  private var attrsArray: [UICollectionViewLayoutAttributes] = []
  //存放每个section中各个列的最后一个高度
  private var columnHeights: [CGFloat] = []
  //collectionView的Content的高度
  private var contentHeight: CGFloat = 0
  //记录上个section高度最高一列的高度
  private var lastContentHeight: CGFloat = 0
  //每个section的header与上个section的footer距离
  private var spacingWithLastSection: CGFloat = 0
  
  
  override func prepare() {
    super.prepare()
    self.contentHeight = 0
    self.lastContentHeight = 0
    self.spacingWithLastSection = 0
    self.lineSpacing = 0
    self.sectionInsets = .zero
    self.headerSize = .zero
    self.footerSize = .zero
    self.columnHeights.removeAll()
    self.attrsArray.removeAll()
    
    let sectionCount = self.collectionView!.numberOfSections
    // 遍历section
    for idx in 0..<sectionCount {
      let indexPath = IndexPath(item: 0, section: idx)
      if let columnCount = self.delegate?.columnNumber?(collectionView: self.collectionView!, layout: self, section: indexPath.section) {
        self.columnCount = columnCount
      }
      if let inset = self.delegate?.insetForSection?(collectionView: self.collectionView!, layout: self, section: indexPath.section) {
        self.sectionInsets = inset
      }
      if let spacingLastSection = self.delegate?.spacingWithLastSection?(collectionView: self.collectionView!, layout: self, section: indexPath.section) {
        self.spacingWithLastSection = spacingLastSection
      }
      // 生成header
      let itemCount = self.collectionView!.numberOfItems(inSection: idx)
      let headerAttri = self.layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: indexPath)
      if let header = headerAttri {
        self.attrsArray.append(header)
        self.columnHeights.removeAll()
      }
      self.lastContentHeight = self.contentHeight
      // 初始化区 y值
      for _ in 0..<self.columnCount {
        self.columnHeights.append(self.contentHeight)
      }
      // 多少个item
      for item in 0..<itemCount {
        let indexPat = IndexPath(item: item, section: idx)
        let attri = self.layoutAttributesForItem(at: indexPat)
        if let attri = attri {
          self.attrsArray.append(attri)
        }
      }
      
      // 初始化footer
      let footerAttri = self.layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, at: indexPath)
      if let footer = footerAttri {
        self.attrsArray.append(footer)
      }
    }
  }
  
  override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    return self.attrsArray
  }
  
  override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    if let column = self.delegate?.columnNumber?(collectionView: self.collectionView!, layout: self, section: indexPath.section) {
      self.columnCount = column
    }
    if let lineSpacing = self.delegate?.lineSpacing?(collectionView: self.collectionView!, layout: self, section: indexPath.section) {
      self.lineSpacing = lineSpacing
    }
    if let interitem = self.delegate?.interitemSpacing?(collectionView: self.collectionView!, layout: self, section: indexPath.section) {
      self.interitemSpacing = interitem
    }
    
    let attri = UICollectionViewLayoutAttributes(forCellWith: indexPath)
    let weight = self.collectionView!.frame.size.width
    let itemSpacing = CGFloat(self.columnCount - 1) * self.interitemSpacing
    let allWeight = weight - self.sectionInsets.left - self.sectionInsets.right - itemSpacing
    let cellWeight = allWeight / CGFloat(self.columnCount)
    let cellHeight: CGFloat = (self.delegate?.heightForRowAtIndexPath(collectionView: self.collectionView!, layout: self, indexPath: indexPath, itemWidth: cellWeight))!
    
    var tmpMinColumn = 0
    var minColumnHeight = self.columnHeights[0]
    for i in 0..<self.columnCount {
      let columnH = self.columnHeights[i]
      if minColumnHeight > columnH {
        minColumnHeight = columnH
        tmpMinColumn = i
      }
    }
    let cellX = self.sectionInsets.left + CGFloat(tmpMinColumn) * (cellWeight + self.interitemSpacing)
    var cellY: CGFloat = 0
    cellY = minColumnHeight
    if cellY != self.lastContentHeight {
      cellY += self.lineSpacing
    }
    
    if self.contentHeight < minColumnHeight {
      self.contentHeight = minColumnHeight
    }
    
    attri.frame = CGRect(x: cellX, y: cellY, width: cellWeight, height: cellHeight)
    self.columnHeights[tmpMinColumn] = attri.frame.maxY
    //取最大的
    for i in 0..<self.columnHeights.count {
      if self.contentHeight < self.columnHeights[i] {
        self.contentHeight = self.columnHeights[i]
      }
    }
    
    return attri
  }
  override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    let attri = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: elementKind, with: indexPath)
    if elementKind == UICollectionView.elementKindSectionHeader {
      if let headerSize = self.delegate?.referenceSizeForHeader?(collectionView: self.collectionView!, layout: self, section: indexPath.section) {
        self.headerSize = headerSize
      }
      self.contentHeight += self.spacingWithLastSection
      attri.frame = CGRect(x: 0, y: self.contentHeight, width: self.headerSize.width, height: self.headerSize.height)
      self.contentHeight += self.headerSize.height
      self.contentHeight += self.sectionInsets.top
    } else if elementKind == UICollectionView.elementKindSectionFooter {
      if let footerSize = self.delegate?.referenceSizeForFooter?(collectionView: self.collectionView!, layout: self, section: indexPath.section) {
        self.footerSize = footerSize
      }
      self.contentHeight += self.sectionInsets.bottom
      attri.frame = CGRect(x: 0, y: self.contentHeight, width: self.footerSize.width, height: self.footerSize.height)
      self.contentHeight += self.footerSize.height
    }
    return attri
  }
  
  override var collectionViewContentSize: CGSize {
    return CGSize(width: self.collectionView!.frame.size.width, height: self.contentHeight)
  }
}
实现效果图

在这里插入图片描述

该文章参考借鉴了iOS 多section瀑布流实现(swift)文章,由此篇文章内容受益匪浅!

借鉴文章作者的github demo:https://github.com/RoganZheng/WaterfallMultiSectionFlowLayout,如果该demo对你有帮助的话,记得帮这位作者star一下

如果该文章对你有所帮助的话,可以点赞、收藏并关注一下!后续会持续更新更多技术内容

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/215730.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

医药行业:轻松学会超低温冰箱技能

超低温冰箱在医疗、科研和生物领域中扮演着至关重要的角色&#xff0c;用于存储和保护对温度极为敏感的样品和药品。 然而&#xff0c;由于这些冰箱内的温度波动可能导致样品的损坏&#xff0c;因此对超低温冰箱的监控变得至关重要。 客户案例 医疗研究机构 上海某医疗研究机…

【Seata源码学习 】篇六 全局事务提交与回滚

【Seata源码学习 】篇六 全局事务提交与回滚 全局事务提交 TM在RPC远程调用RM后,如果没有出现异常&#xff0c;将向TC发送提交全局事务请求io.seata.tm.api.TransactionalTemplate#execute public Object execute(TransactionalExecutor business) throws Throwable {// 1. …

大文件分片上传、分片进度以及整体进度、断点续传【前端原生、后端 Koa、Node 原生】(一)

分片进度 效果展示&#xff0c;一个分片是 500MB 的 这个分片大小是 10MB 的 大文件分片上传 效果展示 前端 思路 前端的思路&#xff1a;将大文件切分成多个小文件&#xff0c;然后并发给后端。 页面构建 先在页面上写几个组件用来获取文件。 <body><input ty…

网工学习7-配置 GVRP 协议

7.1GARP概述 GARP(Generic Attribute Registration Protocol)是通用属性注册协议的应用&#xff0c;提供 802.1Q 兼容的 VLAN 裁剪 VLAN pruning 功能和在 802.1Q 干线端口 trunk port 上建立动态 VLAN 的功能。 GARP 作为一个属性注册协议的载体&#xff0c;可以用来传播属性…

(1w字一篇理解透Unsafe类)Java魔法类:Unsafe详解

Java魔法类 Unsafe 文章导读&#xff1a;(约12015字&#xff0c;阅读时间大约1小时)1. Unsafe介绍2. Unsafe创建3. Unsafe功能3.1内存操作3.2 内存屏障3.3 对象操作3.4 数组操作3.5 CAS操作3.6 线程调度3.7 Class操作3.8 系统信息 4. 总结 JUC源码中的并发工具类出现过很多次 …

外包干了4年,技术退步太明显了。。。。。

先说一下自己的情况&#xff0c;本科生生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年国庆&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测…

netcore swagger 错误 Failed to load API definition

后端接口报错如下&#xff1a; 前端nswag报错如下&#xff1a; 根据网上查询到的资料说明&#xff0c;说一般swagger这种错误都是控制器里有接口代码异常造成的&#xff0c;通常是接口没有加属性Attribute&#xff0c; 比如[HttpPost("Delete")]、[HttpGet("Del…

基于ssm的疫苗预约系统(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于ssm的疫苗预约系统&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring Spri…

从这7点重构品牌企业的业务中台系统|徐礼昭

文&#xff5c;徐礼昭 &#xff08;商派市场负责人&#xff0c;RRL重构零售实验室负责人&#xff09; 重构或者升级企业的数字化系统究竟有多重要&#xff1f; 笔者列举两个简单的数字化项目案例&#xff0c;数据仅供大家参考—— &#xff08;1&#xff09;某连锁企业线上云店…

【OpenCV】计算机视觉图像处理基础知识

目录 前言 推荐 1、OpenCV礼帽操作和黑帽操作 2、Sobel算子理论基础及实际操作 3、Scharr算子简介及相关操作 4、Sobel算子和Scharr算子的比较 5、laplacian算子简介及相关操作 6、Canny边缘检测的原理 6.1 去噪 6.2 梯度运算 6.3 非极大值抑制 6.4 滞后阈值 7、Ca…

mockito加junit实现单元测试笔记

目录 一、简介1.1 单元测试的特点1.2 mock类框架使用场景1.3 常用mock类框架1.3.1 mockito1.3.2 easymock1.3.3 powermock1.3.4 JMockit 二、mockito的单独使用2.1 mock对象与spy对象2.2 初始化mock/spy对象的方式初始化mock/spy对象第1种方式初始化mock/spy对象第2种方式初始化…

数据“表”的增删改查

创建数据表 删除数据表 修改数据表 查看数据表 喜欢点赞收藏&#xff0c;如有疑问&#xff0c;点击链接加入群聊【信创技术交流群】&#xff1a;http://qm.qq.com/cgi-bin/qm/qr?_wv1027&kEjDhISXNgJlMMemn85viUFgIqzkDY3OC&authKey2SKLwlmvTpbqlaQtJ%2FtFXJgHVgl…

全球与中国HDPE管道市场:增长趋势、竞争格局与前景展望

快速成长的人口、快速的经济成长和工业发展增加了对可靠供水系统的需求。工业需要为制造流程、冷却系统和卫生目的提供可靠的水供应。随着国家的发展&#xff0c;它们更加重视基础设施&#xff0c;包括供水系统&#xff0c;以支持工业成长。HDPE管道广泛应用于饮用水和灌溉的配…

LeetCode 1038. 从二叉搜索树到更大和树:(反)中序遍历

【LetMeFly】1038.从二叉搜索树到更大和树&#xff1a;&#xff08;反&#xff09;中序遍历 力扣题目链接&#xff1a;https://leetcode.cn/problems/binary-search-tree-to-greater-sum-tree/ 给定一个二叉搜索树 root (BST)&#xff0c;请将它的每个节点的值替换成树中大于…

虹科技术 | BabyLIN产品如何轻松搞定K线协议实现?

概述&#xff1a;为了实现K线通信&#xff0c;SDF-V3在协议部分中定义了新的协议类型KLine Raw。所有能够运行SDF-V3文件&#xff08;LinWorks版本在V.2.29.4以上&#xff09;并使用最新的固件&#xff08;固件版本在V.6.18以上&#xff09;的BabyLIN设备都可以执行KLine Raw协…

【23-24 秋学期】NNDL 作业12 优化算法2D可视化

简要介绍图中的优化算法&#xff0c;编程实现并2D可视化 1. 被优化函数 2. 被优化函数 3. 分析各个算法的优缺点 REF&#xff1a;图灵社区-图书 (ituring.com.cn) 深度学习入门&#xff1a;基于Python的理论与实现 NNDL 作业11&#xff1a;优化算法比较_"ptimizers[…

MYSQL报错 [ERROR] InnoDB: Unable to create temporary file; errno: 0

起因 服务器的mysql不支持远程访问&#xff0c;在修改完相关配置后重启服务出错。 2023-12-03T10:12:23.895459Z 0 [Note] C:\Program Files\MySQL\MySQL Server 5.7\bin\mysqld.exe (mysqld 5.7.22-log) starting as process 15684 ... 2023-12-03T10:12:23.908886Z 0 [Note…

YOLOv8独家原创改进:创新自研CPMS注意力,多尺度通道注意力具+多尺度深度可分离卷积空间注意力,全面升级CBAM

💡💡💡本文自研创新改进:自研CPMS, 多尺度通道注意力具+多尺度深度可分离卷积空间注意力,全面升级CBAM 1)作为注意力CPMS使用; 推荐指数:五星 CPMS | 亲测在多个数据集能够实现涨点,对标CBAM。 收录 YOLOv8原创自研 https://blog.csdn.net/m0_63774211/ca…

内存是如何工作的

一、什么是内存 从外观上辨识&#xff0c;它就是内存条&#xff1b;从硬件上讲&#xff0c;它叫RAM&#xff0c;翻译过来叫随机存储器。英文全称&#xff1a;Random Access Memory。它也叫主存&#xff0c;是与CPU直接交换数据的内部存储器。其特点是读写速度快&#xff0c;不…

一文搞懂系列——动态库的加载方式及应用场景

引文 我们在工作中经常会遇到动态库链接的问题&#xff0c;因为正常的方式并不能满足我们的场景。常见的问题可以总结如下&#xff1a; 系统路径默认路径、usr/lib、/lib 目录&#xff0c;不会集成第三方动态库。 同名动态库可能在多个路径中存在。 针对不同的场景&#xff0…