iOS瀑布流的简单实现(Swift)

这段时间突然想到一个很久之前用到的知识-瀑布流,本来想用一个简单的方法,发现自己走入了歧途,最终只能狠下心来重写UICollectionViewFlowLayout.下面我将用两种方法实现瀑布流,以及会介绍第一种实现的bug.

<1>第一种

效果图如下所示:

这种实现方法的思路:

1)首先调用随机函数,产生随机高度,并把它保存到数组中

 - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
  CGFloat cellW = 100;
  CGFloat cellH = 100 + (arc4random() % 80);
  [self.heightArrayM addObject:@(cellH)];

  return CGSizeMake(cellW, cellH);

}

2)在设置cell的frame的地方,通过取余,取整确定cell的高度,并设定cell的frame

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {

  UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];
  //当前处于多少行
  NSInteger num1 = indexPath.row / count;
  //当前处于多少列
  int num2 = indexPath.row % count;
  CGFloat cellX = num2 * 100 + (num2 + 1) * margin;
  CGFloat cellY = 0;
  for (int i = 0; i < num1; i++) {
    NSInteger position = num2 + i * 3;
    cellY += [self.heightArrayM[position] floatValue] + margin;
  }
  CGFloat cellW = 100;
  CGFloat cellH = cellHeight;
  cell.frame = CGRectMake(cellX, cellY, cellW, cellH);
//  cell.backgroundColor = [UIColor redColor];
  cell.backgroundColor = [UIColor colorWithRed:(arc4random() % 250) / 250.0 green:(arc4random() % 250) / 250.0 blue:(arc4random() % 250) / 250.0 alpha:1.0];

//  NSLog(@"%@", NSStringFromCGRect(cell.frame));
  return cell;
}

弊端 : 其实这种方法的弊端,相信从上面的动态图中可以看出来,当往上面滑的时候,由于cell的循环机制,下面的cell的会消失,但是由于高度不一致,同时撤销的是最后一行的cell,所以下面的cell在屏幕上就会消失.

下面附上第一种方法的源代码:

#import "ViewController.h"

#define margin 10
#define count 3
#define cellHeight [self.heightArrayM[indexPath.row] floatValue]
static NSString * const ID = @"cell";
@interface ViewController ()<UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
@property (nonatomic, strong) NSMutableArray *heightArrayM;

@end

@implementation ViewController

- (NSMutableArray *)heightArrayM {
  if (_heightArrayM == nil) {
    _heightArrayM = [NSMutableArray array];
  }
  return _heightArrayM;
}

- (void)viewDidLoad {
  [super viewDidLoad];

  [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:ID];
  self.collectionView.dataSource = self;
  self.collectionView.delegate = self;
  //设置collectionView
  [self setupCollectionView];
}

//设置collectionView的布局
- (UICollectionViewFlowLayout *)setupCollectionLayout {
  UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];

  flowLayout.minimumInteritemSpacing = margin;
  flowLayout.minimumLineSpacing = margin;
  flowLayout.sectionInset = UIEdgeInsetsMake(margin, margin, margin, margin);
  return flowLayout;
}

//设置collectionView
- (void)setupCollectionView {
  self.collectionView.collectionViewLayout =[self setupCollectionLayout];

}

#pragma mark - UICollectionViewDataSouce
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
  return 60;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {

  UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];
  //当前处于多少行
  NSInteger num1 = indexPath.row / count;
  //当前处于多少列
  int num2 = indexPath.row % count;
  CGFloat cellX = num2 * 100 + (num2 + 1) * margin;
  CGFloat cellY = 0;
  for (int i = 0; i < num1; i++) {
    NSInteger position = num2 + i * 3;
    cellY += [self.heightArrayM[position] floatValue] + margin;
  }
  CGFloat cellW = 100;
  CGFloat cellH = cellHeight;
  cell.frame = CGRectMake(cellX, cellY, cellW, cellH);
//  cell.backgroundColor = [UIColor redColor];
  cell.backgroundColor = [UIColor colorWithRed:(arc4random() % 250) / 250.0 green:(arc4random() % 250) / 250.0 blue:(arc4random() % 250) / 250.0 alpha:1.0];

