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

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


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一下

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[ 云计算 | AWS 实践 ] 使用 Java 检查指定的密钥是否存在于给定的 Amazon S3 存储桶中

本文收录于【#云计算入门与实践 - AWS】专栏中&#xff0c;收录 AWS 入门与实践相关博文。 本文同步于个人公众号&#xff1a;【云计算洞察】 更多关于云计算技术内容敬请关注&#xff1a;CSDN【#云计算入门与实践 - AWS】专栏。 本系列已更新博文&#xff1a; [ 云计算 | …

Linux——基本指令(一)

写在前面&#xff1a; 我们云服务器搭建的Linux系统&#xff0c;使用的镜像版本CentOS 7.6,使用的Xshell远程连接云服务器 前面我们使用超级管理员root账号登录&#xff0c;一般我们使用普通用户登录&#xff0c;那么如何创建新用户呢&#xff1f; 1.创建新用户 &#xff08…

【Java 基础】17 集合

文章目录 1.基本概念2.核心接口3.常见实现1&#xff09;List 接口的实现类ArrayListLinkedListVector 2&#xff09;Set 接口的实现类HashSetLinkedHashSetTreeSet 3&#xff09;Queue 接口的实现**类**ArrayQueue 4&#xff09;Map 接口的实现类HashMapLinkedHashMapTreeMap 4…

跟着Nature Communications学习Hisat-Trinity-PASA等分析流程

一边学习&#xff0c;一边总结&#xff0c;一边分享&#xff01; 详细教程请访问&#xff1a; 组学分析流程 本期分析流程 Hisat2-SamtoolsTrinity_GG_denovoPASA … 本期教程文章 题目&#xff1a;Genomic insights into local adaptation and future climate-induced vu…

C++-内存管理

目录 一.C/C内存分布 二. C语言中动态内存管理方式&#xff1a;malloc/calloc/realloc/free 三. C内存管理方式 1.new/delete操作内置类型 2.new和delete操作自定义类型 四.C语言中的动态开辟内存空间和C中的区别 1.对于开辟内置类型 2.…

2023/12/3总结

RabbitMq 消息队列 下载地址RabbitMQ: easy to use, flexible messaging and streaming — RabbitMQ 使用详情RabbitMQ使用教程(超详细)-CSDN博客 实现延迟队列&#xff08;为了实现订单15分钟后修改状态&#xff09; 1 死信队列 当一个队列中的消息满足下列情况之一时&…

智能优化算法应用:基于秃鹰算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于秃鹰算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于秃鹰算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.秃鹰算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…

开源项目-对新手极其友好的数据结构与算法入门教程-hello-algo

点击上方“嵌入式应用研究院”&#xff0c;选择“置顶/星标公众号” 干货福利&#xff0c;第一时间送达&#xff01; 排版 | 嵌入式应用研究院 素材来源 |Github开源社区 从业多年&#xff0c;大家是否在工作中遇到很多软件逻辑问题难以解决&#xff1f;其实大多数情况下是由于…

一次电气——电抗器(一)

我之前的工作是在国外建联合循环电厂&#xff0c;现在的工作是研发一次电力设备。虽然仍是在电力行业发展&#xff0c;但这两份不同岗位不同职能的工作究其感受而言有很大的不同。相较于第一份工作&#xff0c;第二份工作带给我带来的更多的是一种由广及微&#xff0c;由浅入深…

小程序SSL证书

小程序通常需要与服务器进行数据交互&#xff0c;包括用户的登录信息、支付数据等。在没有安全保障的情况下&#xff0c;这些敏感数据容易受到黑客攻击&#xff0c;导致信息泄露和用户隐私的严重问题。因此&#xff0c;确保小程序中的通信安全势在必行。 SSL证书在小程序中扮演…

排序算法介绍(一)插入排序

0. 简介 插入排序&#xff08;Insertion Sort&#xff09; 是一种简单直观的排序算法&#xff0c;它的工作原理是通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插入。插入排序在实现上&#xff0c;通常…

TimeGPT:时间序列预测模型实例

时间序列预测领域正在经历一个非常激动人心的时期。在过去的三年里&#xff0c;我们见证了许多重要的贡献&#xff0c;如N-BEATS、N-HiTS、PatchTST和TimesNet等。同时&#xff0c;大型语言模型&#xff08;LLM&#xff09;近来在流行度方面取得了很大的成功&#xff0c;例如Ch…

Zotero 安装及常用插件设置指南

Zotero 安装及常用插件设置指南 本指南旨在帮助用户安装并配置 Zotero。通过本教程&#xff0c;您将能够实现以下功能&#xff1a; 界面语言设置为中文使用颜色标签来区分不同阅读状态的文献重要文献标记显示影响因子、JCP和中科院分区翻译插件Sci-Hub 集成 安装和设置步骤…

【算法】单调栈题单(矩阵系列、字典序最小、贡献法)⭐

文章目录 题单来源经典题单496. 下一个更大元素 I&#xff08;单调栈模板题&#xff09;503. 下一个更大元素 II&#xff08;单调栈循环数组&#xff09;2454. 下一个更大元素 IV&#xff08;第二个更大的元素&#xff1a;两个单调栈&#xff09;456. 132 模式&#xff08;单调…

Dockerfile与Docker网络

一、Dockerfile 1、概念&#xff1a; Dockerfile是用来构建docker镜像的文本文件&#xff0c;是由构建镜像所需要的指令和参数构建的脚本。 2、构建步骤&#xff1a; ① 编写Dockerfile文件 ② docker build命令构建镜像 ③ docker run依据镜像运行容器实例 Dockerfile …

电影《星愿》观后感

上周看了电影《星愿》&#xff0c;看这部电影的动机&#xff0c;主要是回忆的价值大于电影本身的价值&#xff0c;看着影片介绍&#xff0c;是迪士尼工作室成立百年&#xff0c;特别推出的影片。 具体来说&#xff0c;主要是在开头有一段是影片&#xff0c;各个时期的我们看过的…

Echarts大屏可视化_03 定制柱状图

柱状图模块引入 1.找到合适的图表 在echarts中寻找与目标样式相近的图表 Examples - Apache ECharts 2. 引入柱状图 使用立即执行函数构建&#xff0c;防止变量全局污染 实例化对象 将官网中提供的option复制到代码中&#xff0c;并且构建图表 // 柱状图模块1 (function () {/…

若依微服务项目整合rocketMq

原文链接&#xff1a;ttps://mp.weixin.qq.com/s/IYdo_suKvvReqCiEKjCeHw 第一步下载若依项目 第二步安装rocketMq&#xff08;推荐在linux使用docker部署比较快&#xff09; 第二步新建一个生产者模块儿&#xff0c;再建一个消费者模块 第四步在getway模块中配置接口映射规…

浅学指针(5)sizeof和strlen的进阶理解

系列文章目录 文章目录 系列文章目录前言1. sizeof和strlen的对⽐1.1 sizeofsizeof不是函数&#xff0c;是运算符 1.2 strlen1.3 sizeof 和 strlen的对⽐ 2. 数组和指针笔试题解析• sizeof(数组名)&#xff0c;sizeof中单独放数组名&#xff0c;这⾥的数组名表⽰整个数组&…

一款自动帮你生成UI界面和代码的AI神器

我的新书《Android App开发入门与实战》已于2020年8月由人民邮电出版社出版&#xff0c;欢迎购买。点击进入详情 只要描述你想要的UI是什么样的&#xff0c;它就能帮你生成&#xff0c;是不是很神奇&#xff1f; v0使用 AI 模型根据简单的文本提示生成用户界面和代码&#xff…