我有一些数据是在另一个更新 UICollectionView 标头的线程中获取的。但是,我还没有找到重新加载补充视图(例如页眉或页脚)的有效方法。
我可以打电话collectionView reloadSections:
,但这会重新加载整个部分,这是不必要的。collectionView reloadItemsAtIndexPaths:
似乎只针对细胞(不是补充观点)。并且调用setNeedsDisplay
标题本身似乎也不起作用。我错过了什么吗?
我有一些数据是在另一个更新 UICollectionView 标头的线程中获取的。但是,我还没有找到重新加载补充视图(例如页眉或页脚)的有效方法。
我可以打电话collectionView reloadSections:
,但这会重新加载整个部分,这是不必要的。collectionView reloadItemsAtIndexPaths:
似乎只针对细胞(不是补充观点)。并且调用setNeedsDisplay
标题本身似乎也不起作用。我错过了什么吗?
你也可以使用(懒惰的方式)
collectionView.collectionViewLayout.invalidateLayout() // swift
[[_collectionView collectionViewLayout] invalidateLayout] // objc
更复杂的是提供上下文
collectionView.collectionViewLayout.invalidateLayout(with: context) // swift
[[_collectionView collectionViewLayout] invalidateLayoutWithContext:context] // objc
然后,您可以自己创建或配置上下文以告知应更新的内容:UICollectionViewLayoutInvalidationContext
它有一个可以覆盖的功能:
invalidateSupplementaryElements(ofKind:at:) // swift
另一种选择是(如果您已经加载了正确的页眉/页脚/补充视图)并且您只想使用新数据更新视图,而不是可以使用以下函数之一来检索它:
supplementaryView(forElementKind:at:) // get specific one
visibleSupplementaryViews(ofKind:) // all visible ones
可见单元格也是如此visibleCells
。仅获取视图而不完全重新加载视图的优点是单元格保留了它的状态。当表格视图单元格使用滑动来删除/编辑/等时,这特别好,因为重新加载单元格后该状态会丢失。
如果您感到狂热,您当然也可以编写一些扩展来使用泛型仅检索给定类型的单元格/补充视图
if let view = supplementaryView(forType: MySupplementaryView.self, at: indexPath) {
configure(view, at indexPath)
}
这假设您有一个函数,该函数在示例中使用其类名注册/出列视图。我在这里发了一篇关于这个的帖子
我刚刚遇到了同样的问题,最后我使用它的标签来查找视图来编辑标签:
UICollectionReusableView *footer = (UICollectionReusableView*)[self.collectionView viewWithTag:999];
UILabel *footerLabel = (UILabel*)[footer viewWithTag:100];
就像你说的,没有必要重新加载整个部分,这也会取消单元格上的任何动画。我的解决方案并不理想,但很简单。
斯威夫特 3/4/5版本:
collectionView.collectionViewLayout.invalidateLayout()
警告!
如果您collectionView
同时更改项目数(例如,仅当所有单元格都已加载时才显示页脚),它将崩溃。您需要先重新加载数据,以确保前后的项目数相同invalidateLayout()
:
collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()
我遇到了同样的问题。我尝试了@BobVorks 的答案,它工作正常,如果只有单元被重用,否则它不会。因此,我尝试找到一种更简洁的方法来实现这一点,并且在 performBatchUpdate (完成块)之后重新加载了整个 UICollectionView 并且效果很好。它重新加载集合而不取消 insertItemsAtIndexPath 中的动画。实际上,我个人对最近的 2 个答案投了赞成票,因为我发现它有效,但就我而言,这是最干净的方法。
[self.collectionView performBatchUpdates:^{
// perform indexpaths insertion
} completion:^(BOOL finished) {
[self.collectionView reloadData];
}];
[UIView performWithoutAnimation:^{
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:4]];
}];
[UIView performWithoutAnimation:^{
[self.collectionView reloadData];
}];
这里有两种方法可以做到。
1. 创建一个可变模型来支持最终可用的数据。在 UICollectionReusableView 的继承类中使用 KVO 来观察变化并在新数据可用时使用新数据更新标题视图。
[model addObserver:headerView
forKeyPath:@"path_To_Header_Data_I_care_about"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:NULL];
然后在标题视图中实现监听器方法
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
2. 向视图添加通知侦听器,并在数据成功可用时发布通知。缺点是这是应用范围,而不是干净的设计。
// place in shared header file
#define HEADER_DATA_AVAILABLE @"Header Data Available Notification Name"
// object can contain userData property which could hole data needed.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(headerDataAvailable:) name:HEADER_DATA_AVAILABLE object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:HEADER_DATA_AVAILABLE object:nil];
let headerView = collectionView.visibleSupplementaryViews(ofKind: UICollectionView.elementKindSectionHeader)[0] as! UICollectionReusableView
我已经使用上述方法获取当前标题,并成功更新了子视图。
这是我为仅更新当前加载到内存中的节标题所做的操作:
NSMapTable
。创建标头时,将标头添加为弱保留键,并带有 indexPath 对象。如果我们重用标头,我们将更新 indexPath。NSMapTable
根据需要枚举对象/键。
@interface YourCVController ()
@property (nonatomic, strong) NSMapTable *sectionHeaders;
@end
@implementation YourCVContoller
- (void)viewDidLoad {
[super viewDidLoad];
// This will weakly hold on to the KEYS and strongly hold on to the OBJECTS
// keys == HeaderView, object == indexPath
self.sectionHeaders = [NSMapTable weakToStrongObjectsMapTable];
}
// Creating a Header. Shove it into our map so we can update on the fly
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
PresentationSectionHeader *header = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"presentationHeader" forIndexPath:indexPath];
// Shove data into header here
...
// Use header as our weak key. If it goes away we don't care about it
// Set indexPath as object so we can easily find our indexPath if we need it
[self.sectionHeaders setObject:indexPath forKey:header];
return header;
}
// Update Received, need to update our headers
- (void) updateHeaders {
NSEnumerator *enumerator = self.sectionHeaders.keyEnumerator;
PresentationSectionHeader *header = nil;
while ((header = enumerator.nextObject)) {
// Update the header as needed here
NSIndexPath *indexPath = [self.sectionHeaders objectForKey:header];
}
}
@end
这个问题很老了,但一个简单的方法是设置一个延迟,涵盖您的视图动画并在您更新视图时禁用动画的时间......通常删除或插入大约需要 0.35 秒,所以只需做:
delay(0.35){
UIView.performWithoutAnimation{
self.collectionView.reloadSections(NSIndexSet(index: 1))
}
当补充视图的帧大小在布局无效时发生变化时,我的问题就出现了。补充意见似乎并不令人耳目一新。事实证明它们是,但我正在以UICollectionReusableView
编程方式构建对象,并且我没有删除旧的UILabel
子视图。因此,当集合视图将每个标题视图出列时,UILabels 会堆积起来,导致外观不稳定。
解决方案是在方法中完全构建每个 UICollectionReusableView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
,首先 a) 从出队的单元格中删除所有子视图,然后 b) 从项目的布局属性中获取框架大小以允许添加新的子视图。
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
yourClass *header = (yourClass *)[collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"identifier" forIndexPath:indexPath];
[[header viewWithTag:1] removeFromSuperview]; // remove additional subviews as required
UICollectionViewLayoutAttributes *attributes = [collectionView layoutAttributesForSupplementaryElementOfKind:kind atIndexPath:indexPath];
CGRect frame = attributes.frame;
UILabel *label = [[UILabel alloc] initWithFrame: // CGRectMake based on header frame
label.tag = 1;
[header addSubview:label];
// configure label
return header;
}
if let footerView = collectionView.subviews.first(where: {$0 is LoadingFooterCell}) as?LoadingFooterCell { footerView.isLoading = .loading }
我有一个完美的解决方案:
let footerView = self.collectionView.visibleSupplementaryViews(ofKind: UICollectionView.elementKindSectionFooter)
现在您可以使用以下命令访问 footerView的所有子视图:
footerView[0].subviews[0]
如果您的 footerView 中有标签,则:
let label: UILabel = footerView[0].subviews[0] as? UILabel ?? UILabel()
最后一步:
label.text = "Successfully Updated Footer."