//  NSLog(@"%@", NSStringFromCGRect(cell.frame));
  return cell;
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
  CGFloat cellW = 100;
  CGFloat cellH = 100 + (arc4random() % 80);
  [self.heightArrayM addObject:@(cellH)];

  return CGSizeMake(cellW, cellH);

}
@end

<2>下面介绍第二种(Swift实现)

效果图如下所示:

这种实现方法就是比较成熟的了,我把它封装成一个类.其实主要是实现三个函数

1)重写父类的prepare方法,准备所有cell的样式

 extension WaterfallLayout {
  // prepare准备所有Cell的布局样式
  override func prepare() {
    super.prepare()

    // 0.获取item的个数
    let itemCount = collectionView!.numberOfItems(inSection: 0)

    // 1.获取列数
    let cols = dataSource?.numberOfColsInWaterfallLayout?(self) ?? 2

    // 2.计算Item的宽度
    let itemW = (collectionView!.bounds.width - self.sectionInset.left - self.sectionInset.right - self.minimumInteritemSpacing * CGFloat((cols - 1))) / CGFloat(cols)

    // 3.计算所有的item的属性
    for i in startIndex..<itemCount {
      // 1.设置每一个Item位置相关的属性
      let indexPath = IndexPath(item: i, section: 0)

      // 2.根据位置创建Attributes属性
      let attrs = UICollectionViewLayoutAttributes(forCellWith: indexPath)

      // 3.随机一个高度
      guard let height = dataSource?.waterfallLayout(self, indexPath: indexPath) else {
        fatalError("请设置数据源,并且实现对应的数据源方法")
      }

      // 4.取出最小列的位置
      var minH = colHeights.min()!
      let index = colHeights.index(of: minH)!
      minH = minH + height + minimumLineSpacing
      colHeights[index] = minH

      // 5.设置item的属性
      attrs.frame = CGRect(x: self.sectionInset.left + (self.minimumInteritemSpacing + itemW) * CGFloat(index), y: minH - height - self.minimumLineSpacing, width: itemW, height: height)
      attrsArray.append(attrs)
    }

    // 4.记录最大值
    maxH = colHeights.max()!

    // 5.给startIndex重新复制
    startIndex = itemCount
  }
}

2)返回设置cell样式的数组

 override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    return attrsArray
  }

3)返回当前的contentSize

override var collectionViewContentSize: CGSize {
    return CGSize(width: 0, height: maxH + sectionInset.bottom - minimumLineSpacing)
  }

总结:

在下面我封装的这个类中,只需要遵守我的数据代理源协议并且实现我的协议中的两个方法,传给我对应得高度(我这里是传的随机的),可选的方法,若是不实现,会有一个默认值,就可以实现该功能.协议如下:

@objc protocol WaterfallLayoutDataSource : class {
  func waterfallLayout(_ layout : WaterfallLayout, indexPath : IndexPath) -> CGFloat
  @objc optional func numberOfColsInWaterfallLayout(_ layout : WaterfallLayout) -> Int
}

完成代码如下所示:

ViewController.swift中的代码:

import UIKit

extension UIColor {
  class func randomColor() -> UIColor {
    return UIColor(colorLiteralRed: Float(arc4random_uniform(256)) / 255.0, green: Float(arc4random_uniform(256)) / 255.0, blue: Float(arc4random_uniform(256)) / 255.0, alpha: 1.0)
  }
}

private let kWaterCellID = "kWaterCellID"

class ViewController: UIViewController {

  var count : Int = 20

  override func viewDidLoad() {
    super.viewDidLoad()

    // 1.设置布局
    let layout = WaterfallLayout()
    layout.minimumLineSpacing = 10
    layout.minimumInteritemSpacing = 10
    layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    layout.dataSource = self

    // 2.创建UICollectionView
    let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
    collectionView.dataSource = self
    collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: kWaterCellID)
    view.addSubview(collectionView)
  }

}

extension ViewController : UICollectionViewDataSource {
  func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return count
  }

  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kWaterCellID, for: indexPath)

    cell.backgroundColor = UIColor.randomColor()

    if indexPath.item == count - 1 {
      count += 20

      collectionView.reloadData()
    }

    return cell
  }
}

extension ViewController : WaterfallLayoutDataSource {
  func waterfallLayout(_ layout: WaterfallLayout, indexPath: IndexPath) -> CGFloat {
    return CGFloat(arc4random_uniform(80) + 100)
  }

