
本文共 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
由三部分组成
自定义的内容一般式添加到contentView
上
prepareForReuse()
方法来reset
属性和prepare subviews
可以有Selected and highlighted states
section
UICollectionViewFlowLayout
布局中有headers
和footers
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)}
本例的效果如下:
UICollectionViewCell
的isSelected
什么时候被调用?
UICollectionViewCell
的isSelected
,发现在点击一个cell,push到另一个控制器的时候,发现其调用过程如下: 1.isSelected
先被设置为true
,然后调用didSelectItemAt
方法
pop
回来的时候,isSelected
被设置为false
UIViewController
的editButtonItem
与setEditing(_: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) }
效果如下:
移动的时候可以设置collectionView?.isPagingEnabled = true
支持屏幕滚动
对于iOS9以前的做法,可以参考这篇博客
Cell-Prefetching
iOS10
的新特性Cell-Prefetching
UICollectionViewDataSourcePrefetching
优化数据的获取 cell的生命周期
参考
转载地址:https://windzen.blog.csdn.net/article/details/70146473 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!
发表评论
最新留言
关于作者
