服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - IOS - iOS瀑布流的简单实现(Swift)

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

2021-02-21 14:59木子沉雨 IOS

这篇文章主要介绍了iOS瀑布流的简单实现,说到瀑布流, 或许大家都不陌生, 瀑布流的实现也有很多种! 本文使用两种方法介绍,有兴趣的可以了解一下。

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

<1>第一种

效果图如下所示:
iOS瀑布流的简单实现(Swift)

这种实现方法的思路: 

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

?
1
2
3
4
5
6
7
8
- (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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (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在屏幕上就会消失.

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#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实现)

效果图如下所示:
iOS瀑布流的简单实现(Swift)

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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样式的数组

?
1
2
3
override func layoutattributesforelements(in rect: cgrect) -> [uicollectionviewlayoutattributes]? {
   return attrsarray
 }

3)返回当前的contentsize

?
1
2
3
override var collectionviewcontentsize: cgsize {
    return cgsize(width: 0, height: maxh + sectioninset.bottom - minimumlinespacing)
  }

总结:

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

?
1
2
3
4
@objc protocol waterfalllayoutdatasource : class {
  func waterfalllayout(_ layout : waterfalllayout, indexpath : indexpath) -> cgfloat
  @objc optional func numberofcolsinwaterfalllayout(_ layout : waterfalllayout) -> int
}

完成代码如下所示:

viewcontroller.swift中的代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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)
  }
}

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

原文链接:http://www.cnblogs.com/muzichenyu/p/6108040.html

延伸 · 阅读

精彩推荐
  • IOSiOS布局渲染之UIView方法的调用时机详解

    iOS布局渲染之UIView方法的调用时机详解

    在你刚开始开发 iOS 应用时,最难避免或者是调试的就是和布局相关的问题,下面这篇文章主要给大家介绍了关于iOS布局渲染之UIView方法调用时机的相关资料...

    windtersharp7642021-05-04
  • IOSiOS通过逆向理解Block的内存模型

    iOS通过逆向理解Block的内存模型

    自从对 iOS 的逆向初窥门径后,我也经常通过它来分析一些比较大的应用,参考一下这些应用中某些功能的实现。这个探索的过程乐趣多多,不仅能满足自...

    Swiftyper12832021-03-03
  • IOS解析iOS开发中的FirstResponder第一响应对象

    解析iOS开发中的FirstResponder第一响应对象

    这篇文章主要介绍了解析iOS开发中的FirstResponder第一响应对象,包括View的FirstResponder的释放问题,需要的朋友可以参考下...

    一片枫叶4662020-12-25
  • IOSIOS开发之字典转字符串的实例详解

    IOS开发之字典转字符串的实例详解

    这篇文章主要介绍了IOS开发之字典转字符串的实例详解的相关资料,希望通过本文能帮助到大家,让大家掌握这样的方法,需要的朋友可以参考下...

    苦练内功5832021-04-01
  • IOSIOS 屏幕适配方案实现缩放window的示例代码

    IOS 屏幕适配方案实现缩放window的示例代码

    这篇文章主要介绍了IOS 屏幕适配方案实现缩放window的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要...

    xiari5772021-06-01
  • IOS关于iOS自适应cell行高的那些事儿

    关于iOS自适应cell行高的那些事儿

    这篇文章主要给大家介绍了关于iOS自适应cell行高的那些事儿,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的...

    daisy6092021-05-17
  • IOSiOS 雷达效果实例详解

    iOS 雷达效果实例详解

    这篇文章主要介绍了iOS 雷达效果实例详解的相关资料,需要的朋友可以参考下...

    SimpleWorld11022021-01-28
  • IOSiOS中tableview 两级cell的展开与收回的示例代码

    iOS中tableview 两级cell的展开与收回的示例代码

    本篇文章主要介绍了iOS中tableview 两级cell的展开与收回的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    J_Kang3862021-04-22