42

我有一些数据是在另一个更新 UICollectionView 标头的线程中获取的。但是,我还没有找到重新加载补充视图(例如页眉或页脚)的有效方法。

我可以打电话collectionView reloadSections:,但这会重新加载整个部分,这是不必要的。collectionView reloadItemsAtIndexPaths:似乎只针对细胞(不是补充观点)。并且调用setNeedsDisplay标题本身似乎也不起作用。我错过了什么吗?

4

12 回答 12

35

你也可以使用(懒惰的方式)

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)
}

这假设您有一个函数,该函数在示例中使用其类名注册/出列视图。我在这里发了一篇关于这个的帖子

于 2014-02-20T15:42:39.347 回答
10

我刚刚遇到了同样的问题,最后我使用它的标签来查找视图来编辑标签:

UICollectionReusableView *footer = (UICollectionReusableView*)[self.collectionView viewWithTag:999];
UILabel *footerLabel = (UILabel*)[footer viewWithTag:100];

就像你说的,没有必要重新加载整个部分,这也会取消单元格上的任何动画。我的解决方案并不理想,但很简单。

于 2013-01-13T00:36:55.257 回答
9

斯威夫特 3/4/5版本:

collectionView.collectionViewLayout.invalidateLayout()

警告!

如果您collectionView同时更改项目数(例如,仅当所有单元格都已加载时才显示页脚),它将崩溃。您需要先重新加载数据,以确保前后的项目数相同invalidateLayout()

collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()
于 2018-06-29T09:10:36.560 回答
4

我遇到了同样的问题。我尝试了@BobVorks 的答案,它工作正常,如果只有单元被重用,否则它不会。因此,我尝试找到一种更简洁的方法来实现这一点,并且在 performBatchUpdate (完成块)之后重新加载了整个 UICollectionView 并且效果很好。它重新加载集合而不取消 insertItemsAtIndexPath 中的动画。实际上,我个人对最近的 2 个答案投了赞成票,因为我发现它有效,但就我而言,这是最干净的方法。

[self.collectionView performBatchUpdates:^{
     // perform indexpaths insertion
} completion:^(BOOL finished) {
     [self.collectionView reloadData];
}];
于 2014-01-27T23:11:00.190 回答
3
[UIView performWithoutAnimation:^{
    [self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:4]];
}];

[UIView performWithoutAnimation:^{
    [self.collectionView reloadData];
}];
于 2016-12-21T15:49:08.520 回答
2

这里有两种方法可以做到。

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];
于 2012-12-12T17:03:43.587 回答
2
let headerView = collectionView.visibleSupplementaryViews(ofKind: UICollectionView.elementKindSectionHeader)[0] as! UICollectionReusableView

我已经使用上述方法获取当前标题,并成功更新了子视图。

于 2019-08-08T03:36:57.090 回答
1

这是我为仅更新当前加载到内存中的节标题所做的操作:

  • 添加一个 weakToStrong 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
于 2015-01-05T17:06:01.910 回答
0

这个问题很老了,但一个简单的方法是设置一个延迟,涵盖您的视图动画并在您更新视图时禁用动画的时间......通常删除或插入大约需要 0.35 秒,所以只需做:

 delay(0.35){
            UIView.performWithoutAnimation{
            self.collectionView.reloadSections(NSIndexSet(index: 1))  
            }
于 2015-06-05T22:05:54.277 回答
0

当补充视图的帧大小在布局无效时发生变化时,我的问题就出现了。补充意见似乎并不令人耳目一新事实证明它们是,但我正在以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;
}
于 2019-03-03T14:21:33.820 回答
0

if let footerView = collectionView.subviews.first(where: {$0 is LoadingFooterCell}) as?LoadingFooterCell { footerView.isLoading = .loading }

于 2021-04-07T15:09:23.843 回答
0

我有一个完美的解决方案

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."
于 2021-02-25T14:15:24.303 回答