  func numberOfColsInWaterfallLayout(_ layout: WaterfallLayout) -> Int {
    return 3
  }
}

封装自定义布局中的WaterfallLayout.swift代码如下:

import UIKit

@objc protocol WaterfallLayoutDataSource : class {
  func waterfallLayout(_ layout : WaterfallLayout, indexPath : IndexPath) -> CGFloat
  @objc optional func numberOfColsInWaterfallLayout(_ layout : WaterfallLayout) -> Int
}

class WaterfallLayout: UICollectionViewFlowLayout {

  // MARK: 对外提供属性
  weak var dataSource : WaterfallLayoutDataSource?

  // MARK: 私有属性
  fileprivate lazy var attrsArray : [UICollectionViewLayoutAttributes] = [UICollectionViewLayoutAttributes]()

  fileprivate var totalHeight : CGFloat = 0
  fileprivate lazy var colHeights : [CGFloat] = {
    let cols = self.dataSource?.numberOfColsInWaterfallLayout?(self) ?? 2
    var colHeights = Array(repeating: self.sectionInset.top, count: cols)
    return colHeights
  }()
  fileprivate var maxH : CGFloat = 0
  fileprivate var startIndex = 0
}

extension WaterfallLayout {
  // prepare准备所有Cell的布局样式
  override func prepare() {
    super.prepare()

    // 0.获取item的个数
    let itemCount = collectionView!.numberOfItems(inSection: 0)

    // 1.获取列数
    let cols = dataSource?.numberOfColsInWaterfallLayout?(self) ?? 2

    // 2.计算Item的宽度
    let itemW = (collectionView!.bounds.width - self.sectionInset.left - self.sectionInset.right - self.minimumInteritemSpacing * CGFloat((cols - 1))) / CGFloat(cols)

    // 3.计算所有的item的属性
    for i in startIndex..<itemCount {
      // 1.设置每一个Item位置相关的属性
      let indexPath = IndexPath(item: i, section: 0)

      // 2.根据位置创建Attributes属性
      let attrs = UICollectionViewLayoutAttributes(forCellWith: indexPath)

      // 3.随机一个高度
      guard let height = dataSource?.waterfallLayout(self, indexPath: indexPath) else {
        fatalError("请设置数据源,并且实现对应的数据源方法")
      }

      // 4.取出最小列的位置
      var minH = colHeights.min()!
      let index = colHeights.index(of: minH)!
      minH = minH + height + minimumLineSpacing
      colHeights[index] = minH

      // 5.设置item的属性
      attrs.frame = CGRect(x: self.sectionInset.left + (self.minimumInteritemSpacing + itemW) * CGFloat(index), y: minH - height - self.minimumLineSpacing, width: itemW, height: height)
      attrsArray.append(attrs)
    }

    // 4.记录最大值
    maxH = colHeights.max()!

    // 5.给startIndex重新复制
    startIndex = itemCount
  }
}

