web-dev-qa-db-ja.com

UICollectionViewヘッダーまたはフッターをリロードしますか?

UICollectionViewのヘッダーを更新する別のスレッドで取得されるデータがあります。ただし、ヘッダーやフッターなどの補足ビューをリロードする効率的な方法は見つかりませんでした。

collectionView reloadSections:を呼び出すことができますが、これによりセクション全体がリロードされますが、これは不要です。 collectionView reloadItemsAtIndexPaths:は、補助的なビューではなく、セルのみをターゲットにしているようです。また、ヘッダー自体でsetNeedsDisplayを呼び出しても機能しないようです。何か不足していますか?

33
akaru

また、使用することができます(怠zyな方法)

_collectionView.collectionViewLayout.invalidateLayout() // Swift

[[_collectionView collectionViewLayout] invalidateLayout] // objc
_

より複雑なのは、コンテキストを提供することです

_collectionView.collectionViewLayout.invalidateLayout(with: context) // Swift

[[_collectionView collectionViewLayout] invalidateLayoutWithContext:context] // objc
_

その後、自分でコンテキストを作成または構成して、更新する必要があるものについて通知することができます: ICollectionViewLayoutInvalidationContext

オーバーライドできる機能があります:

_invalidateSupplementaryElements(ofKind:at:) // Swift
_

別のオプションは(既に正しいヘッダー/フッター/補足ビューをロードしている場合)、新しいデータでビューを更新するだけで、次の関数のいずれかを使用して取得することができます:

supplementaryView(forElementKind:at:) // get specific onevisibleSupplementaryViews(ofKind:) // all visible ones

visibleCellsの表示されているセルについても同様です。ビューを取得するだけでビューを完全にリロードしないことの利点は、セルがその状態を保持することです。これは、セルのリロード後にその状態が失われるため、スワイプを使用して削除/編集などを行う場合、テーブルビューセルでは特に優れています。

あなたが狂信的だと思うなら、もちろん、ジェネリックを使用して特定の種類のセル/補足ビューのみを取得する拡張機能を作成することもできます

_if let view = supplementaryView(forType: MySupplementaryView.self, at: indexPath) {
    configure(view, at indexPath)
}
_

これは、例のビューをクラス名で登録/デキューする関数があることを前提としています。これについて投稿しました こちら

26
Saren Inden

私はちょうど同じ問題に出くわし、そのタグを使用してラベルを編集するビューを検索することになりました:

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

あなたが言ったように、セクション全体をリロードする必要はありません。これはセルのアニメーションもキャンセルします。私のソリューションは理想的ではありませんが、十分簡単です。

10
Bob Vork

同じ問題が発生しました。私は@BobVorksの答えを試してみましたが、セルだけが再利用されていればそれはうまくいきません。だから、これを達成するためのよりクリーンな方法を見つけようとしましたが、performBatchUpdate(完了ブロック)の後にUICollectionView全体を再読み込みしましたが、うまく機能しています。 insertItemsAtIndexPathでアニメーションをキャンセルせずに、コレクションをリロードします。実際、私は最近2つの答えを投票しましたが、私はそれがうまくいくと思いますが、私の場合、これが最もきれいな方法です。

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

Swift 3/4/5バージョン:

_collectionView.collectionViewLayout.invalidateLayout()
_

注意!

collectionViewアイテムの数を同時に変更すると(たとえば、すべてのセルがロードされた場合にのみフッターを表示する)、クラッシュになります。 invalidateLayout()の前後でアイテムの数が同じであることを確認するには、最初にデータをリロードする必要があります。

_collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()
_
3
KlimczakM
[UIView performWithoutAnimation:^{
    [self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:4]];
}];

[UIView performWithoutAnimation:^{
    [self.collectionView reloadData];
}];
3
ylgwhyh

以下に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];
2
Samuel

以下は、現在メモリにロードされているセクションヘッダーのみを更新するために行ったことです。

  • 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
1

この質問は非常に古いですが、それを行う簡単な方法は、ビューの更新中にビューがアニメーション化および無効化される時間をカバーする遅延を設定することです...通常、削除または挿入には約.35秒かかります行う:

 delay(0.35){
            UIView.performWithoutAnimation{
            self.collectionView.reloadSections(NSIndexSet(index: 1))  
            }
0
Victor --------
let headerView = collectionView.visibleSupplementaryViews(ofKind: UICollectionView.elementKindSectionHeader)[0] as! UICollectionReusableView

上記のメソッドを使用して現在のヘッダーを取得し、サブビューを正常に更新しました。

0

レイアウトを無効にすると補助ビューのフレームサイズが変更されたときに、私の問題が発生しました。 It appeared補足ビューは更新されていません。判明しましたが、プログラムで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;
}
0
Henry95