ios 开发 怎么纪录多选 uicollectionview多选

文档分类:
下载后只包含 1 个 DOC 格式的文档,没有任何的图纸或源代码,
下载前请先预览,预览内容跟原文是一样的,在线预览图片经过高度压缩,下载原文更清晰。
您的浏览器不支持进度条
淘豆网网友近日为您收集整理了关于【无限互联】IOS开发之瀑布流的实现(UICollectionView与UIScrollView)的文档,希望对您的工作和学习有所帮助。以下是文档介绍:【无限互联】IOS开发之瀑布流的实现(UICollectionView与UIScrollView)瀑布流可以在保证图片原始比例的情况下,灵活的展现内容,相对于传统的使用相同大小的网格展现大量图片,要好上很多,而实现瀑布流的方式有很多种,网上比较流行的有三种实现方式。1,使用UIScrollView,主要技术点在于视图的重用。2,使用UITableView,这种方式应该是最易想到的,因为需要展现几列就用几个tabelview就ok了,而且不需要考虑重用,应为苹果已经做好了,只需要考虑如何在几列tabelView滑动的时候,保持同步不出现BUG。3,使用UICollectionView,UICollectionView在iOS6中第一次被介绍,它与UITableView有许多相似点,但它多了一个布局类,而实现瀑布流,就与这个布局类有关。此种方式实现,也不需要考虑视图重用。以上三种方式实现瀑布流,使用UICollectionView应该算是最简单的了,so,就重最简单的开始吧。由于网络太差,所以展现的并不是网络上的图片,而是将用户相册中的图片读取出,并用瀑布流展现。首先,遍历用户相册,将照片放到准备好的数组中。访问用户相册需要导入&AssetsLibrary/AssetsLibrary.h&框架[objc]viewplaincopy1.&spanstyle=&font-size:14&&_images=[NSMutableArrayarray];2.//创建相册库3.library=[[ALAssetsLibraryalloc]init];4.5.[libraryenumerateGroupsWithTypes:ALAssetsGroupSavedPhotosusingBlock:^(ALAssetsGroup*group,BOOLBOOL*stop){6.if(group){7.[groupenumerateAssetsUsingBlock:^(ALAsset*result,NSUIntegerindex,BOOLBOOL*stop){8.if(result){9.ALAssetRepresentation*resentation=[resultdefaultRepresentation];10.CGImageRefimageRef=resentation.fullResolutionI11.UIImage*image=[UIImageimageWithCGImage:imageRef];12.13.//将相片加入到数组中14.[self.imagesaddObject:image];15.[_collectionViewreloadData];16.}17.18.}];19.}20.21.}failureBlock:^(NSError*error){22.23.}];24.&/span&然后创建UICollectionView,注意这里放置UICollectionView的控制器要实现,UICollectionViewDelegateFlowLayout和UICollectionViewDatasource两个协议。UICollectionView创建时需要传入一个布局对象,布局类继承自UICollectionViewLayout这个抽象基类,我们需要自定义布局对象,继承UICollectionViewLayout的子类,UICollectionViewFlowLayout类,命名为WaterFlowLayout。[objc]viewplaincopy1.&spanstyle=&font-size:14&&@interfaceWaterFlowLayout:UICollectionViewFlowLayout2.@property(nonatomic,assign)id&UICollectionViewDelegateFlowLayout&3.4.@end5.&/span&[objc]viewplaincopy1.&spanstyle=&font-size:14&&WaterFlowLayout*layout=[[WaterFlowLayoutalloc]init];2.3._collectionView=[[UICollectionViewalloc]initWithFrame:[UIScreenmainScreen].boundscollectionViewLayout:layout];4.5._collectionView.dataSource=6._collectionView.delegate=7.[self.viewaddSubview:_collectionView];8.9.//注册单元格10.[_collectionViewregisterClass:[WaterfallCellclass]forCellWithReuseIdentifier:indf];11.&/span&设置好代理,下面实现代理协议就行了,在这里同样子类化了UICollectionViewCell,命名为WaterfallCell,并设置一个image属性,便于在协议方法中将照片传入。[objc]viewplaincopy1.&spanstyle=&font-size:14&&@interfaceWaterfallCell:UICollectionViewCell2.@property(nonatomic,retain)UIImage*3.@end&/span&实现UICollectionViewDatasource协议。[objc]viewplaincopy1.&spanstyle=&font-size:14&&-(NSInteger)collectionView:(UICollectionView*)collectionViewnumberOfItemsInSection:(NSInteger)section{2.return_images.3.}4.5.//Thecellthatisreturnedmustberetrievedfromacallto-dequeueReusableCellWithReuseIdentifier:forIndexPath:6.-(UICollectionViewCell*)collectionView:(UICollectionView*)collectionViewcellForItemAtIndexPath:(NSIndexPath*)indexPath{7.WaterfallCell*cell=[collectionViewdequeueReusableCellWithReuseIdentifier:indfforIndexPath:indexPath];8.9.UIImage*image=_images[indexPath.1播放器加载中,请稍候...
该用户其他文档
下载所得到的文件列表【无限互联】IOS开发之瀑布流的实现(UICollectionView与UIScrollView).doc
文档介绍:
【无限互联】IOS开发之瀑布流的实现(UICollectionView与UIScrollView)瀑布流可以在保证图片原始比例的情况下,灵活的展现内容,相对于传统的使用相同大小的网格展现大量图片,要好上很多,而实现瀑布流的方式有很多种,网上比较流行的有三种实现方式。1,使用UIScrollView,主要技术点在于视图的重用。2,使用UITableView,这种方式应该是最易想到的,因为需要展现几列就用几个tabelview就ok了,而且不需要考虑重用,应为苹果已经做好了,只需要考虑如何在几列tabelView滑动的时候,保持同步不出现BUG。3,使用UICollectionView,UICollectionView在iOS6中第一次被介绍,它与UITableView有许多相似点,但它多了一个布局类,而实现瀑布流,就与这个布局类有关。此种方式实现,也不需要考虑视图重用。以上三种方式实现瀑布流,使用UICollectionView应该算是最简单的了,so,就重最简单的开始吧。由于网络太差,所以展现的并不是网络上的图片,而是将用户相册中的图片读取出,并用瀑布流展现。首先,遍历用户相册,将照片放到准备好的数组中。访问用户相册需要导入&AssetsLibrary/AssetsLibrary.h&框架[objc]viewplaincopy1.&spanstyle=&font-size:14&&_images=[NSMutableArrayarray];2.//创建相册库3.libra...
内容来自淘豆网转载请标明出处.ios 开发 怎么纪录多选 uicollectionview_百度知道
ios 开发 怎么纪录多选 uicollectionview
我有更好的答案
不是有delegate 方法么?在里面做文章
其他类似问题
为您推荐:
ios的相关知识
等待您来回答
下载知道APP
随时随地咨询
出门在外也不愁ios开发(34)
项目需要做一个书架功能,期间选取了tableview做控件,还有很多三方的框架,但是深入使用之后发现collectionView的优势在做这样的需求时还是最合适的,一点小感悟把,就想选择控件一样,很多东西其实方向错了,再怎么努力也是徒劳的。鸡汤到此为止,入正题。
首先是建立collectionView和一些他的必要代理,代码如下。
其中cellForItems这个代理中的ShelfLabel是自己定义的一个继承与UICollectionViewCell的类,因为CollectionViewCell没有默认的,所以需要自定义。因为需要正常状态点击跳转书籍阅读页面,长按出现书架编辑页面,所以需要加一个长按手势在这个代理中。
/******************************************CollectionView的创建和代理**************************/
/******************************************CollectionView的创建和代理**************************/
/******************************************CollectionView的创建和代理**************************/
- (void)CreatColletionViewAsShelf
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc]init];
CollectVShelf = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 0, UIScreenWidth, UIScreenHeight-108) collectionViewLayout:flowLayout];
[CollectVShelf registerNib:[UINib nibWithNibName:@&HomeShelfCollectionViewCell& bundle:nil] forCellWithReuseIdentifier:STRIDENT];
CollectVShelf.backgroundColor = [UIColor colorWithRed:241/255.0 green:241/255.0 blue:241/255.0 alpha:1];
[self.view addSubview:CollectVShelf];
CollectVShelf.delegate =
CollectVShelf.dataSource =
//共有多少个单元格
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
return arrIMG.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
HomeShelfCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:STRIDENT forIndexPath:indexPath];
[cell.ShelfimgView setImageWithURL:[NSURL URLWithString:arrIMG[indexPath.row]] placeholderImage:[UIImage imageNamed:@&&]];
cell.Shelflabel.text = arrBookName[indexPath.row];
cell.ShelfimgView.userInteractionEnabled = YES;
cell.m_checkImageView.image = [UIImage imageNamed:@&&];
cell.tag = indexPath.
UILongPressGestureRecognizer *tap = [[UILongPressGestureRecognizer alloc]
initWithTarget:self action:@selector(tapGes:)];
[cell addGestureRecognizer:tap];
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
return CGSizeMake(100, 170);
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
return UIEdgeInsetsMake(20, 20, 0, 20);
而区分这两个点击事件需要一个全局变量来判断,笔者在做这个功能时想了很多奇葩的弯路,诸如长按手势之后再给每一个item加单选手势,施行下去发现难度极大。过程很痛苦就不赘述了,最终选择的方案是定义一个Bool类型的全局变量ClikMode,分为未长按和长按后两种状态,效果也达到了预期
//点击选中
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
if (CLickMode == YES)
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
NSLog(@&%ld&,(long)indexPath.row);
readViewController *readV = [[readViewController alloc] init];
readV.bookID = arrBookID[indexPath.row];;
readV.bookNameStr = @&ef&;
readV.bookUrlStr = @&sdf&;
readV.strChapOrder = @&1&;
readV.bookContent = @[];
readV.bookNewSection = @&&;
readV.bookAuthor = @&&;
[self presentViewController:readV animated:YES completion:nil];
if (CLickMode == NO)
[CollectVShelf deselectItemAtIndexPath:indexPath animated:YES];
HomeShelfCollectionViewCell *cell = (HomeShelfCollectionViewCell*)[collectionView cellForItemAtIndexPath:indexPath];
NSMutableDictionary *dic = [contacts objectAtIndex:indexPath.row];
if ([[dic objectForKey:@&checked&] isEqualToString:@&NO&]) {
[dic setObject:@&YES& forKey:@&checked&];
[cell setChecked:YES];
NSString *str = [NSString stringWithFormat:@&%ld&,(long)indexPath.row];
NSLog(@&%@&,arrBookID[indexPath.row]);
if (mutaArrForDelete)//剔除重复的
if ([mutaArrForDelete containsObject:str])
[mutaArrForDelete addObject:str];
NSLog(@&%@&,mutaArrForDelete);
[dic setObject:@&NO& forKey:@&checked&];
[cell setChecked:NO];
NSString *str = [NSString stringWithFormat:@&%ld&,(long)indexPath.row];
if (mutaArrForDelete)//剔除重复的
if ([mutaArrForDelete containsObject:str])
[mutaArrForDelete removeObject:str];
长按手势的事件代码如下,代码作用是首先将ClikMode状态置为NO,改变点击事件为选择某本书。然后就是navigitonItem的更改,将原来左右两边两个item改为全选和完成两个选项。并且在底部弹出一个新建的view :viewforEdit 用来显示删除和其他的一些自定义事件
-(void)tapGes:(UITapGestureRecognizer*)now
CLickMode = NO;
self.navigationItem.rightBarButtonItem =
self.navigationItem.leftBarButtonItem =
NSArray *anArrayOfIndexPath = [NSArray arrayWithArray:[CollectVShelf indexPathsForVisibleItems]];
NSUInteger row = now.view.
NSMutableDictionary *dic = [contacts objectAtIndex:row];
//一开始将所有状态设置为未选择状态
for (int i = 0; i & [anArrayOfIndexPath count]; i++)
NSIndexPath *indexPath= [anArrayOfIndexPath objectAtIndex:i];
HomeShelfCollectionViewCell *cell = (HomeShelfCollectionViewCell*)[CollectVShelf cellForItemAtIndexPath:indexPath];
if ([[dic objectForKey:@&checked&] isEqualToString:@&NO&]) {
[dic setObject:@&NO& forKey:@&checked&];
[cell setChecked:NO];
[dic setObject:@&NO& forKey:@&checked&];
[cell setChecked:YES];
LogInButton = [[UIBarButtonItem alloc]initWithTitle:@&全选&
style:UIBarButtonItemStylePlain target:self action:@selector(allSelectedBarEditClick:)];
if ([self.navigationItem respondsToSelector:@selector(leftBarButtonItems)]) {
self.navigationItem.leftBarButtonItems = [NSArray arrayWithObjects:LogInButton,
self.navigationItem.leftBarButtonItem = LogInB
UIBarButtonItem *menuButton = [[UIBarButtonItem alloc]initWithTitle:@&完成&
style:UIBarButtonItemStylePlain target:self action:@selector(endEditingBarEditClick:)];
if ([self.navigationItem respondsToSelector:@selector(rightBarButtonItems)]) {
self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:menuButton,nil];
self.navigationItem.rightBarButtonItem = menuB
self.navigationItem.leftBarButtonItem.tintColor = [UIColor whiteColor];
self.navigationItem.rightBarButtonItem.tintColor = [UIColor whiteColor];
viewForEidit = [[UIView alloc]initWithFrame:CGRectMake(0, UIScreenHeight-152, UIScreenWidth, 44)];
viewForEidit.tag = 1001;
viewForEidit.backgroundColor = [UIColor whiteColor];
[self.view addSubview:viewForEidit];
UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(40,10, (UIScreenWidth-200)/2, 24)];
[btn setTitle:@&删除& forState:UIControlStateNormal];
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[viewForEidit addSubview:btn];
btn.tag = 701;
[btn addTarget:self action:@selector(clicktoEdit:) forControlEvents:
UIControlEventTouchUpInside];
UIButton *btn2 = [[UIButton alloc]initWithFrame:CGRectMake(UIScreenWidth-40-(UIScreenWidth-200)/2,10, (UIScreenWidth-200)/2, 24)];
btn2.tag = 702;
[btn2 setTitle:@&其他& forState:UIControlStateNormal];
[btn2 setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[viewForEidit addSubview:btn2];
[btn2 addTarget:self action:@selector(clicktoEdit:) forControlEvents:
UIControlEventTouchUpInside];
全选,完成,还有删除的每个事件如下,我这里用到了数据库删除和界面删除两个删除选项,也有存Plist中这样的方式,大家可以自行在代码中查阅,因为没有代表性就不解释了。其中选择几本书同步删除这个功能需要特别注意,需要用到NSIndexSet,而不是一个简单的For语句就能搞定,因为删除了一本书之后,CollectionView会自动更新,这时你用来记录删除书的标号的数组就不正确了,所以要用到NSIndexSet。
- (void)allSelectedBarEditClick:(UIBarButtonItem *)sender
NSArray *anArrayOfIndexPath = [NSArray arrayWithArray:[CollectVShelf indexPathsForVisibleItems]];
for (int i = 0; i & [anArrayOfIndexPath count]; i++)
NSIndexPath *indexPath= [anArrayOfIndexPath objectAtIndex:i];
HomeShelfCollectionViewCell *cell = (HomeShelfCollectionViewCell*)[CollectVShelf cellForItemAtIndexPath:indexPath];
NSUInteger row = indexPath.
NSLog(@&%lu&,(unsigned long)row);
NSMutableDictionary *dic = [contacts objectAtIndex:row];
if ([LogInButton.title isEqualToString:@&全选&])
[dic setObject:@&YES& forKey:@&checked&];
[cell setChecked:YES];
[dic setObject:@&NO& forKey:@&checked&];
[cell setChecked:NO];
if ([LogInButton.title isEqualToString:@&全选&])
for (NSDictionary *dic in contacts)
[dic setValue:@&YES& forKey:@&checked&];
LogInButton.title = @&取消&;
for (NSDictionary *dic in contacts)
[dic setValue:@&NO& forKey:@&checked&];
LogInButton.title = @&全选&;
- (void)endEditingBarEditClick:(UIButton *)sender
CLickMode = YES;
for (UIView *view in [self.view subviews])
if([view isKindOfClass:[UIView class]]
&&1001 == view.tag)//如果有多个同类型的View可以通过tag来区分
view.hidden = YES;
[view removeFromSuperview];//删除此控件
self.navigationItem.rightBarButtonItem =
self.navigationItem.leftBarButtonItem =
[self navigitionBarItem];
mutaArrForDelete = [[NSMutableArray alloc]init];
[CollectVShelf reloadData];//去除编辑图片的角标
- (void)clicktoEdit:(UIButton*)sender
if (sender.tag == 701)
NSLog(@&delete&);
articleInfoViewController *articleVC = [[articleInfoViewController alloc]init];
NSMutableIndexSet *mut_set = [[NSMutableIndexSet alloc]init];
for (NSString *str in mutaArrForDelete) {
NSInteger a = [str integerValue];
[mut_set addIndex:a];
NSIndexSet *set = [[NSIndexSet alloc]initWithIndexSet:mut_set];
NSMutableArray *IDarrSelected = [[NSMutableArray alloc]init];
for (NSString*str in mutaArrForDelete)
NSInteger t = [str integerValue];
[IDarrSelected addObject:arrBookID[t]];
[articleVC deleteSelected:IDarrSelected];
[arrBookName removeObjectsAtIndexes:set];
[arrIMG removeObjectsAtIndexes:set];
[arrBookID removeObjectsAtIndexes:set];
[downLoadBookAuthor removeObjectsAtIndexes:set];
[downLoadBookNewSection removeObjectsAtIndexes:set];
[downLoadBookContent removeObjectsAtIndexes:set];
[CollectVShelf reloadData];
//这里初始化是为了删除一次之后用户继续再删除,删除的书的序号数组初始化,防止数组崩
mutaArrForDelete = [[NSMutableArray alloc]init];
[ deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
NSLog(@&others&);
上面用到的SetChecked这个方法是在自定义CEll的.m文件中实现的,暴露出接口以供在本类中调用,目的就是实现编辑书架时点几书改变图标的选择和未选择状态
- (void)setChecked:(BOOL)checked{
if (checked)
_m_checkImageView.image = [UIImage imageNamed:@&选中&];
self.backgroundView.backgroundColor = [UIColor colorWithRed:223.0/255.0 green:230.0/255.0 blue:250.0/255.0 alpha:1.0];
_m_checkImageView.image = [UIImage imageNamed:@&未选中&];
self.backgroundView.backgroundColor = [UIColor whiteColor];
m_checked =
谨此记录,欢迎指正。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:3357次
排名:千里之外
原创:26篇
(1)(2)(2)(10)(10)(2)(4)(4)深度好文:UICollectionView自定义布局之风火轮[译]_iOS开发-爱微帮
&& &&& 深度好文:UICollectionVie…
来自Vong的投稿链接:http://vonglo.me//UICollectionView%E8%87%AA%E5%AE%9A%E4%B9%89%E5%B8%83%E5%B1%80%E4%B9%8B%E9%A3%8E%E7%81%AB%E8%BD%AE/现在有许多极具创造力的网站,几周前我碰巧浏览到一个名为Form Follows Function的网站,上面有各种交互动画。其中最吸引我的是网站上的导航转轮,转轮由各种交互体验海报组成。原文:UICollectionView Custom Layout Tutorial: A Spinning Wheel本教程将讲解如何使用自定义的 UICollectionViewFlowLayout 来再现那个导航风火轮。在开始之前,希望你有对 2D 转换、UICollectionView 及其自定义布局的基本知识。如果你对这些还不是很熟悉,推荐你先看看下面几篇教程。UICollectionView Tutorial Part 1: Getting StartedUICollectionView Tutorial Part 2: Reusable Views and Cell SelectionVideo Series: Collection ViewsVideo Series: Custom Collection View Layouts通过学习该教程,你将了解到:从头开始创建自定义collectionView的布局,而不是使用UICollectionViewFlowLayout作为你的基类。view 在其 bounds 之外绕某点旋转那么现在,让我们开搞吧。开始首先下载模板,在 Xcode 中打开,运行。你将看到一系列 cell,每个代表书城中的一本书。下面我们来看看工程目录结构,有一个 CollectionViewController、一个自定义 Cell,cell 中有一个 imageView。然后 VC 被这些 Cell填充。我们的任务就是创建一个UICollectionViewLayout子类来将这些 Cell 按照圆弧排列。理论知识下图是一个带有 cell 的风火轮。黄色区域是 iPhone 的屏幕,蓝色圆角矩形是 cell,红色虚线是你将要放置 cell 的圆弧。你需要三个参数来创建这种排列:1.圆弧半径(radius)2.每个 cell 之间的角度(anglePerItem)3.每个 cell 的角位置你可能已经注意到,并非所有 cell 在屏幕当中能正常显示。假设第0个 cell 的角度为 x 度,那么第1个 cell 的角位置为 x + anglePerItem,第二个为x + anglePerItem * 2,以此类推。第 n 个的角位置的计算公式如下:angle_for_i = x + (i * anglePerItem)下图展示的是角坐标系。0度代表中心,顺时针方向为正,逆时针方向为负。所以0度角的 cell 将处在正中央,完全垂直的方向。现在你对理论知识有了一个全面的理解,让我们开始撸代码吧。Circular Collection View Layout新建一个 swift 文件,取名CircularCollectionViewLayout,继承自UICollectionViewLayout。点击下一步、创建。这个UICollectionViewLayout的子类将包含所有与位置相关的代码。因为CircularCollectionViewLayout继承自UICollectionViewLayout而不是UICollectionViewFlowLayout,所以你需要处理所有布局过程而不是简单调用 super 中的实现。我发现 FlowLayout 非常适合网格视图而非圆形布局。在CircularCollectionViewLayout中,新建两个属性itemSize和radius。let itemSize = CGSize(width: 133, height: 173)var radius: CGFloat = 500 {
invalidateLayout()
}当半径改变时你需要重新计算所有值,所以要在 didSet 中调用invalidateLayout()。在 radius 声明下面紧接着anglePerItem的定义:var anglePerItem: CGFloat {
return atan(itemSize.width / radius)
}anglePerItem可以是你想要的任何值,但是公式要确保 cell 不要被分散的太开。下一步,实现collectionViewContentSize()来声明你的 collectionView 的内容有多大:override func collectionViewContentSize() -& CGSize {
return CGSize(width: CGFloat(collectionView!.numberOfItemsInSection(0)) * itemSize.width,
height: CGRectGetHeight(collectionView!.bounds))
}内容高度与 collectionView 高度一致,但是宽度是itemSize.width * numberOfItems。现在打开Main.storyboard,选中视图大纲中的Collection View,如下图所示打开Attributes Inspector,将其 Layout 设置为自定义,将其 Class 设置为CircularCollectionViewLayout。运行程序,你将发现除了一个可滑动区域外,屏幕上没有任何东西。但是它就是你想要的,因为这确保你正确地将 collectionView 的 Layout 设置为你自定义的 Class 即CircularCollectionViewLayout。自定义布局属性除了新建一个新的布局子类,你还要新建一个继承自UICollectionViewLayoutAttributes的类来存储角位置以及锚点(anchorPoint)。把下面代码加到CircularCollectionViewLayout.swift这个文件中,将其放在CircularCollectionViewLayout类声明上面。class CircularCollectionViewLayoutAttributes: UICollectionViewLayoutAttributes {
var anchorPoint = CGPoint(x: 0.5, y: 0.5)
var angle: CGFloat = 0 {
zIndex = Int(angle * 1000000)
transform = CGAffineTransformMakeRotation(angle)
override func copyWithZone(zone: NSZone) -& AnyObject {
let copiedAttributes: CircularCollectionViewLayoutAttributes =
super.copyWithZone(zone) as! CircularCollectionViewLayoutAttributes
copiedAttributes.anchorPoint = self.anchorPoint
copiedAttributes.angle = self.angle
return copiedAttributes
}1.我们需要一个锚点,因为旋转是围绕锚点而非中心。2.当设置角度(angle)的时候,在内部设置其 transform 旋转 angle 弧度。同时我们想要右边的 cell 覆盖在左边的 cell 上,这个可以通过设置 zIndex 来实现。因为角度用弧度表示,我们将其扩大 1,000,000倍来确保相邻的值不会被四舍五入成同一个 zIndex 值,zIndex 是 Int 型的。3.复写copyWithZone()来遵循NSCopying协议,因为在 collectionView 布局时,内部会拷贝布局属性。复写这个方法来确保复制过程中,anchorPoint 和 angle两个属性也会被拷贝。下面我们回到CircularCollectionViewLayout中来实现 layoutAttributesClass()方法。override class func layoutAttributesClass() -& AnyClass {
return CircularCollectionViewLayoutAttributes.self}这一步是为了告知 collecttionView 你将使用CircularCollectionViewLayoutAttributes而不是默认的UICollectionViewLayoutAttributes。为了持有布局属性,在所有属性声明之后创建一个名为attributesList的数组。var attributesList = [CircularCollectionViewLayoutAttributes]()Preparing the Layout当 collectionView 第一次展示在屏幕上时,Layout 的prepareLayout()方法将被调用。在每次布局生效时这个方法也会被调用。这是布局过程中最重要的方法之一,因为这是创建和存储布局属性的入口。在CircularCollectionViewLayout添加如下代码:override func prepareLayout() {
super.prepareLayout()
let centerX = collectionView!.contentOffset.x + (CGRectGetWidth(collectionView!.bounds) / 2.0)
attributesList = (0..&collectionView!.numberOfItemsInSection(0)).map { (i)
-& CircularCollectionViewLayoutAttributes in
let attributes = CircularCollectionViewLayoutAttributes(forCellWithIndexPath: NSIndexPath(forItem: i,
inSection: 0))
attributes.size = self.itemSize
attributes.center = CGPoint(x: centerX, y: CGRectGetMidY(self.collectionView!.bounds))
attributes.angle = self.anglePerItem*CGFloat(i)
return attributes
}简单来说,我们便利每一个 item,然后执行闭包。下面我们一行行来解释:1.为每个 IndexPath 创建一个CircularCollectionViewLayoutAttributes实例,然后设置其大小(size)2.将 item 放在屏幕中间按弧度来旋转每个 item,旋转量为anglePerItem * i方法中的 map 是 Swift 标准库中的一部分,它创建了一个新的数组,数组中存储的是闭包的执行结果。你可以在这篇文章中了解更多。我们还需要实现下面的方法,这些方法返回在给定矩形区域中的 item 布局属性,以及给定的 indexpath 的 item 布局属性。collectionView 在布局过程中将会多次调用这些方法,在用户滑动 collectionView 也会触发这些方法。为了保证其高效性,我们在prepareLayout()方法中缓存了这些布局属性。把下面代码加到prepareLayout()下面:override func layoutAttributesForElementsInRect(rect: CGRect) -& [AnyObject]? {
return attributesList
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath)
-& UICollectionViewLayoutAttributes! {
return attributesList[indexPath.row]
}第一个方法简单返回了整个布局属性数组,第二个方法返回了指定的 indexpath 对应的布局属性。这个方法非常 OK 因为我们的 item 数目比较小,但是通常我们会遍历数组来判断布局属性的 frame 是否与给定的矩形区域相交,然后返回与给定区域相交的布局属性。这使得 collectionView 在屏幕上只绘制这些 item,或者将要出现在屏幕上的 item。运行,你会看到所有 cell 出现在屏幕上,但是它们是围绕自身来旋转而非外部的某个点。虽然它不是非常急需的效果,但是如果能做到确实挺酷的,你觉得呢?你能猜到为什么会这样吗?有人说是锚点吗?你还记得上面我们说的 cell 的锚点吗?你还没有设置过它,上面的旋转效果远没达到我们希望得到的效果。锚点是 CALayer 的一个属性,所有的旋转和缩放都是围绕着它而发生的。锚点的默认值是 center,就像上面的运行结果那样。真正的锚点的 x 值应该为0.5,y 值应该为radius + (itemSize.height / 2),因为锚点是在归一化坐标系中定义的,所以你要除以itemSize.height。回到prepareLayout(),然后再 centerX 的定义下面定义anchorPointY:let anchorPointY = ((itemSize.height / 2.0) + radius) / itemSize.height在map(_:)闭包中的 return 上方添加如下代码:attributes.anchorPoint = CGPoint(x: 0.5, y: anchorPointY)接着打开open CircularCollectionViewCell.swift,然后复写applyLayoutAttributes(_:) :override func applyLayoutAttributes(layoutAttributes: UICollectionViewLayoutAttributes!) {
super.applyLayoutAttributes(layoutAttributes)
let circularlayoutAttributes = layoutAttributes as! CircularCollectionViewLayoutAttributes
self.layer.anchorPoint = circularlayoutAttributes.anchorPoint
self.center.y += (circularlayoutAttributes.anchorPoint.y - 0.5) * CGRectGetHeight(self.bounds)
}这里你用父类实现来使用默认属性如 center 和 transform 但是因为锚点(anchorPoint)是一个自定义属性,我们需要手动使用它,同样我们也更新了 center.y 来补偿圆形布局中的anchorPoint.y变化。运行程序,你会看到所有的 cell 按照圆形来布局了,但是滑动的过程中…等一下,发生了什么?它们被移出了屏幕而不是旋转!?这使得找到想要的书变得非常困难。改善滑动效果最具挑战性的布局 item 任务已经完成了,可喜可贺!:]现在需要做的就是改变角度值来实现滑动。回到CircularCollectionViewLayout,然后在底部添加下面代码:override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -& Bool {
return true}该方法返回 true 告知 collectionView 在滑动时布局失效,然后它会调用prepareLayout(),进而使用更新后的角位置重新计算 cell 的布局。angle被定义为第0个 item 的角位置。你将要通过把contentOffset.x转换成一个合适的角度值来实现滑动。滑动过程中,contentOffset.x从 0 到collectionViewContentSize().width - CGRectGetWidth(collectionView!.bounds)变化。将contentOffset.x的极值定义为maxContentOffset,当其为 0 时,让第 0 个item 处在中心,当其为极值时(即maxContentOffset),让最后一个 item 处在屏幕中心,这就意味着最后一个 item 的角位置会变为 0 。想象一下右边的场景,如果你是用angle_for_last_item = 0来解决下面等式你会得到:angle_for_last_item = angle_for_zero_item + (totalItems - 1) * anglePerItem
0 = angle_for_zero_item + (totalItems - 1) * anglePerItem
angle_for_zero_item = -(totalItems - 1) * anglePerItem定义-(totalItems - 1) * anglePerItem为angleAtExtreme,如下所示:contentOffset.x = 0, angle = 0contentOffset.x = maxContentOffset, angle = angleAtExtreme由上面,使用下面的公式非常容易计算任意contentOffset.x对应的角度:angle = -angleAtExtreme * contentOffset.x / maxContentOffset脑海中回想以下这些算式,把下面代码添加到 itemSize 的声明下:var angleAtExtreme: CGFloat {
return collectionView!.numberOfItemsInSection(0) & 0 ?
-CGFloat(collectionView!.numberOfItemsInSection(0) - 1) * anglePerItem : 0}var angle: CGFloat {
return angleAtExtreme * collectionView!.contentOffset.x / (collectionViewContentSize().width -
CGRectGetWidth(collectionView!.bounds))
}接下来使用attributes.angle = self.angle + (self.anglePerItem * CGFloat(i))来替换prepareLayout()中的attributes.angle = (self.anglePerItem * CGFloat(i))这一步添加为每个 item 添加了角度值,这样 item 的角度值不在是一个常量,而是一个与contentOffset.x有着函数关系的值。运行程序,在屏幕上滑动,你将发现所有 item 按照你想要的方式在滑动。干得漂亮!加分环节:优化你已经成功的重现了风火轮导航,现在可以在拍拍自己肩膀说一句干得漂亮,然后架着二郎腿享受这美好时光。但是在存在优化空间的情况(滑动丝滑般流畅)下你为什么要停下来呢?在prepareLayout()中为每个 item 创建了一个CircularCollectionViewLayoutAttributes实例,但是不是所有的 item 都会立刻展示在屏幕上。那些离屏的 item,你可以完全跳过对它们的计算,也不必创建CircularCollectionViewLayoutAttributes实例。但是有一个棘手的问题是:我们需要确定哪些 item 正在屏幕上显示,哪些是离屏的。如下图所示,在 (-θ, θ)范围之外的所有 item 都是离屏的。举个栗子,为了计算三角形 ABC 中的 θ 角,可以使用下面公式:tanθ = (collectionView.width / 2) / (radius + (itemSize.height / 2) - (collectionView.height / 2))在prepareLayout()中的anchorPointY下一行加入如下代码:let theta = atan2(CGRectGetWidth(collectionView!.bounds) / 2.0,
radius + (itemSize.height / 2.0) - (CGRectGetHeight(collectionView!.bounds) / 2.0))var startIndex = 0var endIndex = collectionView!.numberOfItemsInSection(0) - 1 if (angle & -theta) {
startIndex = Int(floor((-theta - angle) / anglePerItem))
}endIndex = min(endIndex, Int(ceil((theta - angle) / anglePerItem)))if (endIndex & startIndex) {
endIndex = 0
startIndex = 0}这一步我们做了什么?1.使用反正切函数计算theta角2.初始化startIndex及endIndex3.如果第0个 item 的角位置小于 -theta,那么它就是离屏的,屏上第 1 个 item 的 index 将为 -θ 与 angle 的差值再除以 anglePerItem4.同样的,屏幕上最后一个 item 是θ 与 angle 的差值再除以 anglePerItem,min 是保证endIndex不会越界5.最后做了一个容错处理,防止在快速滑动时所有 cell 都离屏时导致 endIndex小于 startIndex的情况下图把上面的计算过程可视化:既然我们知道了哪些正在显示,哪些是离屏的,我们需要更新用来计算布局属性的起始和结束的 index。使用attributesList = (startIndex...endIndex).map { (i)
-& CircularCollectionViewLayoutAttributes in来替换prepareLayout()中的:attributesList = (0..&collectionView!.numberOfItemsInSection(0)).map { (i)
-& CircularCollectionViewLayoutAttributes in运行程序,你会发现视觉上没有明显变化,因为所有的改变仅仅影响离屏的 item。我们可以打开 Xcode 内置的视图层级查看器因为创建了更少的变量,你应该可以看到性能的提升。何去何从你可以在此 : /wp-content/uploads/2015/06/CircularCollectionView-Final.zip下载完整代码。恭喜,你已经成功使用了自定义的 Layout 来实现一个导航风火轮。在这篇教程中你应该学到不少东西,包括如何旋转 view、改变锚点、从头创建自定义的 Layout 以及如何优化让它变得更好。你可以更改radius和anglePerItem来进一步了解它们是如何来改变最终的圆形布局排列的。这篇教程主要是改变2D 的 transform,你也可以使用3D transform 来创建更有趣的效果。同样你也可以通过复写argetContentOffsetForProposedContentOffset(_:withScrollingVelocity:)方法来实现snapping行为。我相信你已经开始跃跃欲试了吧?如果你遇到问题,可以参考下面的代码:override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -& CGPoint {
var finalContentOffset = proposedContentOffset
let factor = -angleAtExtreme/(collectionViewContentSize().width -
CGRectGetWidth(collectionView!.bounds))
let proposedAngle = proposedContentOffset.x*factor
let ratio = proposedAngle/anglePerItem
var multiplier: CGFloat
if (velocity.x & 0) {
multiplier = ceil(ratio)
} else if (velocity.x & 0) {
multiplier = floor(ratio)
multiplier = round(ratio)
finalContentOffset.x = multiplier*anglePerItem/factor
return finalContentOffset
}如果你有任何疑问、评论或者炫技,请加入下面的讨论。由于不能增加外联,请点击“阅读原文”阅读
点击展开全文
最新iOS、iPhone资讯,万名iOS开发者、swift开发、果粉聚集,参与技术讨论,整理开发技巧,分享创业经验!享受生活、热爱编程!
您的【关注和订阅】是作者不断前行的动力
本站文章来自网友的提交收录,如需删除可发送邮件到 bang@ 或联系QQ ,
(C)2014&&版权所有&&&|&&&
京ICP备号-2&&&&京公网安备34

我要回帖

更多关于 uicollectionview分页 的文章

 

随机推荐