extension WaterfallLayout {
  override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    return attrsArray
  }

  override var collectionViewContentSize: CGSize {
    return CGSize(width: 0, height: maxH + sectionInset.bottom - minimumLineSpacing)
  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

时间: 2016-11-26

Jquery瀑布流插件使用介绍

瀑布流布局浅析 浅谈个人在瀑布流网页的实现中遇到的问题和解决方法 折腾:瀑布流布局(基于多栏列表流体布局实现) javascript 瀑布流.各大瀑布流简析与建议 因为自己用jquery比较多,便萌生了把瀑布流做成插件的想法,图片就借用迅雷UED上的那些美图吧. 先看看Demo 把代码放出来吧 复制代码 代码如下: ;(function($){ var //参数 setting={ column_width:204,//列宽 column_className:'waterfall_column'

jQuery 瀑布流 浮动布局(一)(延迟AJAX加载图片)

浮动布局:即HTML结构的列,是用浮动方式. 一.功能分析: 1.判断图片是否进入可视区域: 2.用AJAX请求服务器数据: 3.将数据播入到相应的列队: 二.实现方法: 给window的scroll事件l绑定一个处理函数:做如下工作: 1.如何判断最后一行的图片,是否进入了可视区域? 如果:最后一行的某个图片距离浏览器可视区域顶部的距离值 小于 (可视区域的高度+滚动条滑动的距离值): 那么:就可以判定这个图片进入了浏览器的可视区域: 2.如何用AJAX请求服务器数据; $.getJSON()

瀑布流布局并自动加载实现代码

自从Pinterest使用了一种新的方式布局取得成功之后,从此互联网出现了布局潮流,我们把他叫做瀑布流!!在互联网流行起来,从国外到国内普遍存在.国内现有美丽说,蘑菇街,花瓣等代表的网站. 瀑布流是流行一段时间了,现在网上出现了很多的插件.使用起来更是非常的方便.目前用的非常多,并且有是一个juqery的插件masonry,插件地址:http://masonry.desandro.com/ 先使用css把一个网页按照从上到下的方式布局好. 使用起来更是非常方便: 我先引用好jquery文件和ma

详解IOS中如何实现瀑布流效果

首先是效果演示 特点:可以自由设置瀑布流的总列数(效果演示为2列) 虽然iphone手机的系统相册没有使用这种布局效果,瀑布流依然是一种很常见的布局方式!!!下面来详细介绍如何实现这种布局. 首先使用的类是UICollectionView 我们要做的是自定义UICollectionViewCell和UICollectionViewLayout 1.自定义UICollectionViewCell类,只需要一个UIImageView即可,frame占满整个cell. 2.重点是自定义UICollec

iOS自定义UICollectionViewLayout实现瀑布流布局

移动端访问不佳,请访问我的个人博客 最近项目中需要用到瀑布流的效果,但是用UICollectionViewFlowLayout又达不到效果,自己动手写了一个瀑布流的layout,下面是我的心路路程 先上效果图与demo地址: 因为是用UICollectionView来实现瀑布流的,决定继承UICollectionViewLayout来自定义一个layout来实现一个简单瀑布流的布局,下面是需要重写的方法: 重写这个属性得出UICollectionView的ContentSize:collecti

IOS简单实现瀑布流UICollectionView

UICollectionView 比tableView 灵活,功能也强大很多.系统实现了流式布局,但用处还有很多限制. 要想实现更灵活的布局,就咬重写UICollectionViewLayout. 先看下实现效果: 废话不多说,直接上代码: 先看WaterfallCollectionLayout.m #import "WaterfallCollectionLayout.h" #define colMargin 5 #define colCount 4 #define rolMargin

iOS实现水平方向瀑布流

效果 源码:https://github.com/YouXianMing/Animations // // GridFlowLayoutViewController.m // Animations // // Created by YouXianMing on 16/5/5. // Copyright © 2016年 YouXianMing. All rights reserved. // #import "GridFlowLayoutViewController.h" #import

IOS实现自定义布局瀑布流

瀑布流是电商应用展示商品通常采用的一种方式,如图示例 瀑布流的实现方式,通常有以下几种 通过UITableView实现(不常用) 通过UIScrollView实现(工作量较大) 通过UICollectionView实现(通常采用的方式) 一.UICollectionView基础 1.UICollectionView与UITableView有很多相似的地方,如 都通过数据源提供数据 都通过代理执行相关的事件 都可以自定义cell,且涉及到cell的重用 都继承自UIScrollView,具有滚动效

js实现的美女瀑布流效果代码

瀑布流以及回顶部的效果 *{ margin:0; padding:0; } h1{ text-align:center; height:100px; } body{ background-color:RGB(232,231,226); } .all{ margin:0 auto; width:1000px; } .number{ float:left; width:225px; } .content { margin:5px; background-color:white; } img{ mar

javascript自适应宽度的瀑布流实现思路

这样的布局并不陌生,从2011年Pinterest创立以来,中国互联网就迅速掀起了一股模仿Pinterest的热潮,国内有众多网站采用瀑布流的布局方式,例如花瓣网.美丽说等等.而事实上在中国互联网,模仿一些在国外被人看好的模式(当然,你也可以说是山寨或抄袭,呵呵!!)向来都是一个不错的idea. OK,现在进入正题.这里主要介绍瀑布流的一种实现方法:绝对定位(css)+javascript+ajax+json.简单一点如果不做滚动加载的话就是绝对定位(css)+javascript了,ajax和

用JavaScript和jQuery实现瀑布流

大致介绍 在慕课网上学习了用原生js和jQuery实现瀑布流,在这里做个笔记 用JavaScript实现 基本结构: <div id="main"> <div class="box"> <div class="pic"><img src="images/1.jpg" alt=""></div> </div> <div class=

android中UIColletionView瀑布流布局实现思路以及封装的实现

瀑布流实现思路 第一种就是用ScrollView来进行实现,由于它不具备复用的功能,因此我们需要自己写一套类似复用的模块来进行优化 第二种就是利用apple做好的复用模块,自定义UIColletionLayout来实现瀑布流,想想也是第二种实现起来更快更优,OK,封装一个小小的框架来试试 默认两列 其他案例 上面的动画切换布局也是自定义UICollectionLayout来进行布局的,简单的静态图片布局展示其实就重写几个方法就可以了 1.prepareLayout 每次重新刷新collectio

javascript实现瀑布流自适应遇到的问题及解决方案

这几天看了Amy老师的用javascript实现瀑布流,我跟着把代码敲出来.发现这样写只能第一次载入时适应屏幕,以后改变窗口大小就不能做到自适应了. 于是我想到了用window.onresize来使得瀑布流函数从新加载来达到目的, 复制代码 代码如下: window.onload=function(){     //瀑布流函数     waterfall('content','box');     //模拟数据加载     var dataInt = {"data":[{"s

详解javascript实现瀑布流列式布局

本文介绍了javascript瀑布流列式布局的相关内容,分享给大家供大家参考,具体内容如下 JS原理 上面说了,列式布局简直算是完虐绝对式布局. 绝对式布局,简直就像10元/天 的搬砖工.而列式布局就是站在那看他搬砖的监工. 同样都是搬砖的,一个卖苦力,一个秀智商.简直了!!! 听了逼逼,我们来直面一下惨淡的人生. 列式布局的原理其实和绝对式布局没有太大的却别. 同样也有3个部分, 一是页面加载自适应,二是滑动加载,三是响应式布局. 分别讲解: 1.加载自适应 我们先看一下代码吧: var $

详解javascript实现瀑布流绝对式布局

瀑布流也应该算是流行几年了吧.首先是由Pinterest掀起的浪潮,然后国内设计如雨后春笋般,冒出很多瀑布流的例子,比如,蘑菇街,Mark之(不过最近涉黄,好像被喝茶了),还有淘宝的 "哇哦". 这些都是很棒的例子, 今天我们就聊一聊瀑布流. 一.绝对式布局: JS实现原理 其实瀑布式主要的难点就在于,如果将图片整齐的排列在对应的列下,以及什么时候开始刷新加载图片. 而图片整齐的排列的主要逻辑和算法即,先获取容器内可以放多少列,然后,通过计算,存放第一列的高度,再遍历剩下(除第一列的元

原生JavaScript+LESS实现瀑布流

HTML(注意包裹关系,方便js调用) 复制代码 代码如下: <body>      <div id="main">          <div class="box">              <div class="pic">                  <img src="images/0.jpg" alt="">          

JavaScript实现图片瀑布流和底部刷新

瀑布流现在基本上是图片显示网页的标配,主要是为了适配图片和文字块的大小,使显示出的效果没有那么呆板 实现这个功能首先要有html,css和js基础 首先先实现瀑布流 即下一行的图片放在上一行的凹下去的地方 基本的html代码如下 <html lang="en"> <head> <meta charset="UTF-8"> <title>瀑布流</title> <link rel="style

javascript实现瀑布流动态加载图片原理

本文实例为大家分享了js瀑布流加载效果,动态加载图片,供大家参考,具体内容如下 鼠标滚动事件,当鼠标滚动到下边,动态加载图片. 1. HTML代码     <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>js实现瀑布流效果-动态加载图片</title> <link rel="stylesheet" href="

javascript实现瀑布流加载图片原理

讲一下大概的原理吧,还是先上图:  功能描述: 根据不同菜单的属性值分别加载不同的数据 下拉滚动条到一定位置预加载图片,滚动条拉到最底下的时候渲染html: 鼠标移到菜单,切换各个图片列表: 鼠标移到图片列表上,显示详细信息:  技术实现方案: 先梳理一下从加载到显示的流程: 1. 加载数据 2. 拼接HTML写入到页面 3. 检查刚刚写入的HTML中的img是否全部加载完成,如果是,进入5.否则进入4 4. 等待图片加载完成 5. 计算每个元素的位置 一开始的时候最头疼的是如何定位的问题,后来