UICollectionView:如何在委托方法中设置特定项目后获取项目大小

Posted

技术标签:

【中文标题】UICollectionView:如何在委托方法中设置特定项目后获取项目大小【英文标题】:UICollectionView: How to get item size for a specific item after setting them in delegate method 【发布时间】:2012-09-25 14:27:04 【问题描述】:

我正在摆弄新的 UICollectionView 和 UICollectionViewLayout 类。我创建了一个自定义布局,继承 UICollectionViewFlowLayout。

我的单元格大小是动态变化的,我使用下面的委托方法设置项目大小

- (CGSize)collectionView:(UICollectionView *)collectionView
                  layout:(UICollectionViewLayout*)collectionViewLayout
  sizeForItemAtIndexPath:(NSIndexPath *)indexPath 

      NSLog(@"SETTING SIZE FOR ITEM AT INDEX %d", indexPath.row);
      return CGSizeMake(80, 80);

现在,在我的自定义 UICollectionViewFlowLayout 类的 prepareLayout 方法下,我需要访问这些大小变量,以便计算如何放置它们并为 layoutAttributesForItemAtIndexPath 缓存它们。

但是,我似乎在 UICollectionView 或 UICollectionViewFlowLayout 下找不到任何属性来达到我在委托方法中设置的自定义项目大小。

【问题讨论】:

可以自己调用委托方法 不能这样做,它 Xcode 不验证这一点: CGSize size = [self.collectionView sizeForItemAtIndexPath: [NSIndexPath indexPathForItem:i inSection:0]]; 你必须打电话给[self collectionView:self.collectionView layout:self.collectionView.collectionViewLayout sizeForItemAtIndexPath:indexPath] 收到此错误:“SECollectionViewCustomLayout”没有可见的@interface 声明选择器“collectionView:layout:sizeForItemAtIndexPath:” 对不起,你在布局类。比你必须打电话给[self.collectionView.delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath] 【参考方案1】:

自己找到的。

在不省略UICollectionViewDelegateFlowLayout的情况下实现自定义类

@interface SECollectionViewCustomLayout : UICollectionViewFlowLayout 
                                          <UICollectionViewDelegateFlowLayout>

然后你就可以打电话了

CGSize size = [self collectionView:self.collectionView 
                            layout:self 
            sizeForItemAtIndexPath:indexPath];

【讨论】:

-1 使布局成为自己的委托是一种奇怪的模式。即使您真的想这样做,您也应该通过调用self.collectionView.delegate 来获取大小信息,而不是假设self 将始终是委托。如果您后来决定使用不同的对象作为委托怎么办?很有可能你最终会得到这段代码,将不同的大小返回给实际的委托。即使删除对委托协议的一致性也不会导致编译器警告,因为self 无论如何都会实现该方法。 我使用的是 Swift 而不是 ObjC,但为什么不让您的委托对象同时符合 UICollectionViewDelegateUICollectionViewDelegateFlowLayout?然后你可以做类似 if let d = self.collectionView?.delegate as? UICollectionViewDelegateFlowLayout 在布局类中,它就像一个魅力【参考方案2】:

查看各种UICollectionView... 头文件,并观看WWDC 2012 Session 219 - Advanced Collection Views and Building Custom Layouts 视频(从大约6:50 开始),似乎可扩展委托模式利用动态类型来确保布局可以正确访问其扩展委托方法。

总之...

    如果您使用自己的委托定义自定义布局,请在布局的头文件中定义该委托协议。 您的委托对象(通常是管理集合视图的UI(Collection)ViewController)应声明自己以支持此自定义协议。 如果您的布局只是 UICollectionViewFlowLayout 或其子类,这仅意味着声明符合 UICollectionViewDelegateFlowLayout 如果您不想将布局标头 #import 放入代理界面,请随意在 .m 文件中的类扩展中执行此操作。 要从布局中访问委托方法,请调用集合视图的委托。 使用布局的 collectionView 属性,并将委托转换为符合所需协议的对象,以说服编译器。 在调用可选委托方法之前,不要忘记像往常一样检查委托respondsToSelector:。事实上,如果你愿意,对所有方法都这样做并没有什么坏处,因为类型转换意味着没有运行时保证委托甚至会实现所需的方法。

在代码中...

因此,如果您实现的自定义布局需要委托来获取其某些信息,则您的标题可能如下所示:

@protocol CollectionViewDelegateCustomLayout <UICollectionViewDelegate>
- (BOOL)collectionView:(UICollectionView *)collectionView
                layout:(UICollectionViewLayout *)layout
