UICollectionView拾遗
发布日期:2022-03-18 08:27:47 浏览次数:34 分类:技术文章

本文共 10015 字,大约阅读时间需要 33 分钟。

UICollectionView拾遗

在项目中使用UICollectionView的机会比较少,感觉还是比较生疏,所以在ray上又仔细的学习了一遍,记录下笔记

可直接在Storyboard中自定义UICollectionViewCell,设置UICollectionCell的大小可以在Storyboard中设置,也可以通过collectionViewLayout属性

let width = collectionView!.frame.width / 3    let layout = collectionViewLayout as! UICollectionViewFlowLayout    layout.itemSize = CGSize(width: width, height: width)

在Storyboard中点击一个cell,跳转到一个详情页面,有2种方式

1.使用Selection Segue,直接在cell上创建一个segue

这里写图片描述

如果需要传递参数,就重写prepare方法

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {    if segue.identifier == "MasterToDetail" {        if let indexPath = collectionView?.indexPathsForSelectedItems?.first {            if let park = parksDataSource.parkForItemAtIndexPath(indexPath) {                let detailVC = segue.destination as! DetailViewController                detailVC.park = park            }        }    }}

2.使用代理

也可以先重建一个segue,只是不在cell上创建,在主控制器上创建

这里写图片描述

如下

// MARK: UICollectionViewDelegateextension MasterViewController {override func collectionView(_ collectionView: UICollectionView,didSelectItemAt indexPath: IndexPath) {    if let nationalPark = parksDataSource.parkForItemAtIndexPath(indexPath) {        performSegue(withIdentifier: "MasterToDetail", sender:nationalPark)}}}

prepare方法中传递参数

自定义Cell

UICollectionViewCell由三部分组成

UICollectionViewCell

自定义的内容一般式添加到contentView

可使用prepareForReuse()方法来reset属性和prepare subviews
可以有Selected and highlighted states

section

UICollectionViewFlowLayout布局中有headersfooters

要使用SUPPLEMENTARY VIEWS
可自定义,继承UICollectionReusableView,实现方法collectionView(_:viewForSupplementaryElementOfKind:at:)

如下自定义header

class SectionHeaderView: UICollectionReusableView {    @IBOutlet weak var titleLabel: UILabel!    @IBOutlet weak var iconImageView: UIImageView!    var title: String? {        didSet{            titleLabel.text = title        }    }    var icon: UIImage? {        didSet{            iconImageView.image = icon        }    }}

storyboard中定义section header,如下,勾选Section Header,则在collectionView的中出现一个header

这里写图片描述

如下代理方法:

override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {    let sectionHeaderView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "SectionHeader", for: indexPath) as! SectionHeaderView    if let title = parksDataSource.titleForSectionAtIndexPath(indexPath) {        sectionHeaderView.title = title    }    return sectionHeaderView}

通过设置layout.sectionHeadersPinToVisibleBounds = true来设置header在滚动的时候固定在顶部

插入cell

插入cell的一般步骤是:

1.更新你的数据模型

2.更新collection view

  • 使用reloadData方法,则会更新整个可见的cell,并且会丢失原来滚动的位置
  • 使用insertItems(at:)方法,则只会加载插入的cell,并保持滚动的位置

批量的添加或者删除

