原文链接:UICollectionView 总结

项目源码:模拟凤凰新闻 Github 仓库

引语

昨天给自己布置这个作业之后,看完文档实践的过程中发现一片很棒的英文总结,于是翻译了一下。这篇总结会简单总结一下我翻译的那篇文章里的内容,以及基于模拟凤凰新闻客户端部分页面的一些 UICollectionView 使用总结。

文章主要是总结一些需要注意的内容,具体请看源码。实现的内容及对应文件包括:

  • 同一个 section 内拖动 cell

    • 直接使用 UICollectionViewController(TestCollectionViewController.swift

    • 在 UIViewController 中使用 UICollectionView(EditTabsViewController.swift

  • 不同 section 间拖动 cell(Test.swift

  • 不同 section 间点击移动 cell(TabsViewController.swift

  • 点击移除 cell(EditTabsViewController.swift

主要内容如图:

《【译】UICollectionView 轻松重排》

这篇文章主要介绍了在 iOS9 之后 UICollectionView 自带的重新排列方法。

  1. 如果直接使用 UICollectionViewController,通过重写`func collectionView(collectionView: UICollectionView,

    moveItemAtIndexPath sourceIndexPath: NSIndexPath,toIndexPath destinationIndexPath: NSIndexPath) `即可以实现拖动重排。
  2. 如果是在 UIViewController 里面使用 UICollectionView,则需要自己添加一个 UILongPressGestureRecognizer,对应状态进行对应处理。

比较重要的几个方法是:

  • func collectionView(collectionView: UICollectionView,toIndexPath destinationIndexPath: NSIndexPath)

  • indexPathForItemAtPoint

  • beginInteractiveMovementForItemAtIndexPath

  • updateInteractiveMovementTargetPosition

  • endInteractiveMovement

  • cancelInteractiveMovement

特别注意

这个方法`func collectionView(collectionView: UICollectionView,

moveItemAtIndexPath sourceIndexPath: NSIndexPath,toIndexPath destinationIndexPath: NSIndexPath)`,
  • 重写这个方法之后,自带的拖动重排才能生效。

  • 这个方法究竟有什么作用?这个方法是在 cell 位置变换之后触发的。它包含两个很有用的参数,被拖动的 cell 的初始 indexPath 和落点 indexPath。因为这个位置的变换只是视图的改变,这些 cell 背后的数据的 index 其实并没有受到影响。因此如果此时 reloadData() 会发现,格子位置又恢复了,但这不是我们想要的,在实际项目中我们希望移动后就一直保持那个位置,也就是说数据的 index 发生相应改变。这个方法就是方便我们处理数据的。具体请看之后内容中的例子。

  • 另外这个方法只与通过交互移动 cell 事件有关。如果是直接调用移动 cell 的方法并不会触发这个方法。所以在类似凤凰新闻编辑订阅频道页面,”点击下面 section 中的频道,移动到上面的 section 中”,实现时需要在 didSelect 方法中添加对应修改数据源的代码。具体参看源码中TabsViewController.swift

使用 UICollectionView 必做的事情

首先你的 UIViewController 要继承 UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout。

其次,记得绑定 delegate 和 datasource。

然后是:

  • func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int

  • collectionView(collectionView: UICollectionView,numberOfItemsInSection section: Int) -> Int

  • collectionView(collectionView: UICollectionView,cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell

有需要的话用上:

  • func collectionView(collectionView: UICollectionView,layout collectionViewLayout: UICollectionViewLayout,sizeforItemAtIndexPath indexPath: NSIndexPath) -> CGSize

  • func collectionView(collectionView: UICollectionView,viewForSupplementaryElementOfKind kind: String,atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView

特别注意

当你在 storyboard 设置了使用 header 或者 footer 或者两者都用的时候,记得添加对应的内容在 viewForSupplementaryElementOfKind 里面。例如:

func collectionView(collectionView: UICollectionView,atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
    
    if kind == UICollectionElementKindSectionHeader {
        let header = collectionView.dequeueReusableSupplementaryViewOfKind(kind,withReuseIdentifier: "TabSectionHeader",forIndexPath: indexPath) as! TabSectionHeader
        return header
    } else {
        let footer = collectionView.dequeueReusableSupplementaryViewOfKind(kind,withReuseIdentifier: "TabSectionFooter",forIndexPath: indexPath)
        return footer
    }
}

个人对这个方法的设计持怀疑态度,觉得像 UITableView 那样分离开会更好。(又或许是我理解不够深刻吧。)

记得设置 reuseIdentifier。

插入、移动、删除 cell 以及 cell 总数的问题

UICollectionView 是在生成 cell 的时候,先通过 numberOfItemsInSection 获得 cell 数量,然后一个一个生成添加在视图中。

你可通过这些方法来插入、移动、删除 cell:

  • insertItemAtIndexPaths

  • moveItemAtIndexPath

  • deleteItemsAtIndexPaths

比如,当来自服务器的数据更新了,新增或者减少了一个数据,我们可以想到有两种情况:

  1. 通过 reloadData() 将整个 UICollectionView 更新。

  2. 只在对应的位置插入或删除对应的那一个 cell。

用第一种方法是没有任何问题的。问题在第二种方法。

当我们直接通过 insertItemAtIndexPaths 或者 deleteItemsAtIndexPaths 添加或删除 cell 时,UICollectionView 中的 cell 数量发生变化了。貌似没问题?如果你尝试滑动一下屏幕,你会发现程序崩溃了。你会看到类似下面的报错:

原因在于,当 UICollectionView 进行任何的更新时,包括局部更新,都会检查 numberOfItemInSection 方法返回的值和当前 UICollectionView 中实际包含的 cell 数量。如果二者不一致就会报错。

特别注意

UICollectionView 中实际包含的 cell 数量在下一次更新前 collection view 视图前一定要和 numberOfItemInSection 的返回值一直。

所以我们在新增或者删除 cell 之后,记得要修改对应的数据源。(当然在实际项目中应该不会忘记。)

插入、移动、删除 section 类似

不同 section 间拖动 cell

项目中的 Test.swift 是关于不同 section 间拖动 cell 的例子。

基本原理和在一个 section 内拖动 cell 一样,都是调用那几个方法。

第一点

需要注意的还是上面提到的记得修改对应数据源,否则第二次拖动就会报错。因为此时两个 section 内 cell 数量和 numberOfItemInSection 返回值不一样了。

第二点

请看一下两个实现方法:

一,『原始』方法

func longPressGestureRecognizerAction(sender: UILongPressGestureRecognizer) {
    switch sender.state {
    case .Began:
        let location = sender.locationInView(self.collectionView)
        let indexPath = self.collectionView.indexPathForItemAtPoint(location)
        self.originalSectionIndex = (indexPath?.section)!
        self.interactiveItem = self.collectionView.cellForItemAtIndexPath(indexPath!)
        self.collectionView.beginInteractiveMovementForItemAtIndexPath(indexPath!)
        break
    case .Changed:
        let location = sender.locationInView(self.collectionView)
        print(location)
        let indexPath = self.collectionView.indexPathForItemAtPoint(location)
        print(indexPath)
        self.collectionView.updateInteractiveMovementTargetPosition(location)
    case .Ended:
        self.collectionView.endInteractiveMovement()
        let currentSectionIndex = (self.collectionView.indexPathForCell(self.interactiveItem)?.section)!
        self.sections[currentSectionIndex]++
        self.sections[self.originalSectionIndex]--
    default:
        self.collectionView.cancelInteractiveMovement()
        break
    }
}

二,借助自带方法的简便方法

func collectionView(collectionView: UICollectionView,moveItemAtIndexPath sourceIndexPath: NSIndexPath,toIndexPath destinationIndexPath: NSIndexPath) {
    self.sections[destinationIndexPath.section]++
    self.sections[sourceIndexPath.section]--
}

func longPressGestureRecognizerAction(sender: UILongPressGestureRecognizer) {
    switch sender.state {
    case .Began:
        guard let selectedindexPath = self.collectionView.indexPathForItemAtPoint(sender.locationInView(self.collectionView)) else {
            break
        }
        self.collectionView.beginInteractiveMovementForItemAtIndexPath(selectedindexPath)
        break
    case .Changed:
        self.collectionView.updateInteractiveMovementTargetPosition(sender.locationInView(self.collectionView))
        break
    case .Ended:
        self.collectionView.endInteractiveMovement()
    default:
        self.collectionView.cancelInteractiveMovement()
        break
    }
}

第一个方法定义了两个全局变量var originalSectionIndex = 0var interactiveItem:UICollectionViewCell!来记录初始位置和正在进行移动的 cell。

而第二个方法,通过使用func collectionView(collectionView: UICollectionView,toIndexPath destinationIndexPath: NSIndexPath),直接就可以使用开始和结束位置 indexPath。非常方便。

所以当然一定要用第二种方法。

UICollectionView 总结的更多相关文章

  1. 移动HTML5前端框架—MUI的使用

    这篇文章主要介绍了移动HTML5前端框架—MUI的使用的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. HTML5 weui使用笔记

    这篇文章主要介绍了HTML5 weui使用笔记,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  3. Html5写一个简单的俄罗斯方块小游戏

    这篇文章主要介绍了基于Html5写一个简单的俄罗斯方块小游戏,本文通过图文并茂的形式给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友参考下吧

  4. ios – UICollectionView在帧更改后错误地显示单元格

    我错过了什么吗?

  5. ios – UITableView和Cell Reuse

    这是我的CustomCell类的init方法解决方法如果没有要显示的图像,则必须清除图像视图:

  6. ios – fetchedResultsController.fetchedObjects.count = 0但它充满了对象

    我正在使用相当标准的fetchedResultsController实现来输出tableView.在-viewDidLoad的最后,我正在进行第一次调用:这是我的fetchedResultsController:我的tableView方法:所以,问题是:在_fetchedResultsController.fetchedobjects.count的日志中等于0,但在视觉上tableView充满了对

  7. ios – 如何将UICollectionViewCell从一个UICollectionView拖到另一个UICollectionView?

    如果是这样,我将如何实施它?

  8. ios – 如何实现`prepareForReuse`?

    解决方法尝试将此添加到您的MGSwipeTableCell.m:

  9. ios – 我的表视图在滚动时在SWIFT中重用所选单元格

    实例变量

  10. ios – 在uicollectionview底部添加加载指示符

    解决方法不,没有“内置”的方式.您需要做的是有一个包含加载器的额外单元格.检测此单元格何时出现非常简单,此时您可以启动调用以加载更多数据.

随机推荐

  1. Swift UITextField,UITextView,UISegmentedControl,UISwitch

    下面我们通过一个demo来简单的实现下这些控件的功能.首先,我们拖将这几个控件拖到storyboard,并关联上相应的属性和动作.如图:关联上属性和动作后,看看实现的代码:

  2. swift UISlider,UIStepper

    我们用两个label来显示slider和stepper的值.再用张图片来显示改变stepper值的效果.首先,这三个控件需要全局变量声明如下然后,我们对所有的控件做个简单的布局:最后,当slider的值改变时,我们用一个label来显示值的变化,同样,用另一个label来显示stepper值的变化,并改变图片的大小:实现效果如下:

  3. preferredFontForTextStyle字体设置之更改

    即:

  4. Swift没有异常处理,遇到功能性错误怎么办?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  5. 字典实战和UIKit初探

    ios中数组和字典的应用Applicationschedule类别子项类别名称优先级数据包contactsentertainment接触UIKit学习用Swift调用CocoaTouchimportUIKitletcolors=[]varbackView=UIView(frame:CGRectMake(0.0,0.0,320.0,CGFloat(colors.count*50)))backView

  6. swift语言IOS8开发战记21 Core Data2

    上一话中我们简单地介绍了一些coredata的基本知识,这一话我们通过编程来实现coredata的使用。还记得我们在coredata中定义的那个Model么,上面这段代码会加载这个Model。定义完方法之后,我们对coredata的准备都已经完成了。最后强调一点,coredata并不是数据库,它只是一个框架,协助我们进行数据库操作,它并不关心我们把数据存到哪里。

  7. swift语言IOS8开发战记22 Core Data3

    上一话我们定义了与coredata有关的变量和方法,做足了准备工作,这一话我们来试试能不能成功。首先打开上一话中生成的Info类,在其中引用头文件的地方添加一个@objc,不然后面会报错,我也不知道为什么。

  8. swift实战小程序1天气预报

    在有一定swift基础的情况下,让我们来做一些小程序练练手,今天来试试做一个简单地天气预报。然后在btnpressed方法中依旧增加loadWeather方法.在loadWeather方法中加上信息的显示语句:运行一下看看效果,如图:虽然显示出来了,但是我们的text是可编辑状态的,在storyboard中勾选Editable,再次运行:大功告成,而且现在每次单击按钮,就会重新请求天气情况,大家也来试试吧。

  9. 【iOS学习01】swift ? and !  的学习

    如果不初始化就会报错。

  10. swift语言IOS8开发战记23 Core Data4

    接着我们需要把我们的Rest类变成一个被coredata管理的类,点开Rest类,作如下修改:关键字@NSManaged的作用是与实体中对应的属性通信,BinaryData对应的类型是NSData,CoreData没有布尔属性,只能用0和1来区分。进行如下操作,输入类名:建立好之后因为我们之前写的代码有些地方并不适用于coredata,所以编译器会报错,现在来一一解决。

返回
顶部