Swiftgram/Source/Details/ASDataController.h
Huy Nguyen 3ccc2f0f15 Introduce ASCollectionGalleryLayoutDelegate (#76)
* Implement ASCollectionGalleryLayoutDelegate
- It arranges items of the same size into a multi-line stack (say photo gallery or pager). It takes advantage of the fact that its items always have a fixed size to measure as few items as possible while still being able to track their positions at all time. This helps reduce startup/reloadData time, as well as memory footprint.
- It then uses a measure range, which also works as a allocate range, to figure out which items to measure ahead of time. And it guarantees that each item is scheduled to measure only once.
- Lastly, ASCollectionLayoutDelegate has some new methods that allow delegates to hook up and stay ahead of layout attributes requests from the backing view. ASCollectionGalleryLayoutDelegate for example uses these methods to ensure elements that have their layout attributes requested are always ready for consumption, and to measure more elements in the background.

* Handle items that span multiple pages and other improvements in gallery delegate

* Minor fixes

* Fix failing tests

* Fix custom collection example

* Implement missing method in gallery layout delegate

* Fix warnings

* Some improvements
- Collection layout delegates must have a crollable directions property.
- Simplify gallery delegate by not storing unmeasured attributes since calling measure on already measured elements should be cache hits and super fast.
- Abstact some code in gallery delegate to ASCollectionLayoutState+Private and _ASCollectionGalleryLayoutItem.
- Other improvements in gallery delegate

* Fix file licenses

* Move measure range logic to ASCollectionLayout

* Track unmeasured elements

* Remove pending layout in ASCollectionLayout

* Get back pending layout because the timing to latch new data is not ideal

* Add ASCollectionLayoutCache

* Fix file licenses

* Fix xcodeproj

* Add async collection layout to examples/ASCollectionView

* Measure method in ASCollectionLayout to be a class method

* Encourage more immutable states
- Make -calculateLayoutWithContext: to be class methods in ASDataControllerLayoutDelegate and ASCollectionLayoutDelegate.
- Add layout delegate class and layout cache to ASCollectionLayoutContext+Private, to be use by ASCollectionLayout only.
- ASDataController no longer allocates all nodes but lets ASCollectionLayout determine.
- Add scrollableDirections to the layout context since it's often needed by the layout pass. Otherwise users have to wrap it in an info object.
- Update built-in layout delegates and CustomCollectionView example.
- Publish ASHashing. It might be helpful for clients that implement custom collection info objects.

* Remove additionalInfo property in ASCollectionLayoutState

* ASCollectionLayoutState to correctly filter unmeasured elements

* Add ASHashing to umbrella header

* Fix file licenses

* Add ASDispatchAsync and use it in ASCollectionLayout

* Improve code comment in ASCollectionLayoutState
2017-07-14 18:50:26 +00:00

279 lines
10 KiB
Objective-C

//
// ASDataController.h
// Texture
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
// grant of patent rights can be found in the PATENTS file in the same directory.
//
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#pragma once
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASBlockTypes.h>
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASEventLog.h>
#ifdef __cplusplus
#import <vector>
#endif
NS_ASSUME_NONNULL_BEGIN
#if ASEVENTLOG_ENABLE
#define ASDataControllerLogEvent(dataController, ...) [dataController.eventLog logEventWithBacktrace:(AS_SAVE_EVENT_BACKTRACES ? [NSThread callStackSymbols] : nil) format:__VA_ARGS__]
#else
#define ASDataControllerLogEvent(dataController, ...)
#endif
@class ASCellNode;
@class ASCollectionElement;
@class ASCollectionLayoutContext;
@class ASCollectionLayoutState;
@class ASDataController;
@class ASElementMap;
@class ASLayout;
@class _ASHierarchyChangeSet;
@protocol ASRangeManagingNode;
@protocol ASTraitEnvironment;
@protocol ASSectionContext;
typedef NSUInteger ASDataControllerAnimationOptions;
extern NSString * const ASDataControllerRowNodeKind;
extern NSString * const ASCollectionInvalidUpdateException;
/**
Data source for data controller
It will be invoked in the same thread as the api call of ASDataController.
*/
@protocol ASDataControllerSource <NSObject>
/**
Fetch the ASCellNode block for specific index path. This block should return the ASCellNode for the specified index path.
*/
- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath;
/**
Fetch the number of rows in specific section.
*/
- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section;
/**
Fetch the number of sections.
*/
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController;
/**
Returns if the collection element size matches a given size.
@precondition The element is present in the data controller's visible map.
*/
- (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size;
- (nullable id)dataController:(ASDataController *)dataController viewModelForItemAtIndexPath:(NSIndexPath *)indexPath;
@optional
/**
The constrained size range for layout. Called only if collection layout delegate is not provided.
*/
- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath;
- (NSArray<NSString *> *)dataController:(ASDataController *)dataController supplementaryNodeKindsInSections:(NSIndexSet *)sections;
- (NSUInteger)dataController:(ASDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section;
- (ASCellNodeBlock)dataController:(ASDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
/**
The constrained size range for layout. Called only if no data controller layout delegate is provided.
*/
- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
- (nullable id<ASSectionContext>)dataController:(ASDataController *)dataController contextForSection:(NSInteger)section;
@end
/**
Delegate for notify the data updating of data controller.
These methods will be invoked from main thread right now, but it may be moved to background thread in the future.
*/
@protocol ASDataControllerDelegate <NSObject>
/**
* Called before updating with given change set.
*
* @param changeSet The change set that includes all updates
*/
- (void)dataController:(ASDataController *)dataController willUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet;
/**
* Called for change set updates.
*
* @param changeSet The change set that includes all updates
*
* @param updates The block that performs relevant data updates.
*
* @discussion The updates block must always be executed or the data controller will get into a bad state.
* It should be called at the time the backing view is ready to process the updates,
* i.e inside the updates block of `-[UICollectionView performBatchUpdates:completion:] or after calling `-[UITableView beginUpdates]`.
*/
- (void)dataController:(ASDataController *)dataController didUpdateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates;
@end
@protocol ASDataControllerLayoutDelegate <NSObject>
/**
* @abstract Returns a layout context needed for a coming layout pass with the given elements.
* The context should contain the elements and any additional information needed.
*
* @discussion This method will be called on main thread.
*/
- (ASCollectionLayoutContext *)layoutContextWithElements:(ASElementMap *)elements;
/**
* @abstract Prepares and returns a new layout for given context.
*
* @param context A context that was previously returned by `-layoutContextWithElements:`.
*
* @return The new layout calculated for the given context.
*
* @discussion This method is called ahead of time, i.e before the underlying collection/table view is aware of the provided elements.
* As a result, clients must solely rely on the given context and should not reach out to other objects for information not available in the context.
*
* This method will be called on background theads. It must be thread-safe and should not change any internal state of the conforming object.
* It must block the calling thread but can dispatch to other theads to reduce total blocking time.
*/
+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context;
@end
/**
* Controller to layout data in background, and managed data updating.
*
* All operations are asynchronous and thread safe. You can call it from background thread (it is recommendated) and the data
* will be updated asynchronously. The dataSource must be updated to reflect the changes before these methods has been called.
* For each data updating, the corresponding methods in delegate will be called.
*/
@interface ASDataController : NSObject
- (instancetype)initWithDataSource:(id<ASDataControllerSource>)dataSource node:(nullable id<ASRangeManagingNode>)node eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
/**
* The node that owns this data controller, if any.
*
* NOTE: Soon we will drop support for using ASTableView/ASCollectionView without the node, so this will be non-null.
*/
@property (nonatomic, nullable, weak, readonly) id<ASRangeManagingNode> node;
/**
* The map that is currently displayed. The "UIKit index space."
*
* This property will only be changed on the main thread.
*/
@property (atomic, copy, readonly) ASElementMap *visibleMap;
/**
* The latest map fetched from the data source. May be more recent than @c visibleMap.
*
* This property will only be changed on the main thread.
*/
@property (atomic, copy, readonly) ASElementMap *pendingMap;
/**
Data source for fetching data info.
*/
@property (nonatomic, weak, readonly) id<ASDataControllerSource> dataSource;
/**
An object that will be included in the backtrace of any update validation exceptions that occur.
*/
@property (nonatomic, weak) id validationErrorSource;
/**
Delegate to notify when data is updated.
*/
@property (nonatomic, weak) id<ASDataControllerDelegate> delegate;
/**
* Delegate for preparing layouts. Main thead only.
*/
@property (nonatomic, weak) id<ASDataControllerLayoutDelegate> layoutDelegate;
#ifdef __cplusplus
/**
* Returns the most recently gathered item counts from the data source. If the counts
* have been invalidated, this synchronously queries the data source and saves the result.
*
* This must be called on the main thread.
*/
- (std::vector<NSInteger>)itemCountsFromDataSource;
#endif
/**
* Returns YES if reloadData has been called at least once. Before this point it is
* important to ignore/suppress some operations. For example, inserting a section
* before the initial data load should have no effect.
*
* This must be called on the main thread.
*/
@property (nonatomic, readonly) BOOL initialReloadDataHasBeenCalled;
#if ASEVENTLOG_ENABLE
/*
* @abstract The primitive event tracing object. You shouldn't directly use it to log event. Use the ASDataControllerLogEvent macro instead.
*/
@property (nonatomic, strong, readonly) ASEventLog *eventLog;
#endif
/**
* @see ASCollectionNode+Beta.h for full documentation.
*/
@property (nonatomic, assign) BOOL usesSynchronousDataLoading;
/** @name Data Updating */
- (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet;
/**
* Re-measures all loaded nodes in the backing store.
*
* @discussion Used to respond to a change in size of the containing view
* (e.g. ASTableView or ASCollectionView after an orientation change).
*/
- (void)relayoutAllNodes;
/**
* Re-measures given nodes in the backing store.
*
* @discussion Used to respond to setNeedsLayout calls in ASCellNode
*/
- (void)relayoutNodes:(id<NSFastEnumeration>)nodes nodesSizeChanged:(NSMutableArray * _Nonnull)nodesSizesChanged;
- (void)waitUntilAllUpdatesAreCommitted;
/**
* Notifies the data controller object that its environment has changed. The object will request its environment delegate for new information
* and propagate the information to all visible elements, including ones that are being prepared in background.
*
* @discussion If called before the initial @c reloadData, this method will do nothing and the trait collection of the initial load will be requested from the environment delegate.
*
* @discussion This method can be called on any threads.
*/
- (void)environmentDidChange;
@end
NS_ASSUME_NONNULL_END