let indexPathsToInsert = ...let indexPathsToDelete = ...collectionView!.performBatchUpdates({ () -> Void in    self.collectionView!.insertItems(at: [indexPathsToInsert])    self.collectionView!.deleteItems(at: [indexPathsToDelete])}, completion: { (finished) -> Void in    // Do something in the completion block})

如下的例子,先更新模型,然后插入cell

@IBAction func addButtonTapped(_ sender: UIBarButtonItem) {    let indexPath = parksDataSource.indexPathForNewRandomPark();    collectionView?.insertItems(at: [indexPath])}

还可以添加动画,把collectionView?.insertItems(at: [indexPath])放在一个动画块中

UIView.animate(withDuration: 1.0 , delay: 0, usingSpringWithDamping: 0.65, initialSpringVelocity: 0.0, options: UIViewAnimationOptions(), animations:        { () -> Void in            self.collectionView!.insertItems(at: [indexPath])    });

示例的动画效果如下:

动画效果

继承默认的布局

默认布局使用的是UICollectionViewFlowLayout,相当于是一种的网格布局,可继承UICollectionViewFlowLayout

本例要做的是:

1.设置添加cell的起始位置
2.给cell添加一个"边框"
所使用到的方法:

override func initialLayoutAttributesForAppearingItem(at itemIndexPath:IndexPath) -> UICollectionViewLayoutAttributes?

这个方法的作用是返回被插入到collectionView中cell的最初始的布局信息。

当向collection view中插入item的时候,这个方法在每次插入的时候都会被调用。因为这个新的item在collection view中还不可见,所以返回的attribute代表item的初始状态。例如,你可以把属性中位置设在屏幕外或者设置其alpha为0。如果返回nil,layout会把item的最终attribute作为其动画开始和结束的位置。

override func layoutAttributesForElements(in rect: CGRect) ->[UICollectionViewLayoutAttributes]?

这个方法返回的是指定矩形中所有的cell和view的布局attribute。

子类必须要重写这个方法,使用它来返回矩形中所有的item的布局信息,包括cell、supplementary views和decoration views。

最终本例的效果如下,打开模拟器的Slow Animations:

这里写图片描述

主要代码如下:

ParksViewFlowLayout继承自UICollectionViewFlowLayout

class ParksViewFlowLayout: UICollectionViewFlowLayout {    /// 保存即将出现的item的indexPath    var appearingIndexPath: IndexPath?    override func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {        guard let attributes = super.initialLayoutAttributesForAppearingItem(at: itemIndexPath) , let indexPath = appearingIndexPath, indexPath == itemIndexPath else {            return nil        }        //设置最开始的布局属性        attributes.alpha = 1.0        attributes.center = CGPoint(x: collectionView!.frame.width - 23.5, y: -24.5)        attributes.transform = CGAffineTransform(scaleX: 0.15, y: 0.15)        attributes.zIndex = 5        return attributes    }    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {        var cache = [UICollectionViewLayoutAttributes]()        if let layoutAttributes = super.layoutAttributesForElements(in: rect) {            for attribute in layoutAttributes {                let cellAttributes = attribute.copy() as! UICollectionViewLayoutAttributes                if cellAttributes.representedElementKind == nil {//即cell                    let frame = cellAttributes.frame                    cellAttributes.frame = frame.insetBy(dx: 2.0, dy: 3.0)                }                cache.append(cellAttributes)            }        }        return cache    }}

另外在插入一个cell的时候也要做一些改动

@IBAction func addButtonTapped(_ sender: UIBarButtonItem?) {    let indexPath = parksDataSource.indexPathForNewRandomPark()    //collectionView!.insertItems(at: [indexPath])    let layout = collectionViewLayout as! ParksViewFlowLayout    layout.appearingIndexPath = indexPath    UIView.animate(withDuration: 1.0, delay: 0, usingSpringWithDamping: 0.65, initialSpringVelocity: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in      self.collectionView!.insertItems(at: [indexPath as IndexPath])    } , completion: { (finished: Bool) -> Void in        layout.appearingIndexPath = nil    })  }

删除cell

删除cell的步骤:

1.从数据模型中删除数据
2.更新collection view,使用deleteItems(at:)方法,这样做很高效,并会保存滚动的位置

如下:

@IBAction func deleteButtonTapped(_ sender: UIBarButtonItem) {    let indexPaths = collectionView!.indexPathsForSelectedItems! as [IndexPath]    parksDataSource.deleteItemsAtIndexPaths(indexPaths)    collectionView?.deleteItems(at: indexPaths)}

本例的效果如下:

这里写图片描述


UICollectionViewCellisSelected什么时候被调用?

重写UICollectionViewCellisSelected,发现在点击一个cell,push到另一个控制器的时候,发现其调用过程如下:

1.isSelected先被设置为true,然后调用didSelectItemAt方法

2.当pop回来的时候,isSelected被设置为false

UIViewControllereditButtonItemsetEditing(_:animated:)

据,子类使用edit-done必须重写使用这个方法
设置navigationItem.leftBarButtonItem = editButtonItem,所以重写这个方法。

If `true`, the view controller should display an editable view; otherwise, false.If `true` and one of the custom views of the navigationItem property is set to the value returned by the editButtonItem method, the associated navigation controller displays a Done button; otherwise, an Edit button.

本例中,其重写方法如下:

override func setEditing(_ editing: Bool, animated: Bool) {    super.setEditing(editing, animated: animated)    addButton.isEnabled = !editing    collectionView?.allowsMultipleSelection = editing    let indexPaths = collectionView!.indexPathsForVisibleItems as [IndexPath]    for indexPath in indexPaths {        //取消选中的状态deselectItem不会调用代理方法        collectionView?.deselectItem(at: indexPath, animated: false)        let cell = collectionView!.cellForItem(at: indexPath) as? ParkCell        cell?.editing = editing    }    if !editing {        navigationController!.setToolbarHidden(true, animated: animated)    }}

通过设置collectionView?.allowsMultipleSelection = editing来支持多选


在删除cell的时候可以添加动画

在自定义的layou类中,添加一个保存要删除的cell的indexPath的集合

var disappearingItemsIndexPaths: [NSIndexPath]?

实现finalLayoutAttributesForDisappearingItem方法,该方法returns the final layout information for an item that is about to be removed from the collection view.

override func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {    guard let attributes = super.finalLayoutAttributesForDisappearingItem(at: itemIndexPath),        let indexPaths = disappearingItemsIndexPaths,        indexPaths.contains(itemIndexPath as NSIndexPath) else {        return nil    }    attributes.alpha = 1.0    attributes.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)    attributes.zIndex = -1    return attributes}

然后在点击删除按钮时,获取到要删除的indexPath的集合,并添加一个动画块

@IBAction func deleteButtonTapped(_ sender: UIBarButtonItem) {    let indexPaths = collectionView!.indexPathsForSelectedItems! as [IndexPath]    let layout = collectionViewLayout as! ParksViewFlowLayout    layout.disappearingItemsIndexPaths = indexPaths as [NSIndexPath]?    parksDataSource.deleteItemsAtIndexPaths(indexPaths)    UIView.animate(withDuration: 0.65, delay: 0.0, options:        UIViewAnimationOptions(), animations: { () -> Void in            self.collectionView!.deleteItems(at: indexPaths)    }) { (finished: Bool) -> Void in        layout.disappearingItemsIndexPaths = nil    }}

其效果如下:

删除动画

移动cell

iOS9以前,要移动cell,需要添加手势UIGestureRecognizer,并且还需要创建一个自定义的动画。

但是在iOS10中,就不用这么干了,设置self.installsStandardGestureForInteractiveMovement = true,然后实现如下的方法就行:

override func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {        parksDataSource.moveParkAtIndexPath(sourceIndexPath, toIndexPath: destinationIndexPath)    }

效果如下:

移动cell的效果

移动的时候可以设置collectionView?.isPagingEnabled = true支持屏幕滚动

对于iOS9以前的做法,可以参考这篇博客

Cell-Prefetching

iOS10的新特性Cell-Prefetching

1.在显示cell之前预加载
2.调用代理方法加载数据,并配置cell
3.Recycled back to reuse queue
4.可使用UICollectionViewDataSourcePrefetching优化数据的获取

cell的生命周期

cell的生命周期

参考

转载地址:https://windzen.blog.csdn.net/article/details/70146473 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:iOS10适配
下一篇:UIViewController相关

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2024年03月09日 02时50分33秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

docker mysql开机自启动_Docker学习4-学会如何让容器开机自启服务【坑】 2021-06-24
在mysql中删除表正确的是什么_在MySQL中删除表的操作教程 2021-06-24
mysql有3个共同好友_共同好友mysql 2021-06-24
代理查询 mysql_查询数据库代理设置 2021-06-24
mysql dif_mysqldiff实现MySQL数据表比较 2021-06-24
mysql 允许其他主机访问权限_允许其他主机访问本机MySQL 2021-06-24
druid不能close mysql连接_alibaba druid mysql连接问题 2021-06-24
mysql 设置按天分表_MySQL 优化实战记录 2021-06-24
java连接mysql 不推荐_java连接mysql 2021-06-24
mysql数据库 quota_shell脚本抓取用户存储quota写道mysql并展现到grafana面板 2021-06-24
idea测试连接mysql报错08001_IDEA连接MySQL错误 2021-06-24
layui导入模板数据_layui表格-template模板的三种用法 2021-06-24
mysql分组显示行号_mysql 显示行号,以及分组排序 2021-06-24
MySQL常见的主从复制架构_如何搭建经典的MySQL 主从复制架构 2021-06-24
编写python程序、计算账户余额_小明有20w存款存在余额宝中,按余额宝年收益为3.35%计算,用Python编写程序计算,多少年后小明的存款达到30w?... 2021-06-24
python 公众号引流_公众号引流方法有哪些? 2021-06-24
java 减少内存_java中减少内存占用小技巧 2021-06-24
centos 7 mysql图形界面_centos7-vnstat图形界面搭建 2019-04-21
java 防渗透_「java、工程师工作经验怎么写」-看准网 2019-04-21
java中跳出当前循环怎么做_在java中,如何跳出当前的多重循环? 2019-04-21