【问题标题】:Reload UICollectionView header or footer?重新加载 UICollectionView 页眉或页脚?
【发布时间】:2012-11-29 15:24:46
【问题描述】:

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

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

【问题讨论】:

    标签: ios6 uicollectionview


    【解决方案1】:

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

    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];
    

    【讨论】:

    • 我没有使用 KVO,但我已经监听了数据的更新,在我的自定义头类上设置了字段,并在其上调用了 setNeedsDisplay,但没有任何改变。与设计如何更新标题相比,我在让标题自行刷新方面遇到的麻烦更多。
    • 你有没有试过调用invalidateLayout.这会刷新你的标题吗?
    • 在最简单的层面上,当数据返回时,您希望显示什么?
    • 我只是有一些 UILabel 需要更新。我不得不重写它以使用单元格而不是标题 - 标题只是出于某种原因拒绝更新,除非我重新加载了整个部分。
    • KVO 是观察模型变化和更新 UI 组件的推荐方法。
    【解决方案2】:

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

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

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

    【讨论】:

    • 我担心这样做会抑制标头重用,或导致内存问题。
    • @akaru 它对我来说很好用。不知道为什么会有内存问题,因为它只是一个标签。
    • KVO 旁边的最差答案
    • 很好的答案!特别是如果您需要支持 ios 8.0+
    【解决方案3】:

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

    [self.collectionView performBatchUpdates:^{
         // perform indexpaths insertion
    } completion:^(BOOL finished) {
         [self.collectionView reloadData];
    }];
    

    【讨论】:

      【解决方案4】:

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

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

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

      【讨论】:

      【解决方案5】:

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

      • 添加一个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
      

      【讨论】:

        【解决方案6】:

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

         delay(0.35){
                    UIView.performWithoutAnimation{
                    self.collectionView.reloadSections(NSIndexSet(index: 1))  
                    }
        

        【讨论】:

          【解决方案7】:
          [UIView performWithoutAnimation:^{
              [self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:4]];
          }];
          
          [UIView performWithoutAnimation:^{
              [self.collectionView reloadData];
          }];
          

          【讨论】:

            【解决方案8】:

            Swift 3/4/5 版本:

            collectionView.collectionViewLayout.invalidateLayout()
            

            小心!

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

            collectionView.reloadData()
            collectionView.collectionViewLayout.invalidateLayout()
            

            【讨论】:

              【解决方案9】:

              当补充视图的帧大小在布局无效时发生变化时,我的问题就出现了。 似乎补充视图没有令人耳目一新。事实证明它们是,但我正在以编程方式构建UICollectionReusableView 对象,并且我没有删除旧的UILabel 子视图。所以当collection view对每个header view进行出列时,UILabels就会堆积起来,导致外观不稳定。

              解决方案是完全在 viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath 方法内构建每个 UICollectionReusableView,首先 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;
              }
              

              【讨论】:

                【解决方案10】:
                let headerView = collectionView.visibleSupplementaryViews(ofKind: UICollectionView.elementKindSectionHeader)[0] as! UICollectionReusableView
                

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

                【讨论】:

                  【解决方案11】:

                  我有一个完美的解决方案

                  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."
                  

                  【讨论】:

                    【解决方案12】:

                    如果让 footerView = collectionView.subviews.first(where: {$0 is LoadingFooterCell}) 为?加载FooterCell { footerView.isLoading = .loading }

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2020-05-15
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2014-10-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多