UICollectionViewのヘッダーを更新する別のスレッドで取得されるデータがあります。ただし、ヘッダーやフッターなどの補足ビューをリロードする効率的な方法は見つかりませんでした。
collectionView reloadSections:
を呼び出すことができますが、これによりセクション全体がリロードされますが、これは不要です。 collectionView reloadItemsAtIndexPaths:
は、補助的なビューではなく、セルのみをターゲットにしているようです。また、ヘッダー自体でsetNeedsDisplay
を呼び出しても機能しないようです。何か不足していますか?
また、使用することができます(怠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 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];
あなたが言ったように、セクション全体をリロードする必要はありません。これはセルのアニメーションもキャンセルします。私のソリューションは理想的ではありませんが、十分簡単です。
同じ問題が発生しました。私は@BobVorksの答えを試してみましたが、セルだけが再利用されていればそれはうまくいきません。だから、これを達成するためのよりクリーンな方法を見つけようとしましたが、performBatchUpdate(完了ブロック)の後にUICollectionView全体を再読み込みしましたが、うまく機能しています。 insertItemsAtIndexPathでアニメーションをキャンセルせずに、コレクションをリロードします。実際、私は最近2つの答えを投票しましたが、私はそれがうまくいくと思いますが、私の場合、これが最もきれいな方法です。
[self.collectionView performBatchUpdates:^{
// perform indexpaths insertion
} completion:^(BOOL finished) {
[self.collectionView reloadData];
}];
Swift 3/4/5バージョン:
_collectionView.collectionViewLayout.invalidateLayout()
_
注意!
collectionView
アイテムの数を同時に変更すると(たとえば、すべてのセルがロードされた場合にのみフッターを表示する)、クラッシュになります。 invalidateLayout()
の前後でアイテムの数が同じであることを確認するには、最初にデータをリロードする必要があります。
_collectionView.reloadData()
collectionView.collectionViewLayout.invalidateLayout()
_
[UIView performWithoutAnimation:^{
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:4]];
}];
[UIView performWithoutAnimation:^{
[self.collectionView reloadData];
}];
以下に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];
以下は、現在メモリにロードされているセクションヘッダーのみを更新するために行ったことです。
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
この質問は非常に古いですが、それを行う簡単な方法は、ビューの更新中にビューがアニメーション化および無効化される時間をカバーする遅延を設定することです...通常、削除または挿入には約.35秒かかります行う:
delay(0.35){
UIView.performWithoutAnimation{
self.collectionView.reloadSections(NSIndexSet(index: 1))
}
let headerView = collectionView.visibleSupplementaryViews(ofKind: UICollectionView.elementKindSectionHeader)[0] as! UICollectionReusableView
上記のメソッドを使用して現在のヘッダーを取得し、サブビューを正常に更新しました。
レイアウトを無効にすると補助ビューのフレームサイズが変更されたときに、私の問題が発生しました。 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;
}