shouldDoSomethingMindblowingAtIndexPath:(NSIndexPath *)indexPath;
@end

@interface CustomLayout : UICollectionViewLayout
// ...
@end

你的委托声明了一致性(我在这里的实现文件中这样做了):

#import "CustomLayout.h"

@interface MyCollectionViewController () <CollectionViewDelegateCustomLayout>
@end

@implementation
// ...
- (BOOL)collectionView:(UICollectionView *)collectionView
                layout:(UICollectionViewLayout *)layout
shouldDoSomethingMindblowingAtIndexPath:(NSIndexPath *)indexPath

    return [self canDoSomethingMindblowing];

// ...
@end

在您的布局实现中,您可以像这样访问该方法:

BOOL blowMind;
if ([self.collectionView.delegate respondsToSelector:@selecor(collectionView:layout:shouldDoSomethingMindblowingAtIndexPath:)]) 
    blowMind = [(id<CollectionViewDelegateCustomLayout>)self.collectionView.delegate collectionView:self.collectionView
                                                                                             layout:self
                                                            shouldDoSomethingMindblowingAtIndexPath:indexPath];
 else 
    // Perhaps the layout also has a property for this, if the delegate
    // doesn't support dynamic layout properties...?
    // blowMind = self.blowMind;

请注意,在这里进行类型转换是安全的,因为无论如何我们都会事先检查委托对该方法的响应。

证据...

这只是猜测,但我怀疑这是 Apple 管理UICollectionViewDelegateFlowLayout 协议的方式。

流布局上没有 delegate 属性,因此调用必须通过集合视图的委托。 UICollectionViewController 不公开遵守扩展流布局委托(我怀疑它在另一个私有标头中这样做)。 UICollectionViewdelegate 属性仅声明符合“基本”UICollectionViewDelegate 协议。同样,我怀疑流布局使用UICollectionView 的私有子类/类别来防止需要进行类型转换。为了进一步强调这一点,Apple 不鼓励在文档 (Collection View Programming Guide for ios: Creating Custom Layouts) 中对 UICollectionView 进行子类化:

避免继承 UICollectionView。集合视图很少或没有自己的外观。相反,它会从您的数据源对象中提取所有视图,并从布局对象中提取所有与布局相关的信息。

所以我们开始了。并不复杂,但值得了解如何以范式友好的方式进行操作。

【讨论】:

【参考方案3】:

有一个快捷版本:

self.collectionView(self.collectionView, layout: self.collectionView.collectionViewLayout, sizeForItemAtIndexPath: indexPath)

【讨论】:

【参考方案4】:

查看 GitHub 上的 UICollectionView-FlowLayout。同样的想法,这只是让访问 flowLayout 的扩展委托方法更加简洁。

【讨论】:

【参考方案5】:

对于后来的读者,IOS 7 有 UICollectionViewFlowLayout 已经定义了。

【讨论】:

【参考方案6】:

在我的情况下,关于布局、单元格布局等的所有内容都在 UIViewController 的 nib 和 UICollectionViewCell 的单独 nib 中定义。 MyCollectionViewCell 包含 UIImageView 自动布局到具有填充/边距但方形的单元格。

我需要圆形图标而不是方形图标,但不想关心我为 iPhone 或 iPad 使用哪个笔尖(我有单独的笔尖用于设备和方向)。

我不想在我的视图控制器中实现@selector(collectionView:layout:sizeForItemAtIndexPath:)

所以,在collectionView:cellForItemAtIndexPath: 我可以使用

CGSize size = cell.imageView.bounds.size;
cell.imageView.layer.masksToBounds = YES;
cell.imageView.layer.cornerRadius = size.height/2.0;

因为collectionView:layout:sizeForItemAtIndexPath:collectionView:cellForItemAtIndexPath: 之前调用并完成了布局。

你可以在底部查看圆形头像

【讨论】:

以上是关于UICollectionView:如何在委托方法中设置特定项目后获取项目大小的主要内容,如果未能解决你的问题,请参考以下文章

在实例化单元格之前调用 UICollectionView 委托方法?

UICollectionView 委托方法没有被正确调用

UICollectionView 委托方法的问题

在 UITableviewCell 中使用 UICollection 视图时未调用 UICollectionView 委托方法“didSelectItemAtIndexPath”

未调用 UICollectionView 内部 UITableView 的委托方法

为啥 UICollectionView 的委托方法不起作用?