mirror of
https://github.com/HackPlan/AsyncDisplayKit.git
synced 2026-03-28 23:58:50 +08:00
Merge branch 'master' of https://github.com/facebook/AsyncDisplayKit into ASVideoNode-overwrittenplaceholder
This commit is contained in:
61
.github/GITHUB_RULES.md
vendored
Normal file
61
.github/GITHUB_RULES.md
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
### Contribute to ASDK's Friendly Reputation
|
||||
|
||||
ASDK has earned its reputation as an exceptionally welcoming place for newbie & experienced developers alike through the extra time Scott takes to thank _everyone_ who posts a question, bug, feature request or PR, for their time and contribution to the project, no matter how large the contribution (or silly the question).
|
||||
|
||||
###PR Reviewing
|
||||
|
||||
Merge permissions granted to Scott Goodson (@appleguy), Michael Schneider (@maicki), Adlai Holler (@Adlai-Holler)
|
||||
|
||||
**PR Type** | **Required Reviewers**
|
||||
--- | ---
|
||||
Documentation | Anyone
|
||||
Bug Fix | 2 (external PR) or 1 (internal PR) of the following (Scott, Michael, Adlai, Levi)
|
||||
Refactoring | 1-3 depending on size / author familiarity with feature
|
||||
New API | Scott + component owner + 1 additional
|
||||
Breaking API | Scott + component owner + 1 additional
|
||||
|
||||
**Component** | **Experts For Reviewing**
|
||||
--- | ---
|
||||
ASTextNode + subclasses | Ricky / Oliver
|
||||
ASImageNode + subclasses | Garrett / Scott / Michael
|
||||
ASDataController / Table / Collection | Michael
|
||||
ASRangeController | Scott
|
||||
ASLayout | Huy
|
||||
ASDisplayNode | Garret / Michael / Levi
|
||||
ASVideoNode | #asvideonode channel
|
||||
|
||||
###PR Merging
|
||||
|
||||
BE CAUTIOUS, DON'T CAUSE A REGRESSION
|
||||
|
||||
Try to include as much as possible:
|
||||
- Description / Screenshots
|
||||
- Motivation & Context
|
||||
- Methods of testing / Sample app
|
||||
- What type of change it is (bug fix, new feature, breaking change)
|
||||
- Tag @hannahmbanana on any documentation needs*
|
||||
- Title the PR with the component in brackets - e.g. "[ASTextNode] fix threading issues..."
|
||||
- New files need to include the required Facebook licensing header info.
|
||||
- For future viewers / potential contributors, try to describe why this PR is helpful / useful / awesome / makes an impact on the current or future community
|
||||
|
||||
###What stays on GitHub vs goes to Ship?
|
||||
|
||||
GitHub:
|
||||
- active bugs
|
||||
- active community discussions
|
||||
- unresolved community questions
|
||||
- open issue about slack channel
|
||||
- open issue with list of “up-for-grabs” tasks to get involved
|
||||
|
||||
Ship:
|
||||
- feature requests
|
||||
- documentation requests
|
||||
- performance optimizations / refactoring
|
||||
|
||||
Comment for moving to Ship:
|
||||
|
||||
@\<FEATURE_REQUESTOR\> The community is planning an exciting long term road map for the project and getting organized around how to deliver these feature requests.
|
||||
|
||||
If you are interested in helping contribute to this component or any other, don’t hesitate to send us an email at AsyncDisplayKit@gmail.com or ping us on ASDK's Slack (#1582). If you would like to contribute for a few weeks, we can also add you to our Ship bug tracker so that you can see what everyone is working on and actively coordinate with us.
|
||||
|
||||
As always, keep filing issues and submitting pull requests here on Github and we will only move things to the new tracker if they require long term coordination.
|
||||
4
.github/ISSUE_TEMPLATE.md
vendored
Normal file
4
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
// The more information you include, the faster we can help you out!
|
||||
// Please include: a sample project or screenshots, code snippets
|
||||
// AsyncDisplayKit version, and/or backtraces for any crashes (> bt all).
|
||||
// Please delete these lines before posting. Thanks!
|
||||
@@ -1,4 +1,7 @@
|
||||
language: objective-c
|
||||
cache:
|
||||
- bundler
|
||||
- cocoapods
|
||||
osx_image: xcode7.3
|
||||
git:
|
||||
depth: 10
|
||||
@@ -6,7 +9,7 @@ before_install:
|
||||
- brew update
|
||||
- brew outdated xctool || brew upgrade xctool
|
||||
- brew outdated carthage || brew upgrade carthage
|
||||
- gem install cocoapods -v 0.38.2
|
||||
- gem install cocoapods -v 1.0.1
|
||||
- gem install xcpretty
|
||||
- gem install xcpretty-travis-formatter
|
||||
# - gem install slather
|
||||
@@ -14,7 +17,9 @@ before_install:
|
||||
install: echo "<3"
|
||||
env:
|
||||
- MODE=tests
|
||||
- MODE=examples
|
||||
- MODE=examples-pt1
|
||||
- MODE=examples-pt2
|
||||
- MODE=examples-pt3
|
||||
- MODE=life-without-cocoapods
|
||||
- MODE=framework
|
||||
script: ./build.sh $MODE
|
||||
|
||||
@@ -545,6 +545,7 @@
|
||||
CC3B208C1C3F7A5400798563 /* ASWeakSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20881C3F7A5400798563 /* ASWeakSet.m */; };
|
||||
CC3B208E1C3F7D0A00798563 /* ASWeakSetTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */; };
|
||||
CC3B20901C3F892D00798563 /* ASBridgedPropertiesTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */; };
|
||||
CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.m */; };
|
||||
CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; };
|
||||
CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */; };
|
||||
@@ -935,6 +936,7 @@
|
||||
CC3B20881C3F7A5400798563 /* ASWeakSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASWeakSet.m; sourceTree = "<group>"; };
|
||||
CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASWeakSetTests.m; sourceTree = "<group>"; };
|
||||
CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBridgedPropertiesTests.mm; sourceTree = "<group>"; };
|
||||
CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableViewThrashTests.m; sourceTree = "<group>"; };
|
||||
CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPhotosFrameworkImageRequest.h; sourceTree = "<group>"; };
|
||||
CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequest.m; sourceTree = "<group>"; };
|
||||
CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequestTests.m; sourceTree = "<group>"; };
|
||||
@@ -1201,6 +1203,7 @@
|
||||
052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.m */,
|
||||
058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m */,
|
||||
3C9C128419E616EF00E942A0 /* ASTableViewTests.m */,
|
||||
CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.m */,
|
||||
058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m */,
|
||||
254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */,
|
||||
254C6B531BF8FF2A003EC431 /* ASTextKitTests.mm */,
|
||||
@@ -2171,6 +2174,7 @@
|
||||
2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */,
|
||||
058D0A39195D057000B7D73C /* ASDisplayNodeAppearanceTests.m in Sources */,
|
||||
058D0A3A195D057000B7D73C /* ASDisplayNodeTests.m in Sources */,
|
||||
CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.m in Sources */,
|
||||
058D0A3B195D057000B7D73C /* ASDisplayNodeTestsHelper.m in Sources */,
|
||||
056D21551ABCEF50001107EF /* ASImageNodeSnapshotTests.m in Sources */,
|
||||
AC026B581BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m in Sources */,
|
||||
|
||||
@@ -99,6 +99,13 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) {
|
||||
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
|
||||
- (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
|
||||
|
||||
/**
|
||||
* Called by the system when ASCellNode is used with an ASCollectionNode. It will not be called by ASTableNode.
|
||||
* When the UICollectionViewLayout object returns a new UICollectionViewLayoutAttributes object, the corresponding ASCellNode will be updated.
|
||||
* See UICollectionViewCell's applyLayoutAttributes: for a full description.
|
||||
*/
|
||||
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes;
|
||||
|
||||
/**
|
||||
* @abstract Initializes a cell with a given view controller block.
|
||||
*
|
||||
|
||||
@@ -211,6 +211,11 @@
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
|
||||
{
|
||||
// To be overriden by subclasses
|
||||
}
|
||||
|
||||
- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame
|
||||
{
|
||||
// To be overriden by subclasses
|
||||
|
||||
@@ -407,8 +407,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* due to the data access in async mode.
|
||||
*
|
||||
* @param collectionView The sender.
|
||||
* @deprecated The data source is always accessed on the main thread, and this method will not be called.
|
||||
*/
|
||||
- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView;
|
||||
- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED;
|
||||
|
||||
/**
|
||||
* Indicator to unlock the data source for data fetching in async mode.
|
||||
@@ -416,8 +417,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* due to the data access in async mode.
|
||||
*
|
||||
* @param collectionView The sender.
|
||||
* @deprecated The data source is always accessed on the main thread, and this method will not be called.
|
||||
*/
|
||||
- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView;
|
||||
- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED;
|
||||
|
||||
@end
|
||||
|
||||
@@ -430,6 +432,17 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Informs the delegate that the collection view will add the node
|
||||
* at the given index path to the view hierarchy.
|
||||
*
|
||||
* @param collectionView The sender.
|
||||
* @param indexPath The index path of the item that will be displayed.
|
||||
*
|
||||
* @warning AsyncDisplayKit processes collection view edits asynchronously. The index path
|
||||
* passed into this method may not correspond to the same item in your data source
|
||||
* if your data source has been updated since the last edit was processed.
|
||||
*/
|
||||
- (void)collectionView:(ASCollectionView *)collectionView willDisplayNodeForItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
@@ -440,6 +453,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* @param collectionView The sender.
|
||||
* @param node The node which was removed from the view hierarchy.
|
||||
* @param indexPath The index path at which the node was located before it was removed.
|
||||
*
|
||||
* @warning AsyncDisplayKit processes collection view edits asynchronously. The index path
|
||||
* passed into this method may not correspond to the same item in your data source
|
||||
* if your data source has been updated since the last edit was processed.
|
||||
*/
|
||||
- (void)collectionView:(ASCollectionView *)collectionView didEndDisplayingNode:(ASCellNode *)node forItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
@@ -475,6 +492,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* Informs the delegate that the collection view did remove the node which was previously
|
||||
* at the given index path from the view hierarchy.
|
||||
*
|
||||
* @warning AsyncDisplayKit processes collection view edits asynchronously. The index path
|
||||
* passed into this method may not correspond to the same item in your data source
|
||||
* if your data source has been updated since the last edit was processed.
|
||||
*
|
||||
* This method is deprecated. Use @c collectionView:didEndDisplayingNode:forItemAtIndexPath: instead.
|
||||
*/
|
||||
- (void)collectionView:(ASCollectionView *)collectionView didEndDisplayingNodeForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED;
|
||||
|
||||
@@ -58,6 +58,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
_node.highlighted = highlighted;
|
||||
}
|
||||
|
||||
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
|
||||
{
|
||||
[_node applyLayoutAttributes:layoutAttributes];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
@@ -107,7 +112,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
BOOL _performingBatchUpdates;
|
||||
NSMutableArray *_batchUpdateBlocks;
|
||||
|
||||
BOOL _asyncDataFetchingEnabled;
|
||||
_ASCollectionViewNodeSizeInvalidationContext *_queuedNodeSizeInvalidationContext; // Main thread only
|
||||
BOOL _isDeallocating;
|
||||
|
||||
@@ -150,14 +154,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
unsigned int asyncDataSourceNodeForItemAtIndexPath:1;
|
||||
unsigned int asyncDataSourceNodeBlockForItemAtIndexPath:1;
|
||||
unsigned int asyncDataSourceNumberOfSectionsInCollectionView:1;
|
||||
unsigned int asyncDataSourceCollectionViewLockDataSource:1;
|
||||
unsigned int asyncDataSourceCollectionViewUnlockDataSource:1;
|
||||
unsigned int asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath:1;
|
||||
} _asyncDataSourceFlags;
|
||||
}
|
||||
|
||||
@property (atomic, assign) BOOL asyncDataSourceLocked;
|
||||
|
||||
// Used only when ASCollectionView is created directly rather than through ASCollectionNode.
|
||||
// We create a node so that logic related to appearance, memory management, etc can be located there
|
||||
// for both the node-based and view-based version of the table.
|
||||
@@ -226,7 +226,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
_rangeController.delegate = self;
|
||||
_rangeController.layoutController = _layoutController;
|
||||
|
||||
_dataController = [[ASCollectionDataController alloc] initWithAsyncDataFetching:NO];
|
||||
_dataController = [[ASCollectionDataController alloc] init];
|
||||
_dataController.delegate = _rangeController;
|
||||
_dataController.dataSource = self;
|
||||
_dataController.environmentDelegate = self;
|
||||
@@ -235,9 +235,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
|
||||
_leadingScreensForBatching = 2.0;
|
||||
|
||||
_asyncDataFetchingEnabled = NO;
|
||||
_asyncDataSourceLocked = NO;
|
||||
|
||||
_performingBatchUpdates = NO;
|
||||
_batchUpdateBlocks = [NSMutableArray array];
|
||||
|
||||
@@ -370,9 +367,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
_asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)];
|
||||
_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)];
|
||||
_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)];
|
||||
_asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource = [_asyncDataSource respondsToSelector:@selector(collectionViewLockDataSource:)];
|
||||
_asyncDataSourceFlags.asyncDataSourceCollectionViewUnlockDataSource = [_asyncDataSource respondsToSelector:@selector(collectionViewUnlockDataSource:)];
|
||||
_asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];;
|
||||
_asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)];
|
||||
|
||||
// Data-source must implement collectionView:nodeForItemAtIndexPath: or collectionView:nodeBlockForItemAtIndexPath:
|
||||
ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath || _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath);
|
||||
@@ -932,26 +927,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dataControllerLockDataSource
|
||||
{
|
||||
ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked");
|
||||
|
||||
self.asyncDataSourceLocked = YES;
|
||||
if (_asyncDataSourceFlags.asyncDataSourceCollectionViewLockDataSource) {
|
||||
[_asyncDataSource collectionViewLockDataSource:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dataControllerUnlockDataSource
|
||||
{
|
||||
ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked");
|
||||
|
||||
self.asyncDataSourceLocked = NO;
|
||||
if (_asyncDataSourceFlags.asyncDataSourceCollectionViewUnlockDataSource) {
|
||||
[_asyncDataSource collectionViewUnlockDataSource:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (id<ASEnvironment>)dataControllerEnvironment
|
||||
{
|
||||
if (self.collectionNode) {
|
||||
@@ -1002,6 +977,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
- (NSArray *)visibleNodeIndexPathsForRangeController:(ASRangeController *)rangeController
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
// Calling visibleNodeIndexPathsForRangeController: will trigger UIKit to call reloadData if it never has, which can result
|
||||
// in incorrect layout if performed at zero size. We can use the fact that nothing can be visible at zero size to return fast.
|
||||
BOOL isZeroSized = CGRectEqualToRect(self.bounds, CGRectZero);
|
||||
@@ -1065,6 +1041,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
_performingBatchUpdates = NO;
|
||||
}
|
||||
|
||||
- (void)didCompleteUpdatesInRangeController:(ASRangeController *)rangeController
|
||||
{
|
||||
[self _checkForBatchFetching];
|
||||
}
|
||||
|
||||
- (void)rangeController:(ASRangeController *)rangeController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
@@ -273,6 +273,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
_preferredFrameSize = CGSizeZero;
|
||||
|
||||
_environmentState = ASEnvironmentStateMakeDefault();
|
||||
|
||||
_flags.canClearContentsOfLayer = YES;
|
||||
_flags.canCallNeedsDisplayOfLayer = NO;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
@@ -440,6 +443,15 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
}
|
||||
view = [[_viewClass alloc] init];
|
||||
}
|
||||
|
||||
// Update flags related to special handling of UIImageView layers. More details on the flags
|
||||
if (_flags.synchronous) {
|
||||
if ([view isKindOfClass:[UIImageView class]]) {
|
||||
_flags.canClearContentsOfLayer = NO;
|
||||
} else {
|
||||
_flags.canCallNeedsDisplayOfLayer = YES;
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -1515,40 +1527,36 @@ static NSInteger incrementIfFound(NSInteger i) {
|
||||
[subnode __setSupernode:nil];
|
||||
}
|
||||
|
||||
// NOTE: You must not called this method while holding the receiver's property lock. This may cause deadlocks.
|
||||
- (void)removeFromSupernode
|
||||
{
|
||||
ASDisplayNodeAssertThreadAffinity(self);
|
||||
BOOL shouldRemoveFromSuperviewOrSuperlayer = NO;
|
||||
_propertyLock.lock();
|
||||
__weak ASDisplayNode *supernode = _supernode;
|
||||
__weak UIView *view = _view;
|
||||
__weak CALayer *layer = _layer;
|
||||
BOOL layerBacked = _flags.layerBacked;
|
||||
_propertyLock.unlock();
|
||||
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
if (!_supernode)
|
||||
return;
|
||||
if (supernode == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
[supernode _removeSubnode:self];
|
||||
|
||||
if (self.nodeLoaded && supernode.nodeLoaded) {
|
||||
// Check to ensure that our view or layer is actually inside of our supernode; otherwise, don't remove it.
|
||||
// Though _ASDisplayView decouples the supernode if it is inserted inside another view hierarchy, this is
|
||||
// more difficult to guarantee with _ASDisplayLayer because CoreAnimation doesn't have a -didMoveToSuperlayer.
|
||||
|
||||
if (self.nodeLoaded && _supernode.nodeLoaded) {
|
||||
if (_flags.layerBacked || _supernode.layerBacked) {
|
||||
shouldRemoveFromSuperviewOrSuperlayer = (_layer.superlayer == _supernode.layer);
|
||||
} else {
|
||||
shouldRemoveFromSuperviewOrSuperlayer = (_view.superview == _supernode.view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do this before removing the view from the hierarchy, as the node will clear its supernode pointer when its view is removed from the hierarchy.
|
||||
// This call may result in the object being destroyed.
|
||||
[_supernode _removeSubnode:self];
|
||||
|
||||
if (shouldRemoveFromSuperviewOrSuperlayer) {
|
||||
ASPerformBlockOnMainThread(^{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
if (_flags.layerBacked) {
|
||||
[_layer removeFromSuperlayer];
|
||||
if (layerBacked || supernode.layerBacked) {
|
||||
if (layer.superlayer == supernode.layer) {
|
||||
[layer removeFromSuperlayer];
|
||||
}
|
||||
} else {
|
||||
[_view removeFromSuperview];
|
||||
if (view.superview == supernode.view) {
|
||||
[view removeFromSuperview];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1792,10 +1800,18 @@ static NSInteger incrementIfFound(NSInteger i) {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to summarize whether or not the node run through the display process
|
||||
/// Helper method to summarize whether or not the node run through the display process
|
||||
- (BOOL)__implementsDisplay
|
||||
{
|
||||
return _flags.implementsDrawRect || _flags.implementsImageDisplay || _flags.shouldRasterizeDescendants || _flags.implementsInstanceDrawRect || _flags.implementsInstanceImageDisplay;
|
||||
return _flags.implementsDrawRect || _flags.implementsImageDisplay || _flags.shouldRasterizeDescendants ||
|
||||
_flags.implementsInstanceDrawRect || _flags.implementsInstanceImageDisplay;
|
||||
}
|
||||
|
||||
// Helper method to determine if it's save to call setNeedsDisplay on a layer without throwing away the content.
|
||||
// For details look at the comment on the canCallNeedsDisplayOfLayer flag
|
||||
- (BOOL)__canCallNeedsDisplayOfLayer
|
||||
{
|
||||
return _flags.canCallNeedsDisplayOfLayer;
|
||||
}
|
||||
|
||||
- (BOOL)placeholderShouldPersist
|
||||
@@ -1845,6 +1861,13 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
|
||||
// (even a runloop observer at a late call order will not stop the next frame from compositing, showing placeholders).
|
||||
|
||||
ASDisplayNode *node = [layer asyncdisplaykit_node];
|
||||
|
||||
if ([node __canCallNeedsDisplayOfLayer]) {
|
||||
// Layers for UIKit components that are wrapped wtihin a node needs to be set to be displayed as the contents of
|
||||
// the layer get's cleared and would not be recreated otherwise
|
||||
[layer setNeedsDisplay];
|
||||
}
|
||||
|
||||
if ([node __implementsDisplay]) {
|
||||
// For layers that do get displayed here, this immediately kicks off the work on the concurrent -[_ASDisplayLayer displayQueue].
|
||||
// At the same time, it creates an associated _ASAsyncTransaction, which we can use to block on display completion. See ASDisplayNode+AsyncDisplay.mm.
|
||||
@@ -2092,8 +2115,11 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
|
||||
|
||||
- (void)clearContents
|
||||
{
|
||||
// No-op if these haven't been created yet, as that guarantees they don't have contents that needs to be released.
|
||||
_layer.contents = nil;
|
||||
if (_flags.canClearContentsOfLayer) {
|
||||
// No-op if these haven't been created yet, as that guarantees they don't have contents that needs to be released.
|
||||
_layer.contents = nil;
|
||||
}
|
||||
|
||||
_placeholderLayer.contents = nil;
|
||||
_placeholderImage = nil;
|
||||
}
|
||||
|
||||
@@ -192,7 +192,9 @@
|
||||
{
|
||||
ASTextKitComponents *displayedComponents = [self isDisplayingPlaceholder] ? _placeholderTextKitComponents : _textKitComponents;
|
||||
CGSize textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width];
|
||||
return CGSizeMake(fminf(ceilf(textSize.width), constrainedSize.width), fminf(ceilf(textSize.height), constrainedSize.height));
|
||||
CGFloat width = ceilf(textSize.width + _textContainerInset.left + _textContainerInset.right);
|
||||
CGFloat height = ceilf(textSize.height + _textContainerInset.top + _textContainerInset.bottom);
|
||||
return CGSizeMake(fminf(width, constrainedSize.width), fminf(height, constrainedSize.height));
|
||||
}
|
||||
|
||||
- (void)layout
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#import "ASInternalHelpers.h"
|
||||
#import "ASWeakProxy.h"
|
||||
|
||||
NSString *const ASAnimatedImageDefaultRunLoopMode = NSDefaultRunLoopMode;
|
||||
NSString *const ASAnimatedImageDefaultRunLoopMode = NSRunLoopCommonModes;
|
||||
|
||||
@implementation ASImageNode (AnimatedImage)
|
||||
|
||||
@@ -45,6 +45,10 @@ NSString *const ASAnimatedImageDefaultRunLoopMode = NSDefaultRunLoopMode;
|
||||
};
|
||||
}
|
||||
|
||||
if (animatedImage.playbackReady) {
|
||||
[self animatedImageFileReady];
|
||||
}
|
||||
|
||||
animatedImage.playbackReadyCallback = ^{
|
||||
[weakSelf animatedImageFileReady];
|
||||
};
|
||||
|
||||
@@ -144,9 +144,11 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image);
|
||||
@property (atomic, assign) BOOL animatedImagePaused;
|
||||
|
||||
/**
|
||||
* @abstract The runloop mode used to animte th image.
|
||||
* @abstract The runloop mode used to animate the image.
|
||||
*
|
||||
* @discussion Defaults to NSDefaultRunLoopMode. Another commonly used mode is NSRunLoopCommonModes.
|
||||
* @discussion Defaults to NSRunLoopCommonModes. Another commonly used mode is NSDefaultRunLoopMode.
|
||||
* Setting NSDefaultRunLoopMode will cause animation to pause while scrolling (if the ASImageNode is
|
||||
* in a scroll view), which may improve scroll performance in some use cases.
|
||||
*/
|
||||
@property (atomic, strong) NSString *animatedImageRunLoopMode;
|
||||
|
||||
|
||||
@@ -26,46 +26,18 @@
|
||||
#import "ASInternalHelpers.h"
|
||||
#import "ASEqualityHelpers.h"
|
||||
|
||||
@interface _ASImageNodeDrawParameters : NSObject
|
||||
|
||||
@property (nonatomic, retain) UIImage *image;
|
||||
@property (nonatomic, assign) BOOL opaque;
|
||||
@property (nonatomic, assign) CGRect bounds;
|
||||
@property (nonatomic, assign) CGFloat contentsScale;
|
||||
@property (nonatomic, strong) UIColor *backgroundColor;
|
||||
@property (nonatomic, assign) UIViewContentMode contentMode;
|
||||
|
||||
@end
|
||||
|
||||
// TODO: eliminate explicit parameters with a set of keys copied from the node
|
||||
@implementation _ASImageNodeDrawParameters
|
||||
|
||||
- (instancetype)initWithImage:(UIImage *)image
|
||||
bounds:(CGRect)bounds
|
||||
opaque:(BOOL)opaque
|
||||
contentsScale:(CGFloat)contentsScale
|
||||
backgroundColor:(UIColor *)backgroundColor
|
||||
contentMode:(UIViewContentMode)contentMode
|
||||
{
|
||||
if (!(self = [self init]))
|
||||
return nil;
|
||||
|
||||
_image = image;
|
||||
_opaque = opaque;
|
||||
_bounds = bounds;
|
||||
_contentsScale = contentsScale;
|
||||
_backgroundColor = backgroundColor;
|
||||
_contentMode = contentMode;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@ : %p opaque:%@ bounds:%@ contentsScale:%.2f backgroundColor:%@ contentMode:%@>", [self class], self, @(self.opaque), NSStringFromCGRect(self.bounds), self.contentsScale, self.backgroundColor, ASDisplayNodeNSStringFromUIContentMode(self.contentMode)];
|
||||
}
|
||||
|
||||
@end
|
||||
struct ASImageNodeDrawParameters {
|
||||
BOOL opaque;
|
||||
CGRect bounds;
|
||||
CGFloat contentsScale;
|
||||
UIColor *backgroundColor;
|
||||
UIViewContentMode contentMode;
|
||||
BOOL cropEnabled;
|
||||
BOOL forceUpscaling;
|
||||
CGRect cropRect;
|
||||
CGRect cropDisplayBounds;
|
||||
asimagenode_modification_block_t imageModificationBlock;
|
||||
};
|
||||
|
||||
@implementation ASImageNode
|
||||
{
|
||||
@@ -75,18 +47,32 @@
|
||||
void (^_displayCompletionBlock)(BOOL canceled);
|
||||
ASDN::RecursiveMutex _imageLock;
|
||||
|
||||
// Drawing
|
||||
ASImageNodeDrawParameters _drawParameter;
|
||||
ASTextNode *_debugLabelNode;
|
||||
|
||||
// Cropping.
|
||||
BOOL _cropEnabled; // Defaults to YES.
|
||||
BOOL _forceUpscaling; //Defaults to NO.
|
||||
CGRect _cropRect; // Defaults to CGRectMake(0.5, 0.5, 0, 0)
|
||||
CGRect _cropDisplayBounds;
|
||||
|
||||
ASTextNode *_debugLabelNode;
|
||||
CGRect _cropDisplayBounds; // Defaults to CGRectNull
|
||||
}
|
||||
|
||||
@synthesize image = _image;
|
||||
@synthesize imageModificationBlock = _imageModificationBlock;
|
||||
|
||||
#pragma mark - NSObject
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
[super initialize];
|
||||
|
||||
if (self != [ASImageNode class]) {
|
||||
// Prevent custom drawing in subclasses
|
||||
ASDisplayNodeAssert(!ASSubclassOverridesClassSelector([ASImageNode class], self, @selector(displayWithParameters:isCancelled:)), @"Subclass %@ must not override displayWithParameters:isCancelled: method. Custom drawing in %@ subclass is not supported.", NSStringFromClass(self), NSStringFromClass([ASImageNode class]));
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (!(self = [super init]))
|
||||
@@ -124,6 +110,8 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - Layout and Sizing
|
||||
|
||||
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
|
||||
{
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
@@ -136,6 +124,8 @@
|
||||
return CGSizeZero;
|
||||
}
|
||||
|
||||
#pragma mark - Setter / Getter
|
||||
|
||||
- (void)setImage:(UIImage *)image
|
||||
{
|
||||
_imageLock.lock();
|
||||
@@ -177,54 +167,72 @@
|
||||
self.placeholderEnabled = placeholderColor != nil;
|
||||
}
|
||||
|
||||
#pragma mark - Drawing
|
||||
|
||||
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
|
||||
{
|
||||
return [[_ASImageNodeDrawParameters alloc] initWithImage:self.image
|
||||
bounds:self.bounds
|
||||
opaque:self.opaque
|
||||
contentsScale:self.contentsScaleForDisplay
|
||||
backgroundColor:self.backgroundColor
|
||||
contentMode:self.contentMode];
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
|
||||
_drawParameter = {
|
||||
.bounds = self.bounds,
|
||||
.opaque = self.opaque,
|
||||
.contentsScale = _contentsScaleForDisplay,
|
||||
.backgroundColor = self.backgroundColor,
|
||||
.contentMode = self.contentMode,
|
||||
.cropEnabled = _cropEnabled,
|
||||
.forceUpscaling = _forceUpscaling,
|
||||
.cropRect = _cropRect,
|
||||
.cropDisplayBounds = _cropDisplayBounds,
|
||||
.imageModificationBlock = _imageModificationBlock
|
||||
};
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSDictionary *)debugLabelAttributes
|
||||
{
|
||||
return @{ NSFontAttributeName: [UIFont systemFontOfSize:15.0],
|
||||
NSForegroundColorAttributeName: [UIColor redColor] };
|
||||
return @{
|
||||
NSFontAttributeName: [UIFont systemFontOfSize:15.0],
|
||||
NSForegroundColorAttributeName: [UIColor redColor]
|
||||
};
|
||||
}
|
||||
|
||||
- (UIImage *)displayWithParameters:(_ASImageNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
|
||||
- (UIImage *)displayWithParameters:(id<NSObject> *)parameter isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled
|
||||
{
|
||||
UIImage *image = parameters.image;
|
||||
if (!image) {
|
||||
UIImage *image = self.image;
|
||||
if (image == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
CGRect drawParameterBounds = CGRectZero;
|
||||
BOOL forceUpscaling = NO;
|
||||
BOOL cropEnabled = NO;
|
||||
BOOL isOpaque = parameters.opaque;
|
||||
UIColor *backgroundColor = parameters.backgroundColor;
|
||||
UIViewContentMode contentMode = parameters.contentMode;
|
||||
BOOL cropEnabled = YES;
|
||||
BOOL isOpaque = NO;
|
||||
UIColor *backgroundColor = nil;
|
||||
UIViewContentMode contentMode = UIViewContentModeScaleAspectFill;
|
||||
CGFloat contentsScale = 0.0;
|
||||
CGRect cropDisplayBounds = CGRectZero;
|
||||
CGRect cropRect = CGRectZero;
|
||||
asimagenode_modification_block_t imageModificationBlock;
|
||||
|
||||
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
{
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
ASImageNodeDrawParameters drawParameter = _drawParameter;
|
||||
|
||||
// FIXME: There is a small risk of these values changing between the main thread creation of drawParameters, and the execution of this method.
|
||||
// We should package these up into the draw parameters object. Might be easiest to create a struct for the non-objects and make it one property.
|
||||
cropEnabled = _cropEnabled;
|
||||
forceUpscaling = _forceUpscaling;
|
||||
contentsScale = _contentsScaleForDisplay;
|
||||
cropDisplayBounds = _cropDisplayBounds;
|
||||
cropRect = _cropRect;
|
||||
imageModificationBlock = _imageModificationBlock;
|
||||
drawParameterBounds = drawParameter.bounds;
|
||||
forceUpscaling = drawParameter.forceUpscaling;
|
||||
cropEnabled = drawParameter.cropEnabled;
|
||||
isOpaque = drawParameter.opaque;
|
||||
backgroundColor = drawParameter.backgroundColor;
|
||||
contentMode = drawParameter.contentMode;
|
||||
contentsScale = drawParameter.contentsScale;
|
||||
cropDisplayBounds = drawParameter.cropDisplayBounds;
|
||||
cropRect = drawParameter.cropRect;
|
||||
imageModificationBlock = drawParameter.imageModificationBlock;
|
||||
}
|
||||
|
||||
BOOL hasValidCropBounds = cropEnabled && !CGRectIsNull(cropDisplayBounds) && !CGRectIsEmpty(cropDisplayBounds);
|
||||
CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : parameters.bounds);
|
||||
CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : drawParameterBounds);
|
||||
|
||||
ASDisplayNodeContextModifier preContextBlock = self.willDisplayNodeContentWithRenderingContext;
|
||||
ASDisplayNodeContextModifier postContextBlock = self.didDisplayNodeContentWithRenderingContext;
|
||||
@@ -359,7 +367,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
- (void)setNeedsDisplayWithCompletion:(void (^ _Nullable)(BOOL canceled))displayCompletionBlock
|
||||
{
|
||||
if (self.displaySuspended) {
|
||||
@@ -378,6 +385,7 @@
|
||||
}
|
||||
|
||||
#pragma mark - Cropping
|
||||
|
||||
- (BOOL)isCropEnabled
|
||||
{
|
||||
ASDN::MutexLocker l(_imageLock);
|
||||
@@ -462,6 +470,7 @@
|
||||
}
|
||||
|
||||
#pragma mark - Debug
|
||||
|
||||
- (void)layout
|
||||
{
|
||||
[super layout];
|
||||
@@ -477,6 +486,7 @@
|
||||
@end
|
||||
|
||||
#pragma mark - Extras
|
||||
|
||||
extern asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat borderWidth, UIColor *borderColor)
|
||||
{
|
||||
return ^(UIImage *originalImage) {
|
||||
|
||||
@@ -44,9 +44,11 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
CGFloat _currentImageQuality;
|
||||
CGFloat _renderedImageQuality;
|
||||
|
||||
// TODO: Move this to flags
|
||||
BOOL _delegateSupportsDidStartFetchingData;
|
||||
BOOL _delegateSupportsDidFailWithError;
|
||||
BOOL _delegateSupportsImageNodeDidFinishDecoding;
|
||||
BOOL _delegateSupportsDidFinishDecoding;
|
||||
BOOL _delegateSupportsDidLoadImage;
|
||||
|
||||
BOOL _shouldRenderProgressImages;
|
||||
|
||||
@@ -214,7 +216,8 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
|
||||
_delegateSupportsDidStartFetchingData = [delegate respondsToSelector:@selector(imageNodeDidStartFetchingData:)];
|
||||
_delegateSupportsDidFailWithError = [delegate respondsToSelector:@selector(imageNode:didFailWithError:)];
|
||||
_delegateSupportsImageNodeDidFinishDecoding = [delegate respondsToSelector:@selector(imageNodeDidFinishDecoding:)];
|
||||
_delegateSupportsDidFinishDecoding = [delegate respondsToSelector:@selector(imageNodeDidFinishDecoding:)];
|
||||
_delegateSupportsDidLoadImage = [delegate respondsToSelector:@selector(imageNode:didLoadImage:)];
|
||||
}
|
||||
|
||||
- (id<ASNetworkImageNodeDelegate>)delegate
|
||||
@@ -494,7 +497,9 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.currentImageQuality = 1.0;
|
||||
});
|
||||
[_delegate imageNode:self didLoadImage:self.image];
|
||||
if (_delegateSupportsDidLoadImage) {
|
||||
[_delegate imageNode:self didLoadImage:self.image];
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -529,7 +534,9 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
strongSelf->_cacheUUID = nil;
|
||||
|
||||
if (imageContainer != nil) {
|
||||
[strongSelf->_delegate imageNode:strongSelf didLoadImage:strongSelf.image];
|
||||
if (strongSelf->_delegateSupportsDidLoadImage) {
|
||||
[strongSelf->_delegate imageNode:strongSelf didLoadImage:strongSelf.image];
|
||||
}
|
||||
}
|
||||
else if (error && strongSelf->_delegateSupportsDidFailWithError) {
|
||||
[strongSelf->_delegate imageNode:strongSelf didFailWithError:error];
|
||||
@@ -581,7 +588,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
[super displayDidFinish];
|
||||
|
||||
ASDN::MutexLocker l(_lock);
|
||||
if (_delegateSupportsImageNodeDidFinishDecoding && self.layer.contents != nil) {
|
||||
if (_delegateSupportsDidFinishDecoding && self.layer.contents != nil) {
|
||||
/* We store the image quality in _currentImageQuality whenever _image is set. On the following displayDidFinish, we'll know that
|
||||
_currentImageQuality is the quality of the image that has just finished rendering. In order for this to be accurate, we
|
||||
need to be sure we are on main thread when we set _currentImageQuality. Otherwise, it is possible for _currentImageQuality
|
||||
|
||||
@@ -44,18 +44,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* The frame of the table view changes as table cells are added and deleted.
|
||||
*
|
||||
* @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants.
|
||||
*
|
||||
* @param asyncDataFetchingEnabled This option is reserved for future use, and currently a no-op.
|
||||
*
|
||||
* @discussion If asyncDataFetching is enabled, the `ASTableView` will fetch data through `tableView:numberOfRowsInSection:` and
|
||||
* `tableView:nodeForRowAtIndexPath:` in async mode from background thread. Otherwise, the methods will be invoked synchronically
|
||||
* from calling thread.
|
||||
* Enabling asyncDataFetching could avoid blocking main thread for `ASCellNode` allocation, which is frequently reported issue for
|
||||
* large scale data. On another hand, the application code need take the responsibility to avoid data inconsistence. Specifically,
|
||||
* we will lock the data source through `tableViewLockDataSource`, and unlock it by `tableViewUnlockDataSource` after the data fetching.
|
||||
* The application should not update the data source while the data source is locked, to keep data consistence.
|
||||
*/
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled;
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style;
|
||||
|
||||
/**
|
||||
* Tuning parameters for a range type in full mode.
|
||||
@@ -363,8 +353,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* due to the data access in async mode.
|
||||
*
|
||||
* @param tableView The sender.
|
||||
* @deprecated The data source is always accessed on the main thread, and this method will not be called.
|
||||
*/
|
||||
- (void)tableViewLockDataSource:(ASTableView *)tableView;
|
||||
- (void)tableViewLockDataSource:(ASTableView *)tableView ASDISPLAYNODE_DEPRECATED;
|
||||
|
||||
/**
|
||||
* Indicator to unlock the data source for data fetching in asyn mode.
|
||||
@@ -372,8 +363,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* due to the data access in async mode.
|
||||
*
|
||||
* @param tableView The sender.
|
||||
* @deprecated The data source is always accessed on the main thread, and this method will not be called.
|
||||
*/
|
||||
- (void)tableViewUnlockDataSource:(ASTableView *)tableView;
|
||||
- (void)tableViewUnlockDataSource:(ASTableView *)tableView ASDISPLAYNODE_DEPRECATED;
|
||||
|
||||
@end
|
||||
|
||||
@@ -390,6 +382,17 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Informs the delegate that the table view will add the node
|
||||
* at the given index path to the view hierarchy.
|
||||
*
|
||||
* @param tableView The sender.
|
||||
* @param indexPath The index path of the row that will be displayed.
|
||||
*
|
||||
* @warning AsyncDisplayKit processes table view edits asynchronously. The index path
|
||||
* passed into this method may not correspond to the same item in your data source
|
||||
* if your data source has been updated since the last edit was processed.
|
||||
*/
|
||||
- (void)tableView:(ASTableView *)tableView willDisplayNodeForRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
@@ -434,6 +437,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* Informs the delegate that the table view did remove the node which was previously
|
||||
* at the given index path from the view hierarchy.
|
||||
*
|
||||
* @warning AsyncDisplayKit processes table view edits asynchronously. The index path
|
||||
* passed into this method may not correspond to the same item in your data source
|
||||
* if your data source has been updated since the last edit was processed.
|
||||
*
|
||||
* This method is deprecated. Use @c tableView:didEndDisplayingNode:forRowAtIndexPath: instead.
|
||||
*/
|
||||
- (void)tableView:(ASTableView *)tableView didEndDisplayingNodeForRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED;
|
||||
@@ -443,4 +450,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@protocol ASTableViewDelegate <ASTableDelegate>
|
||||
@end
|
||||
|
||||
@interface ASTableView (Deprecated)
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled ASDISPLAYNODE_DEPRECATED;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -100,8 +100,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
|
||||
ASRangeController *_rangeController;
|
||||
|
||||
BOOL _asyncDataFetchingEnabled;
|
||||
|
||||
ASBatchContext *_batchContext;
|
||||
|
||||
NSIndexPath *_pendingVisibleIndexPath;
|
||||
@@ -133,12 +131,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
unsigned int asyncDataSourceNumberOfSectionsInTableView:1;
|
||||
unsigned int asyncDataSourceTableViewNodeBlockForRowAtIndexPath:1;
|
||||
unsigned int asyncDataSourceTableViewNodeForRowAtIndexPath:1;
|
||||
unsigned int asyncDataSourceTableViewLockDataSource:1;
|
||||
unsigned int asyncDataSourceTableViewUnlockDataSource:1;
|
||||
} _asyncDataSourceFlags;
|
||||
}
|
||||
|
||||
@property (atomic, assign) BOOL asyncDataSourceLocked;
|
||||
@property (nonatomic, strong, readwrite) ASDataController *dataController;
|
||||
|
||||
// Used only when ASTableView is created directly rather than through ASTableNode.
|
||||
@@ -177,16 +172,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
_rangeController.dataSource = self;
|
||||
_rangeController.delegate = self;
|
||||
|
||||
_dataController = [[dataControllerClass alloc] initWithAsyncDataFetching:NO];
|
||||
_dataController = [[dataControllerClass alloc] init];
|
||||
_dataController.dataSource = self;
|
||||
_dataController.delegate = _rangeController;
|
||||
_dataController.environmentDelegate = self;
|
||||
|
||||
_layoutController.dataSource = _dataController;
|
||||
|
||||
_asyncDataFetchingEnabled = NO;
|
||||
_asyncDataSourceLocked = NO;
|
||||
|
||||
_leadingScreensForBatching = 2.0;
|
||||
_batchContext = [[ASBatchContext alloc] init];
|
||||
|
||||
@@ -290,8 +282,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
_asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInTableView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)];
|
||||
_asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeForRowAtIndexPath:)];
|
||||
_asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath = [_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)];
|
||||
_asyncDataSourceFlags.asyncDataSourceTableViewLockDataSource = [_asyncDataSource respondsToSelector:@selector(tableViewLockDataSource:)];
|
||||
_asyncDataSourceFlags.asyncDataSourceTableViewUnlockDataSource = [_asyncDataSource respondsToSelector:@selector(tableViewUnlockDataSource:)];
|
||||
|
||||
// Data source must implement tableView:nodeBlockForRowAtIndexPath: or tableView:nodeForRowAtIndexPath:
|
||||
ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceTableViewNodeBlockForRowAtIndexPath || _asyncDataSourceFlags.asyncDataSourceTableViewNodeForRowAtIndexPath);
|
||||
@@ -968,6 +958,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didCompleteUpdatesInRangeController:(ASRangeController *)rangeController
|
||||
{
|
||||
[self _checkForBatchFetching];
|
||||
}
|
||||
|
||||
- (void)rangeController:(ASRangeController *)rangeController didInsertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
@@ -1078,28 +1073,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
CGSizeMake(_nodesConstrainedWidth, FLT_MAX));
|
||||
}
|
||||
|
||||
- (void)dataControllerLockDataSource
|
||||
{
|
||||
ASDisplayNodeAssert(!self.asyncDataSourceLocked, @"The data source has already been locked");
|
||||
|
||||
self.asyncDataSourceLocked = YES;
|
||||
|
||||
if (_asyncDataSourceFlags.asyncDataSourceTableViewLockDataSource) {
|
||||
[_asyncDataSource tableViewLockDataSource:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dataControllerUnlockDataSource
|
||||
{
|
||||
ASDisplayNodeAssert(self.asyncDataSourceLocked, @"The data source has already been unlocked");
|
||||
|
||||
self.asyncDataSourceLocked = NO;
|
||||
|
||||
if (_asyncDataSourceFlags.asyncDataSourceTableViewUnlockDataSource) {
|
||||
[_asyncDataSource tableViewUnlockDataSource:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section
|
||||
{
|
||||
return [_asyncDataSource tableView:self numberOfRowsInSection:section];
|
||||
|
||||
@@ -11,22 +11,19 @@
|
||||
#import "ASTextNode.h"
|
||||
#import "ASTextNode+Beta.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#import <AsyncDisplayKit/_ASDisplayLayer.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
|
||||
#import <AsyncDisplayKit/ASHighlightOverlayLayer.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
|
||||
#import "ASTextKitCoreTextAdditions.h"
|
||||
#import "ASTextKitComponents.h"
|
||||
#import "ASTextKitFontSizeAdjuster.h"
|
||||
#import "ASTextKitRenderer.h"
|
||||
#import "ASTextKitRenderer+Positioning.h"
|
||||
#import "ASTextKitShadower.h"
|
||||
|
||||
#import "ASInternalHelpers.h"
|
||||
#import "ASEqualityHelpers.h"
|
||||
#import "ASLayout.h"
|
||||
|
||||
static const NSTimeInterval ASTextNodeHighlightFadeOutDuration = 0.15;
|
||||
@@ -35,27 +32,10 @@ static const CGFloat ASTextNodeHighlightLightOpacity = 0.11;
|
||||
static const CGFloat ASTextNodeHighlightDarkOpacity = 0.22;
|
||||
static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncationAttribute";
|
||||
|
||||
@interface ASTextNodeDrawParameters : NSObject
|
||||
|
||||
@property (nonatomic, assign, readonly) CGRect bounds;
|
||||
|
||||
@property (nonatomic, strong, readonly) UIColor *backgroundColor;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASTextNodeDrawParameters
|
||||
|
||||
- (instancetype)initWithBounds:(CGRect)bounds
|
||||
backgroundColor:(UIColor *)backgroundColor
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_bounds = bounds;
|
||||
_backgroundColor = backgroundColor;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
struct ASTextNodeDrawParameter {
|
||||
CGRect bounds;
|
||||
UIColor *backgroundColor;
|
||||
};
|
||||
|
||||
@interface ASTextNode () <UIGestureRecognizerDelegate, NSLayoutManagerDelegate>
|
||||
|
||||
@@ -73,21 +53,34 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
||||
|
||||
NSString *_highlightedLinkAttributeName;
|
||||
id _highlightedLinkAttributeValue;
|
||||
ASTextNodeHighlightStyle _highlightStyle;
|
||||
NSRange _highlightRange;
|
||||
ASHighlightOverlayLayer *_activeHighlightLayer;
|
||||
|
||||
ASDN::Mutex _rendererLock;
|
||||
std::recursive_mutex _textLock;
|
||||
|
||||
CGSize _constrainedSize;
|
||||
|
||||
ASTextKitRenderer *_renderer;
|
||||
|
||||
ASTextNodeDrawParameter _drawParameter;
|
||||
|
||||
UILongPressGestureRecognizer *_longPressGestureRecognizer;
|
||||
}
|
||||
@dynamic placeholderEnabled;
|
||||
|
||||
#pragma mark - NSObject
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
[super initialize];
|
||||
|
||||
if (self != [ASTextNode class]) {
|
||||
// Prevent custom drawing in subclasses
|
||||
ASDisplayNodeAssert(!ASSubclassOverridesClassSelector([ASTextNode class], self, @selector(drawRect:withParameters:isCancelled:isRasterizing:)), @"Subclass %@ must not override drawRect:withParameters:isCancelled:isRasterizing: method. Custom drawing in %@ subclass is not supported.", NSStringFromClass(self), NSStringFromClass([ASTextNode class]));
|
||||
}
|
||||
}
|
||||
|
||||
static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
- (instancetype)init
|
||||
@@ -160,6 +153,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
NSString *plainString = [[_attributedText string] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
|
||||
NSString *truncationString = [_composedTruncationText string];
|
||||
if (plainString.length > 50)
|
||||
@@ -195,10 +190,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
- (void)didLoad
|
||||
{
|
||||
[super didLoad];
|
||||
|
||||
|
||||
// If we are view-backed and the delegate cares, support the long-press callback.
|
||||
SEL longPressCallback = @selector(textNode:longPressedLinkAttribute:value:atPoint:textRange:);
|
||||
if (!self.isLayerBacked && [self.delegate respondsToSelector:longPressCallback]) {
|
||||
if (!self.isLayerBacked && [_delegate respondsToSelector:longPressCallback]) {
|
||||
_longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_handleLongPress:)];
|
||||
_longPressGestureRecognizer.cancelsTouchesInView = self.longPressCancelsTouches;
|
||||
_longPressGestureRecognizer.delegate = self;
|
||||
@@ -227,7 +222,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
- (ASTextKitRenderer *)_rendererWithBounds:(CGRect)bounds
|
||||
{
|
||||
ASDN::MutexLocker l(_rendererLock);
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
if (_renderer == nil) {
|
||||
CGSize constrainedSize = _constrainedSize.width != -INFINITY ? _constrainedSize : bounds.size;
|
||||
_renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes]
|
||||
@@ -238,6 +234,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
- (ASTextKitAttributes)_rendererAttributes
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
return {
|
||||
.attributedString = _attributedText,
|
||||
.truncationAttributedString = _composedTruncationText,
|
||||
@@ -250,9 +248,28 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
};
|
||||
}
|
||||
|
||||
- (void)_invalidateRendererIfNeeded
|
||||
{
|
||||
[self _invalidateRendererIfNeededForBoundsSize:self.threadSafeBounds.size];
|
||||
}
|
||||
|
||||
- (void)_invalidateRendererIfNeededForBoundsSize:(CGSize)boundsSize
|
||||
{
|
||||
if ([self _needInvalidateRendererForBoundsSize:boundsSize]) {
|
||||
// Our bounds have changed to a size that is not identical to our constraining size,
|
||||
// so our previous layout information is invalid, and TextKit may draw at the
|
||||
// incorrect origin.
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
_constrainedSize = CGSizeMake(-INFINITY, -INFINITY);
|
||||
}
|
||||
[self _invalidateRenderer];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_invalidateRenderer
|
||||
{
|
||||
ASDN::MutexLocker l(_rendererLock);
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
if (_renderer) {
|
||||
// Destruction of the layout managers/containers/text storage is quite
|
||||
@@ -267,27 +284,13 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_invalidateRendererIfNeeded
|
||||
{
|
||||
[self _invalidateRendererIfNeededForBoundsSize:self.threadSafeBounds.size];
|
||||
}
|
||||
|
||||
- (void)_invalidateRendererIfNeededForBoundsSize:(CGSize)boundsSize
|
||||
{
|
||||
if ([self _needInvalidateRendererForBoundsSize:boundsSize]) {
|
||||
// Our bounds of frame have changed to a size that is not identical to our constraining size,
|
||||
// so our previous layout information is invalid, and TextKit may draw at the
|
||||
// incorrect origin.
|
||||
_constrainedSize = CGSizeMake(-INFINITY, -INFINITY);
|
||||
[self _invalidateRenderer];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Layout and Sizing
|
||||
|
||||
- (BOOL)_needInvalidateRendererForBoundsSize:(CGSize)boundsSize
|
||||
{
|
||||
if (!_renderer) {
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
if (_renderer == nil) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -322,6 +325,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
[super calculatedLayoutDidChange];
|
||||
|
||||
ASLayout *layout = self.calculatedLayout;
|
||||
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
if (layout != nil) {
|
||||
_constrainedSize = layout.size;
|
||||
_renderer.constrainedSize = layout.size;
|
||||
@@ -333,6 +338,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
ASDisplayNodeAssert(constrainedSize.width >= 0, @"Constrained width for text (%f) is too narrow", constrainedSize.width);
|
||||
ASDisplayNodeAssert(constrainedSize.height >= 0, @"Constrained height for text (%f) is too short", constrainedSize.height);
|
||||
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
_constrainedSize = constrainedSize;
|
||||
|
||||
// Instead of invalidating the renderer, in case this is a new call with a different constrained size,
|
||||
@@ -341,7 +348,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
[self setNeedsDisplay];
|
||||
|
||||
CGSize size = [[self _renderer] size];
|
||||
CGSize size = [self _renderer].size;
|
||||
if (_attributedText.length > 0) {
|
||||
CGFloat screenScale = ASScreenScale();
|
||||
self.ascender = round([[_attributedText attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL] ascender] * screenScale)/screenScale;
|
||||
@@ -359,6 +366,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
- (void)setAttributedText:(NSAttributedString *)attributedText
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
if (attributedText == nil) {
|
||||
attributedText = [[NSAttributedString alloc] initWithString:@"" attributes:nil];
|
||||
}
|
||||
@@ -387,17 +396,19 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
[self invalidateCalculatedLayout];
|
||||
|
||||
[self setNeedsDisplay];
|
||||
|
||||
|
||||
|
||||
// Accessiblity
|
||||
self.accessibilityLabel = _attributedText.string;
|
||||
|
||||
// We're an accessibility element by default if there is a string.
|
||||
self.isAccessibilityElement = _attributedText.length != 0;
|
||||
self.isAccessibilityElement = (_attributedText.length != 0); // We're an accessibility element by default if there is a string.
|
||||
}
|
||||
|
||||
#pragma mark - Text Layout
|
||||
|
||||
- (void)setExclusionPaths:(NSArray *)exclusionPaths
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
if (ASObjectIsEqual(exclusionPaths, _exclusionPaths)) {
|
||||
return;
|
||||
}
|
||||
@@ -410,34 +421,51 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
- (NSArray *)exclusionPaths
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
return _exclusionPaths;
|
||||
}
|
||||
|
||||
#pragma mark - Drawing
|
||||
|
||||
- (void)drawRect:(CGRect)bounds withParameters:(ASTextNodeDrawParameters *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing
|
||||
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
_drawParameter = {
|
||||
.backgroundColor = self.backgroundColor,
|
||||
.bounds = self.bounds
|
||||
};
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
- (void)drawRect:(CGRect)bounds withParameters:(id <NSObject>)p isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
ASTextNodeDrawParameter drawParameter = _drawParameter;
|
||||
CGRect drawParameterBounds = drawParameter.bounds;
|
||||
UIColor *backgroundColor = isRasterizing ? nil : drawParameter.backgroundColor;
|
||||
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
ASDisplayNodeAssert(context, @"This is no good without a context.");
|
||||
|
||||
CGContextSaveGState(context);
|
||||
|
||||
ASTextKitRenderer *renderer = [self _rendererWithBounds:parameters.bounds];
|
||||
ASTextKitRenderer *renderer = [self _rendererWithBounds:drawParameterBounds];
|
||||
UIEdgeInsets shadowPadding = [self shadowPaddingWithRenderer:renderer];
|
||||
CGPoint boundsOrigin = parameters.bounds.origin;
|
||||
CGPoint boundsOrigin = drawParameterBounds.origin;
|
||||
CGPoint textOrigin = CGPointMake(boundsOrigin.x - shadowPadding.left, boundsOrigin.y - shadowPadding.top);
|
||||
|
||||
// Fill background
|
||||
if (!isRasterizing) {
|
||||
UIColor *backgroundColor = parameters.backgroundColor;
|
||||
if (backgroundColor) {
|
||||
[backgroundColor setFill];
|
||||
UIRectFillUsingBlendMode(CGContextGetClipBoundingBox(context), kCGBlendModeCopy);
|
||||
}
|
||||
if (backgroundColor != nil) {
|
||||
[backgroundColor setFill];
|
||||
UIRectFillUsingBlendMode(CGContextGetClipBoundingBox(context), kCGBlendModeCopy);
|
||||
}
|
||||
|
||||
// Draw shadow
|
||||
[[renderer shadower] setShadowInContext:context];
|
||||
[renderer.shadower setShadowInContext:context];
|
||||
|
||||
// Draw text
|
||||
bounds.origin = textOrigin;
|
||||
@@ -446,11 +474,6 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
|
||||
{
|
||||
return [[ASTextNodeDrawParameters alloc] initWithBounds:self.threadSafeBounds backgroundColor:self.backgroundColor];
|
||||
}
|
||||
|
||||
#pragma mark - Attributes
|
||||
|
||||
- (id)linkAttributeValueAtPoint:(CGPoint)point
|
||||
@@ -470,6 +493,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
inAdditionalTruncationMessage:(out BOOL *)inAdditionalTruncationMessageOut
|
||||
forHighlighting:(BOOL)highlighting
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
ASTextKitRenderer *renderer = [self _renderer];
|
||||
NSRange visibleRange = renderer.firstVisibleRange;
|
||||
NSAttributedString *attributedString = _attributedText;
|
||||
@@ -562,6 +589,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if (gestureRecognizer == _longPressGestureRecognizer) {
|
||||
// Don't allow long press on truncation message
|
||||
if ([self _pendingTruncationTap]) {
|
||||
@@ -569,8 +598,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
}
|
||||
|
||||
// Ask our delegate if a long-press on an attribute is relevant
|
||||
if ([self.delegate respondsToSelector:@selector(textNode:shouldLongPressLinkAttribute:value:atPoint:)]) {
|
||||
return [self.delegate textNode:self
|
||||
if ([_delegate respondsToSelector:@selector(textNode:shouldLongPressLinkAttribute:value:atPoint:)]) {
|
||||
return [_delegate textNode:self
|
||||
shouldLongPressLinkAttribute:_highlightedLinkAttributeName
|
||||
value:_highlightedLinkAttributeValue
|
||||
atPoint:[gestureRecognizer locationInView:self.view]];
|
||||
@@ -591,8 +620,24 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
#pragma mark - Highlighting
|
||||
|
||||
- (ASTextNodeHighlightStyle)highlightStyle
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
return _highlightStyle;
|
||||
}
|
||||
|
||||
- (void)setHighlightStyle:(ASTextNodeHighlightStyle)highlightStyle
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
_highlightStyle = highlightStyle;
|
||||
}
|
||||
|
||||
- (NSRange)highlightRange
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
return _highlightRange;
|
||||
}
|
||||
|
||||
@@ -672,7 +717,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
NSMutableArray *converted = [NSMutableArray arrayWithCapacity:highlightRects.count];
|
||||
for (NSValue *rectValue in highlightRects) {
|
||||
UIEdgeInsets shadowPadding = _renderer.shadower.shadowPadding;
|
||||
CGRect rendererRect = [[self class] _adjustRendererRect:rectValue.CGRectValue forShadowPadding:shadowPadding];
|
||||
CGRect rendererRect = ASTextNodeAdjustRenderRectForShadowPadding(rectValue.CGRectValue, shadowPadding);
|
||||
CGRect highlightedRect = [self.layer convertRect:rendererRect toLayer:highlightTargetLayer];
|
||||
|
||||
// We set our overlay layer's frame to the bounds of the highlight target layer.
|
||||
@@ -709,6 +754,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
- (void)_clearHighlightIfNecessary
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if ([self _pendingLinkTap] || [self _pendingTruncationTap]) {
|
||||
[self setHighlightRange:NSMakeRange(0, 0) animated:YES];
|
||||
}
|
||||
@@ -726,29 +773,12 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
#pragma mark - Text rects
|
||||
|
||||
+ (CGRect)_adjustRendererRect:(CGRect)rendererRect forShadowPadding:(UIEdgeInsets)shadowPadding
|
||||
{
|
||||
static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UIEdgeInsets shadowPadding) {
|
||||
rendererRect.origin.x -= shadowPadding.left;
|
||||
rendererRect.origin.y -= shadowPadding.top;
|
||||
return rendererRect;
|
||||
}
|
||||
|
||||
- (NSArray *)_rectsForTextRange:(NSRange)textRange measureOption:(ASTextKitRendererMeasureOption)measureOption
|
||||
{
|
||||
NSArray *rects = [[self _renderer] rectsForTextRange:textRange measureOption:measureOption];
|
||||
NSMutableArray *adjustedRects = [NSMutableArray array];
|
||||
|
||||
for (NSValue *rectValue in rects) {
|
||||
CGRect rect = [rectValue CGRectValue];
|
||||
rect = [self.class _adjustRendererRect:rect forShadowPadding:self.shadowPadding];
|
||||
|
||||
NSValue *adjustedRectValue = [NSValue valueWithCGRect:rect];
|
||||
[adjustedRects addObject:adjustedRectValue];
|
||||
}
|
||||
|
||||
return adjustedRects;
|
||||
}
|
||||
|
||||
- (NSArray *)rectsForTextRange:(NSRange)textRange
|
||||
{
|
||||
return [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionCapHeight];
|
||||
@@ -759,22 +789,46 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
return [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionBlock];
|
||||
}
|
||||
|
||||
- (NSArray *)_rectsForTextRange:(NSRange)textRange measureOption:(ASTextKitRendererMeasureOption)measureOption
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
NSArray *rects = [[self _renderer] rectsForTextRange:textRange measureOption:measureOption];
|
||||
NSMutableArray *adjustedRects = [NSMutableArray array];
|
||||
|
||||
for (NSValue *rectValue in rects) {
|
||||
CGRect rect = [rectValue CGRectValue];
|
||||
rect = ASTextNodeAdjustRenderRectForShadowPadding(rect, self.shadowPadding);
|
||||
|
||||
NSValue *adjustedRectValue = [NSValue valueWithCGRect:rect];
|
||||
[adjustedRects addObject:adjustedRectValue];
|
||||
}
|
||||
|
||||
return adjustedRects;
|
||||
}
|
||||
|
||||
- (CGRect)trailingRect
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
CGRect rect = [[self _renderer] trailingRect];
|
||||
return [self.class _adjustRendererRect:rect forShadowPadding:self.shadowPadding];
|
||||
return ASTextNodeAdjustRenderRectForShadowPadding(rect, self.shadowPadding);
|
||||
}
|
||||
|
||||
- (CGRect)frameForTextRange:(NSRange)textRange
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
CGRect frame = [[self _renderer] frameForTextRange:textRange];
|
||||
return [self.class _adjustRendererRect:frame forShadowPadding:self.shadowPadding];
|
||||
return ASTextNodeAdjustRenderRectForShadowPadding(frame, self.shadowPadding);
|
||||
}
|
||||
|
||||
#pragma mark - Placeholders
|
||||
|
||||
- (void)setPlaceholderColor:(UIColor *)placeholderColor
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
_placeholderColor = placeholderColor;
|
||||
|
||||
// prevent placeholders if we don't have a color
|
||||
@@ -790,6 +844,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
return nil;
|
||||
}
|
||||
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
UIGraphicsBeginImageContext(size);
|
||||
[self.placeholderColor setFill];
|
||||
|
||||
@@ -816,8 +872,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
#pragma mark - Touch Handling
|
||||
|
||||
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
|
||||
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if (!_passthroughNonlinkTouches) {
|
||||
return [super pointInside:point withEvent:event];
|
||||
}
|
||||
@@ -846,9 +904,11 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
[super touchesBegan:touches withEvent:event];
|
||||
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
[super touchesBegan:touches withEvent:event];
|
||||
|
||||
CGPoint point = [[touches anyObject] locationInView:self.view];
|
||||
|
||||
@@ -866,8 +926,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1);
|
||||
|
||||
if (inAdditionalTruncationMessage) {
|
||||
ASTextKitRenderer *renderer = [self _renderer];
|
||||
NSRange visibleRange = renderer.firstVisibleRange;
|
||||
NSRange visibleRange = [self _renderer].firstVisibleRange;
|
||||
NSRange truncationMessageRange = [self _additionalTruncationMessageRangeWithVisibleRange:visibleRange];
|
||||
[self _setHighlightRange:truncationMessageRange forAttributeName:ASTextNodeTruncationTokenAttributeName value:nil animated:YES];
|
||||
} else if (range.length && !linkCrossesVisibleRange && linkAttributeValue != nil && linkAttributeName != nil) {
|
||||
@@ -878,15 +937,17 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[super touchesCancelled:touches withEvent:event];
|
||||
|
||||
|
||||
[self _clearHighlightIfNecessary];
|
||||
}
|
||||
|
||||
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[super touchesEnded:touches withEvent:event];
|
||||
|
||||
|
||||
if ([self _pendingLinkTap] && [_delegate respondsToSelector:@selector(textNode:tappedLinkAttribute:value:atPoint:textRange:)]) {
|
||||
CGPoint point = [[touches anyObject] locationInView:self.view];
|
||||
[_delegate textNode:self tappedLinkAttribute:_highlightedLinkAttributeName value:_highlightedLinkAttributeValue atPoint:point textRange:_highlightRange];
|
||||
@@ -903,6 +964,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[super touchesMoved:touches withEvent:event];
|
||||
|
||||
UITouch *touch = [touches anyObject];
|
||||
@@ -928,22 +990,28 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
- (void)_handleLongPress:(UILongPressGestureRecognizer *)longPressRecognizer
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
// Respond to long-press when it begins, not when it ends.
|
||||
if (longPressRecognizer.state == UIGestureRecognizerStateBegan) {
|
||||
if ([self.delegate respondsToSelector:@selector(textNode:longPressedLinkAttribute:value:atPoint:textRange:)]) {
|
||||
if ([_delegate respondsToSelector:@selector(textNode:longPressedLinkAttribute:value:atPoint:textRange:)]) {
|
||||
CGPoint touchPoint = [_longPressGestureRecognizer locationInView:self.view];
|
||||
[self.delegate textNode:self longPressedLinkAttribute:_highlightedLinkAttributeName value:_highlightedLinkAttributeValue atPoint:touchPoint textRange:_highlightRange];
|
||||
[_delegate textNode:self longPressedLinkAttribute:_highlightedLinkAttributeName value:_highlightedLinkAttributeValue atPoint:touchPoint textRange:_highlightRange];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)_pendingLinkTap
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
return (_highlightedLinkAttributeValue != nil && ![self _pendingTruncationTap]) && _delegate != nil;
|
||||
}
|
||||
|
||||
- (BOOL)_pendingTruncationTap
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
return [_highlightedLinkAttributeName isEqualToString:ASTextNodeTruncationTokenAttributeName];
|
||||
}
|
||||
|
||||
@@ -951,11 +1019,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
- (CGColorRef)shadowColor
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
return _shadowColor;
|
||||
}
|
||||
|
||||
- (void)setShadowColor:(CGColorRef)shadowColor
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
if (_shadowColor != shadowColor) {
|
||||
if (shadowColor != NULL) {
|
||||
CGColorRetain(shadowColor);
|
||||
@@ -968,11 +1040,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
- (CGSize)shadowOffset
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
return _shadowOffset;
|
||||
}
|
||||
|
||||
- (void)setShadowOffset:(CGSize)shadowOffset
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
if (!CGSizeEqualToSize(_shadowOffset, shadowOffset)) {
|
||||
_shadowOffset = shadowOffset;
|
||||
[self _invalidateRenderer];
|
||||
@@ -982,11 +1058,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
- (CGFloat)shadowOpacity
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
return _shadowOpacity;
|
||||
}
|
||||
|
||||
- (void)setShadowOpacity:(CGFloat)shadowOpacity
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
if (_shadowOpacity != shadowOpacity) {
|
||||
_shadowOpacity = shadowOpacity;
|
||||
[self _invalidateRenderer];
|
||||
@@ -996,11 +1076,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
- (CGFloat)shadowRadius
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
return _shadowRadius;
|
||||
}
|
||||
|
||||
- (void)setShadowRadius:(CGFloat)shadowRadius
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
if (_shadowRadius != shadowRadius) {
|
||||
_shadowRadius = shadowRadius;
|
||||
[self _invalidateRenderer];
|
||||
@@ -1015,6 +1099,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
|
||||
- (UIEdgeInsets)shadowPaddingWithRenderer:(ASTextKitRenderer *)renderer
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
return renderer.shadower.shadowPadding;
|
||||
}
|
||||
|
||||
@@ -1032,6 +1118,8 @@ static NSAttributedString *DefaultTruncationAttributedString()
|
||||
|
||||
- (void)setTruncationAttributedText:(NSAttributedString *)truncationAttributedText
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
if (ASObjectIsEqual(_truncationAttributedText, truncationAttributedText)) {
|
||||
return;
|
||||
}
|
||||
@@ -1042,6 +1130,8 @@ static NSAttributedString *DefaultTruncationAttributedString()
|
||||
|
||||
- (void)setAdditionalTruncationMessage:(NSAttributedString *)additionalTruncationMessage
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
if (ASObjectIsEqual(_additionalTruncationMessage, additionalTruncationMessage)) {
|
||||
return;
|
||||
}
|
||||
@@ -1052,6 +1142,8 @@ static NSAttributedString *DefaultTruncationAttributedString()
|
||||
|
||||
- (void)setTruncationMode:(NSLineBreakMode)truncationMode
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
if (_truncationMode != truncationMode) {
|
||||
_truncationMode = truncationMode;
|
||||
[self _invalidateRenderer];
|
||||
@@ -1061,12 +1153,16 @@ static NSAttributedString *DefaultTruncationAttributedString()
|
||||
|
||||
- (BOOL)isTruncated
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
ASTextKitRenderer *renderer = [self _renderer];
|
||||
return renderer.firstVisibleRange.length < _attributedText.length;
|
||||
}
|
||||
|
||||
- (void)setPointSizeScaleFactors:(NSArray *)pointSizeScaleFactors
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
if ([_pointSizeScaleFactors isEqualToArray:pointSizeScaleFactors] == NO) {
|
||||
_pointSizeScaleFactors = pointSizeScaleFactors;
|
||||
[self _invalidateRenderer];
|
||||
@@ -1075,15 +1171,19 @@ static NSAttributedString *DefaultTruncationAttributedString()
|
||||
|
||||
- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines
|
||||
{
|
||||
if (_maximumNumberOfLines != maximumNumberOfLines) {
|
||||
_maximumNumberOfLines = maximumNumberOfLines;
|
||||
[self _invalidateRenderer];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
if (_maximumNumberOfLines != maximumNumberOfLines) {
|
||||
_maximumNumberOfLines = maximumNumberOfLines;
|
||||
[self _invalidateRenderer];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSUInteger)lineCount
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
return [[self _renderer] lineCount];
|
||||
}
|
||||
|
||||
@@ -1091,6 +1191,8 @@ static NSAttributedString *DefaultTruncationAttributedString()
|
||||
|
||||
- (void)_updateComposedTruncationText
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
_composedTruncationText = [self _prepareTruncationStringForDrawing:[self _composedTruncationText]];
|
||||
}
|
||||
|
||||
@@ -1107,6 +1209,8 @@ static NSAttributedString *DefaultTruncationAttributedString()
|
||||
*/
|
||||
- (NSRange)_additionalTruncationMessageRangeWithVisibleRange:(NSRange)visibleRange
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
// Check if we even have an additional truncation message.
|
||||
if (!_additionalTruncationMessage) {
|
||||
return NSMakeRange(NSNotFound, 0);
|
||||
@@ -1118,8 +1222,7 @@ static NSAttributedString *DefaultTruncationAttributedString()
|
||||
NSUInteger additionalTruncationMessageLength = _additionalTruncationMessage.length;
|
||||
// We get the location of the truncation token, then add the length of the
|
||||
// truncation attributed string +1 for the space between.
|
||||
NSRange range = NSMakeRange(truncationTokenIndex + _truncationAttributedText.length + 1, additionalTruncationMessageLength);
|
||||
return range;
|
||||
return NSMakeRange(truncationTokenIndex + _truncationAttributedText.length + 1, additionalTruncationMessageLength);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1129,6 +1232,8 @@ static NSAttributedString *DefaultTruncationAttributedString()
|
||||
*/
|
||||
- (NSAttributedString *)_composedTruncationText
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
//If we have neither return the default
|
||||
if (!_additionalTruncationMessage && !_truncationAttributedText) {
|
||||
return _composedTruncationText;
|
||||
@@ -1157,6 +1262,8 @@ static NSAttributedString *DefaultTruncationAttributedString()
|
||||
*/
|
||||
- (NSAttributedString *)_prepareTruncationStringForDrawing:(NSAttributedString *)truncationString
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> l(_textLock);
|
||||
|
||||
truncationString = ASCleanseAttributedStringOfCoreTextAttributes(truncationString);
|
||||
NSMutableAttributedString *truncationMutableString = [truncationString mutableCopy];
|
||||
// Grab the attributes from the full string
|
||||
|
||||
@@ -453,7 +453,9 @@ static NSString * const kStatus = @"status";
|
||||
|
||||
- (void)setDelegate:(id<ASVideoNodeDelegate>)delegate
|
||||
{
|
||||
[super setDelegate:delegate];
|
||||
_delegate = delegate;
|
||||
|
||||
if (_delegate == nil) {
|
||||
memset(&_delegateFlags, 0, sizeof(_delegateFlags));
|
||||
} else {
|
||||
|
||||
@@ -7,3 +7,13 @@
|
||||
#ifdef __OBJC__
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
|
||||
|
||||
// CocoaPods has a preproceessor macro for PIN_REMOTE_IMAGE, if already defined, okay
|
||||
#ifndef PIN_REMOTE_IMAGE
|
||||
|
||||
// For Carthage or manual builds, this will define PIN_REMOTE_IMAGE if the header is available in the
|
||||
// search path e.g. they've dragged in the framework (technically this will not be able to detect if
|
||||
// a user does not include the framework in the link binary with build step).
|
||||
#define PIN_REMOTE_IMAGE __has_include(<PINRemoteImage/PINRemoteImage.h>)
|
||||
#endif
|
||||
@@ -49,8 +49,8 @@ extern BOOL ASRangeTuningParametersEqualToRangeTuningParameters(ASRangeTuningPar
|
||||
.trailingBufferScreenfuls = 0.25
|
||||
};
|
||||
_tuningParameters[ASLayoutRangeModeMinimum][ASLayoutRangeTypeFetchData] = {
|
||||
.leadingBufferScreenfuls = 0.25,
|
||||
.trailingBufferScreenfuls = 0.5
|
||||
.leadingBufferScreenfuls = 0.5,
|
||||
.trailingBufferScreenfuls = 0.25
|
||||
};
|
||||
|
||||
_tuningParameters[ASLayoutRangeModeVisibleOnly][ASLayoutRangeTypeDisplay] = {
|
||||
|
||||
@@ -26,17 +26,6 @@
|
||||
|
||||
@implementation ASChangeSetDataController
|
||||
|
||||
- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled
|
||||
{
|
||||
if (!(self = [super initWithAsyncDataFetching:asyncDataFetchingEnabled])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_changeSetBatchUpdateCounter = 0;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Batching (External API)
|
||||
|
||||
- (void)beginUpdates
|
||||
@@ -66,6 +55,8 @@
|
||||
[super deleteSections:change.indexSet withAnimationOptions:change.animationOptions];
|
||||
}
|
||||
|
||||
// TODO: Shouldn't reloads be processed before deletes, since deletes affect
|
||||
// the index space and reloads don't?
|
||||
for (_ASHierarchySectionChange *change in [_changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload]) {
|
||||
[super reloadSections:change.indexSet withAnimationOptions:change.animationOptions];
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
NSMutableDictionary<NSString *, NSMutableArray<ASIndexedNodeContext *> *> *_pendingContexts;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super initWithAsyncDataFetching:asyncDataFetchingEnabled];
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_pendingContexts = [NSMutableDictionary dictionary];
|
||||
}
|
||||
@@ -53,15 +53,13 @@
|
||||
|
||||
- (void)willReloadData
|
||||
{
|
||||
NSArray *keys = _pendingContexts.allKeys;
|
||||
for (NSString *kind in keys) {
|
||||
NSMutableArray<ASIndexedNodeContext *> *contexts = _pendingContexts[kind];
|
||||
[_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray<ASIndexedNodeContext *> * _Nonnull contexts, __unused BOOL * _Nonnull stop) {
|
||||
// Remove everything that existed before the reload, now that we're ready to insert replacements
|
||||
NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind];
|
||||
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
|
||||
|
||||
NSArray *editingNodes = [self editingNodesOfKind:kind];
|
||||
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)];
|
||||
NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)];
|
||||
[self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil];
|
||||
|
||||
// Insert each section
|
||||
@@ -75,8 +73,8 @@
|
||||
[self batchLayoutNodesFromContexts:contexts ofKind:kind completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
|
||||
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
|
||||
}];
|
||||
[_pendingContexts removeObjectForKey:kind];
|
||||
}
|
||||
}];
|
||||
[_pendingContexts removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)prepareForInsertSections:(NSIndexSet *)sections
|
||||
|
||||
@@ -57,17 +57,6 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
|
||||
*/
|
||||
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController;
|
||||
|
||||
/**
|
||||
Lock the data source for data fetching.
|
||||
*/
|
||||
- (void)dataControllerLockDataSource;
|
||||
|
||||
/**
|
||||
Unlock the data source after data fetching.
|
||||
*/
|
||||
- (void)dataControllerUnlockDataSource;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@protocol ASDataControllerEnvironmentDelegate
|
||||
@@ -135,20 +124,6 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
|
||||
*/
|
||||
@property (nonatomic, weak) id<ASDataControllerEnvironmentDelegate> environmentDelegate;
|
||||
|
||||
/**
|
||||
* Designated initializer.
|
||||
*
|
||||
* @param asyncDataFetchingEnabled Enable the data fetching in async mode.
|
||||
*
|
||||
* @discussion If enabled, we will fetch data through `dataController:nodeAtIndexPath:` and `dataController:rowsInSection:` in background thread.
|
||||
* Otherwise, the methods will be invoked synchronically in calling thread. Enabling data fetching in async mode could avoid blocking main thread
|
||||
* while allocating cell on main thread, which is frequently reported issue for handling large scale data. On another hand, the application code
|
||||
* will take the responsibility to avoid data inconsistency. Specifically, we will lock the data source through `dataControllerLockDataSource`,
|
||||
* and unlock it by `dataControllerUnlockDataSource` after the data fetching. The application should not update the data source while
|
||||
* the data source is locked.
|
||||
*/
|
||||
- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled;
|
||||
|
||||
/** @name Data Updating */
|
||||
|
||||
- (void)beginUpdates;
|
||||
|
||||
@@ -44,8 +44,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
NSMutableArray *_pendingEditCommandBlocks; // To be run on the main thread. Handles begin/endUpdates tracking.
|
||||
NSOperationQueue *_editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes.
|
||||
|
||||
BOOL _asyncDataFetchingEnabled;
|
||||
|
||||
BOOL _initialReloadDataHasBeenCalled;
|
||||
|
||||
BOOL _delegateDidInsertNodes;
|
||||
@@ -62,7 +60,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled
|
||||
- (instancetype)init
|
||||
{
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
@@ -83,7 +81,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
_editingTransactionQueue.name = @"org.AsyncDisplayKit.ASDataController.editingTransactionQueue";
|
||||
|
||||
_batchUpdateCounter = 0;
|
||||
_asyncDataFetchingEnabled = asyncDataFetchingEnabled;
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -119,6 +116,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
- (void)batchLayoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock
|
||||
{
|
||||
ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd));
|
||||
|
||||
NSUInteger blockSize = [[ASDataController class] parallelProcessorCount] * kASDataControllerSizingCountPerProcessor;
|
||||
NSUInteger count = contexts.count;
|
||||
|
||||
@@ -143,8 +142,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
*/
|
||||
- (void)_layoutNode:(ASCellNode *)node withConstrainedSize:(ASSizeRange)constrainedSize
|
||||
{
|
||||
[node measureWithSizeRange:constrainedSize];
|
||||
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
|
||||
CGSize size = [node measureWithSizeRange:constrainedSize].size;
|
||||
node.frame = { .size = size };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,6 +151,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
*/
|
||||
- (void)_batchLayoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd));
|
||||
|
||||
[self batchLayoutNodesFromContexts:contexts ofKind:ASDataControllerRowNodeKind completion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
|
||||
// Insert finished nodes into data storage
|
||||
[self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
@@ -163,6 +164,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
*/
|
||||
- (void)_layoutNodes:(NSArray<ASCellNode *> *)nodes fromContexts:(NSArray<ASIndexedNodeContext *> *)contexts atIndexesOfRange:(NSRange)range ofKind:(NSString *)kind
|
||||
{
|
||||
ASDisplayNodeAssert([NSOperationQueue currentQueue] != _editingTransactionQueue, @"%@ should not be called on the editing transaction queue", NSStringFromSelector(_cmd));
|
||||
|
||||
if (_dataSource == nil) {
|
||||
return;
|
||||
}
|
||||
@@ -182,6 +185,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
- (void)_layoutNodesFromContexts:(NSArray<ASIndexedNodeContext *> *)contexts ofKind:(NSString *)kind completion:(ASDataControllerCompletionBlock)completionBlock
|
||||
{
|
||||
ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd));
|
||||
|
||||
if (!contexts.count || _dataSource == nil) {
|
||||
return;
|
||||
}
|
||||
@@ -278,7 +283,6 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
NSMutableArray *editingNodes = _editingNodes[kind];
|
||||
ASInsertElementsIntoMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths, nodes);
|
||||
_editingNodes[kind] = editingNodes;
|
||||
|
||||
// Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads.
|
||||
NSMutableArray *completedNodes = ASTwoDimensionalArrayDeepMutableCopy(editingNodes);
|
||||
@@ -359,7 +363,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
*/
|
||||
- (void)_insertNodes:(NSArray *)nodes atIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd));
|
||||
|
||||
[self insertNodes:nodes ofKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if (_delegateDidInsertNodes)
|
||||
[_delegate dataController:self didInsertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
}];
|
||||
@@ -373,7 +381,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
*/
|
||||
- (void)_deleteNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd));
|
||||
|
||||
[self deleteNodesOfKind:ASDataControllerRowNodeKind atIndexPaths:indexPaths completion:^(NSArray *nodes, NSArray *indexPaths) {
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if (_delegateDidDeleteNodes)
|
||||
[_delegate dataController:self didDeleteNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
}];
|
||||
@@ -387,7 +399,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
*/
|
||||
- (void)_insertSections:(NSMutableArray *)sections atIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd));
|
||||
|
||||
[self insertSections:sections ofKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSArray *sections, NSIndexSet *indexSet) {
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if (_delegateDidInsertSections)
|
||||
[_delegate dataController:self didInsertSections:sections atIndexSet:indexSet withAnimationOptions:animationOptions];
|
||||
}];
|
||||
@@ -401,7 +417,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
*/
|
||||
- (void)_deleteSectionsAtIndexSet:(NSIndexSet *)indexSet withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
{
|
||||
ASDisplayNodeAssert([NSOperationQueue currentQueue] == _editingTransactionQueue, @"%@ must be called on the editing transaction queue", NSStringFromSelector(_cmd));
|
||||
|
||||
[self deleteSectionsOfKind:ASDataControllerRowNodeKind atIndexSet:indexSet completion:^(NSIndexSet *indexSet) {
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if (_delegateDidDeleteSections)
|
||||
[_delegate dataController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
|
||||
}];
|
||||
@@ -426,49 +446,44 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
[self accessDataSourceSynchronously:synchronously withBlock:^{
|
||||
NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self];
|
||||
NSIndexSet *sectionIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)];
|
||||
NSArray<ASIndexedNodeContext *> *contexts = [self _populateFromDataSourceWithSectionIndexSet:sectionIndexSet];
|
||||
NSUInteger sectionCount = [_dataSource numberOfSectionsInDataController:self];
|
||||
NSIndexSet *sectionIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)];
|
||||
NSArray<ASIndexedNodeContext *> *contexts = [self _populateFromDataSourceWithSectionIndexSet:sectionIndexSet];
|
||||
|
||||
// Allow subclasses to perform setup before going into the edit transaction
|
||||
[self prepareForReloadData];
|
||||
// Allow subclasses to perform setup before going into the edit transaction
|
||||
[self prepareForReloadData];
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
LOG(@"Edit Transaction - reloadData");
|
||||
|
||||
void (^transactionBlock)() = ^{
|
||||
LOG(@"Edit Transaction - reloadData");
|
||||
|
||||
// Remove everything that existed before the reload, now that we're ready to insert replacements
|
||||
NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind];
|
||||
NSUInteger editingNodesSectionCount = editingNodes.count;
|
||||
|
||||
if (editingNodesSectionCount) {
|
||||
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodesSectionCount)];
|
||||
[self _deleteNodesAtIndexPaths:ASIndexPathsForTwoDimensionalArray(editingNodes) withAnimationOptions:animationOptions];
|
||||
[self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
|
||||
}
|
||||
|
||||
[self willReloadData];
|
||||
|
||||
// Insert empty sections
|
||||
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
[sections addObject:[[NSMutableArray alloc] init]];
|
||||
}
|
||||
[self _insertSections:sections atIndexSet:sectionIndexSet withAnimationOptions:animationOptions];
|
||||
|
||||
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
|
||||
|
||||
if (completion) {
|
||||
dispatch_async(dispatch_get_main_queue(), completion);
|
||||
}
|
||||
};
|
||||
// Remove everything that existed before the reload, now that we're ready to insert replacements
|
||||
NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind];
|
||||
NSUInteger editingNodesSectionCount = editingNodes.count;
|
||||
|
||||
if (synchronously) {
|
||||
transactionBlock();
|
||||
} else {
|
||||
[_editingTransactionQueue addOperationWithBlock:transactionBlock];
|
||||
if (editingNodesSectionCount) {
|
||||
NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodesSectionCount)];
|
||||
[self _deleteNodesAtIndexPaths:ASIndexPathsForTwoDimensionalArray(editingNodes) withAnimationOptions:animationOptions];
|
||||
[self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions];
|
||||
}
|
||||
|
||||
[self willReloadData];
|
||||
|
||||
// Insert empty sections
|
||||
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
[sections addObject:[[NSMutableArray alloc] init]];
|
||||
}
|
||||
[self _insertSections:sections atIndexSet:sectionIndexSet withAnimationOptions:animationOptions];
|
||||
|
||||
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
|
||||
|
||||
if (completion) {
|
||||
dispatch_async(dispatch_get_main_queue(), completion);
|
||||
}
|
||||
}];
|
||||
if (synchronously) {
|
||||
[self waitUntilAllUpdatesAreCommitted];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -491,45 +506,21 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
#pragma mark - Data Source Access (Calling _dataSource)
|
||||
|
||||
/**
|
||||
* Safely locks access to the data source and executes the given block, unlocking once complete.
|
||||
*
|
||||
* @discussion When `asyncDataFetching` is enabled, the block is executed on a background thread.
|
||||
*/
|
||||
- (void)accessDataSourceWithBlock:(dispatch_block_t)block
|
||||
{
|
||||
[self accessDataSourceSynchronously:NO withBlock:block];
|
||||
}
|
||||
|
||||
- (void)accessDataSourceSynchronously:(BOOL)synchronously withBlock:(dispatch_block_t)block
|
||||
{
|
||||
if (!synchronously && _asyncDataFetchingEnabled) {
|
||||
[_dataSource dataControllerLockDataSource];
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
|
||||
block();
|
||||
[_dataSource dataControllerUnlockDataSource];
|
||||
});
|
||||
} else {
|
||||
[_dataSource dataControllerLockDataSource];
|
||||
block();
|
||||
[_dataSource dataControllerUnlockDataSource];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches row contexts for the provided sections from the data source.
|
||||
*/
|
||||
- (NSArray<ASIndexedNodeContext *> *)_populateFromDataSourceWithSectionIndexSet:(NSIndexSet *)indexSet
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
|
||||
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
|
||||
|
||||
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
|
||||
[indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
|
||||
NSUInteger rowNum = [_dataSource dataController:self rowsInSection:idx];
|
||||
NSIndexPath *sectionIndex = [[NSIndexPath alloc] initWithIndex:idx];
|
||||
[indexSet enumerateIndexesUsingBlock:^(NSUInteger sectionIndex, BOOL *stop) {
|
||||
NSUInteger rowNum = [_dataSource dataController:self rowsInSection:sectionIndex];
|
||||
for (NSUInteger i = 0; i < rowNum; i++) {
|
||||
NSIndexPath *indexPath = [sectionIndex indexPathByAddingIndex:i];
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sectionIndex];
|
||||
ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath];
|
||||
|
||||
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath];
|
||||
@@ -628,24 +619,22 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
LOG(@"Edit Command - insertSections: %@", sections);
|
||||
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
[self accessDataSourceWithBlock:^{
|
||||
NSArray<ASIndexedNodeContext *> *contexts = [self _populateFromDataSourceWithSectionIndexSet:sections];
|
||||
NSArray<ASIndexedNodeContext *> *contexts = [self _populateFromDataSourceWithSectionIndexSet:sections];
|
||||
|
||||
[self prepareForInsertSections:sections];
|
||||
[self prepareForInsertSections:sections];
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
[self willInsertSections:sections];
|
||||
|
||||
LOG(@"Edit Transaction - insertSections: %@", sections);
|
||||
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count];
|
||||
for (NSUInteger i = 0; i < sections.count; i++) {
|
||||
[sectionArray addObject:[NSMutableArray array]];
|
||||
}
|
||||
|
||||
[self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions];
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
[self willInsertSections:sections];
|
||||
|
||||
LOG(@"Edit Transaction - insertSections: %@", sections);
|
||||
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count];
|
||||
for (NSUInteger i = 0; i < sections.count; i++) {
|
||||
[sectionArray addObject:[NSMutableArray array]];
|
||||
}
|
||||
|
||||
[self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions];
|
||||
|
||||
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
|
||||
}];
|
||||
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
@@ -678,23 +667,21 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
[self accessDataSourceWithBlock:^{
|
||||
NSArray<ASIndexedNodeContext *> *contexts= [self _populateFromDataSourceWithSectionIndexSet:sections];
|
||||
NSArray<ASIndexedNodeContext *> *contexts= [self _populateFromDataSourceWithSectionIndexSet:sections];
|
||||
|
||||
[self prepareForReloadSections:sections];
|
||||
[self prepareForReloadSections:sections];
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
[self willReloadSections:sections];
|
||||
|
||||
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections);
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
[self willReloadSections:sections];
|
||||
LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForTwoDimensionalArray(_editingNodes[ASDataControllerRowNodeKind]));
|
||||
|
||||
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
|
||||
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections);
|
||||
|
||||
LOG(@"Edit Transaction - reloadSections: updatedIndexPaths: %@, indexPaths: %@, _editingNodes: %@", updatedIndexPaths, indexPaths, ASIndexPathsForTwoDimensionalArray(_editingNodes[ASDataControllerRowNodeKind]));
|
||||
|
||||
[self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions];
|
||||
|
||||
// reinsert the elements
|
||||
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
|
||||
}];
|
||||
// reinsert the elements
|
||||
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
@@ -818,27 +805,25 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
|
||||
NSMutableArray<ASIndexedNodeContext *> *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
|
||||
|
||||
[self accessDataSourceWithBlock:^{
|
||||
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
|
||||
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
|
||||
|
||||
for (NSIndexPath *indexPath in sortedIndexPaths) {
|
||||
ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath];
|
||||
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath];
|
||||
[contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock
|
||||
indexPath:indexPath
|
||||
constrainedSize:constrainedSize
|
||||
environmentTraitCollection:environmentTraitCollection]];
|
||||
}
|
||||
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
|
||||
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
|
||||
|
||||
for (NSIndexPath *indexPath in sortedIndexPaths) {
|
||||
ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath];
|
||||
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath];
|
||||
[contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock
|
||||
indexPath:indexPath
|
||||
constrainedSize:constrainedSize
|
||||
environmentTraitCollection:environmentTraitCollection]];
|
||||
}
|
||||
|
||||
[self prepareForInsertRowsAtIndexPaths:indexPaths];
|
||||
[self prepareForInsertRowsAtIndexPaths:indexPaths];
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
[self willInsertRowsAtIndexPaths:indexPaths];
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
[self willInsertRowsAtIndexPaths:indexPaths];
|
||||
|
||||
LOG(@"Edit Transaction - insertRows: %@", indexPaths);
|
||||
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
|
||||
}];
|
||||
LOG(@"Edit Transaction - insertRows: %@", indexPaths);
|
||||
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
@@ -874,35 +859,32 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
|
||||
[_editingTransactionQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
// Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source.
|
||||
[self accessDataSourceWithBlock:^{
|
||||
NSMutableArray<ASIndexedNodeContext *> *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
|
||||
|
||||
// Sort indexPath to avoid messing up the index when deleting
|
||||
// FIXME: Shouldn't deletes be sorted in descending order?
|
||||
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
|
||||
|
||||
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
|
||||
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
|
||||
|
||||
for (NSIndexPath *indexPath in sortedIndexPaths) {
|
||||
ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath];
|
||||
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath];
|
||||
[contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock
|
||||
indexPath:indexPath
|
||||
constrainedSize:constrainedSize
|
||||
environmentTraitCollection:environmentTraitCollection]];
|
||||
}
|
||||
NSMutableArray<ASIndexedNodeContext *> *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
|
||||
|
||||
// Sort indexPath to avoid messing up the index when deleting
|
||||
// FIXME: Shouldn't deletes be sorted in descending order?
|
||||
NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)];
|
||||
|
||||
id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
|
||||
ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection;
|
||||
|
||||
for (NSIndexPath *indexPath in sortedIndexPaths) {
|
||||
ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath];
|
||||
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath];
|
||||
[contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock
|
||||
indexPath:indexPath
|
||||
constrainedSize:constrainedSize
|
||||
environmentTraitCollection:environmentTraitCollection]];
|
||||
}
|
||||
|
||||
[self prepareForReloadRowsAtIndexPaths:indexPaths];
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
[self willReloadRowsAtIndexPaths:indexPaths];
|
||||
[self prepareForReloadRowsAtIndexPaths:indexPaths];
|
||||
|
||||
[_editingTransactionQueue addOperationWithBlock:^{
|
||||
[self willReloadRowsAtIndexPaths:indexPaths];
|
||||
|
||||
LOG(@"Edit Transaction - reloadRows: %@", indexPaths);
|
||||
[self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions];
|
||||
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
|
||||
}];
|
||||
LOG(@"Edit Transaction - reloadRows: %@", indexPaths);
|
||||
[self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions];
|
||||
[self _batchLayoutNodesFromContexts:contexts withAnimationOptions:animationOptions];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
@@ -935,16 +917,18 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
return;
|
||||
}
|
||||
|
||||
[self accessDataSourceWithBlock:^{
|
||||
[nodes enumerateObjectsUsingBlock:^(NSMutableArray *section, NSUInteger sectionIndex, BOOL *stop) {
|
||||
[section enumerateObjectsUsingBlock:^(ASCellNode *node, NSUInteger rowIndex, BOOL *stop) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex];
|
||||
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
|
||||
ASLayout *layout = [node measureWithSizeRange:constrainedSize];
|
||||
node.frame = CGRectMake(0.0f, 0.0f, layout.size.width, layout.size.height);
|
||||
}];
|
||||
}];
|
||||
}];
|
||||
NSUInteger sectionIndex = 0;
|
||||
for (NSMutableArray *section in nodes) {
|
||||
NSUInteger rowIndex = 0;
|
||||
for (ASCellNode *node in section) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex];
|
||||
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
|
||||
CGSize size = [node measureWithSizeRange:constrainedSize].size;
|
||||
node.frame = { .size = size };
|
||||
rowIndex += 1;
|
||||
}
|
||||
sectionIndex += 1;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions
|
||||
@@ -1021,17 +1005,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext;
|
||||
- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode;
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
NSArray *nodes = [self completedNodes];
|
||||
NSUInteger numberOfNodes = nodes.count;
|
||||
|
||||
NSInteger section = 0;
|
||||
// Loop through each section to look for the cellNode
|
||||
for (NSUInteger i = 0; i < numberOfNodes; i++) {
|
||||
NSArray *sectionNodes = nodes[i];
|
||||
NSUInteger cellIndex = [sectionNodes indexOfObjectIdenticalTo:cellNode];
|
||||
if (cellIndex != NSNotFound) {
|
||||
return [NSIndexPath indexPathForRow:cellIndex inSection:i];
|
||||
for (NSArray *sectionNodes in [self completedNodes]) {
|
||||
NSUInteger item = [sectionNodes indexOfObjectIdenticalTo:cellNode];
|
||||
if (item != NSNotFound) {
|
||||
return [NSIndexPath indexPathForItem:item inSection:section];
|
||||
}
|
||||
section += 1;
|
||||
}
|
||||
|
||||
return nil;
|
||||
|
||||
@@ -147,6 +147,9 @@ ASDISPLAYNODE_EXTERN_C_END
|
||||
//
|
||||
// If there is any new downward propagating state, it should be added to this define.
|
||||
//
|
||||
// If the only change in a trait collection is that its dislplayContext has gone from non-nil to nil,
|
||||
// assume that we are clearing the context as part of a ASVC dealloc and do not trigger a layout.
|
||||
//
|
||||
// This logic is used in both ASCollectionNode and ASTableNode
|
||||
#define ASEnvironmentCollectionTableSetEnvironmentState(lock) \
|
||||
- (void)setEnvironmentState:(ASEnvironmentState)environmentState\
|
||||
@@ -156,12 +159,16 @@ ASDISPLAYNODE_EXTERN_C_END
|
||||
[super setEnvironmentState:environmentState];\
|
||||
ASEnvironmentTraitCollection currentTraits = environmentState.environmentTraitCollection;\
|
||||
if (ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(currentTraits, oldTraits) == NO) {\
|
||||
/* Must dispatch to main for self.view && [self.view.dataController completedNodes]*/ \
|
||||
ASPerformBlockOnMainThread(^{\
|
||||
BOOL needsLayout = (oldTraits.displayContext == currentTraits.displayContext) || currentTraits.displayContext != nil;\
|
||||
NSArray<NSArray <ASCellNode *> *> *completedNodes = [self.view.dataController completedNodes];\
|
||||
for (NSArray *sectionArray in completedNodes) {\
|
||||
for (ASCellNode *cellNode in sectionArray) {\
|
||||
ASEnvironmentStatePropagateDown(cellNode, currentTraits);\
|
||||
[cellNode setNeedsLayout];\
|
||||
if (needsLayout) {\
|
||||
[cellNode setNeedsLayout];\
|
||||
}\
|
||||
}\
|
||||
}\
|
||||
});\
|
||||
|
||||
@@ -85,7 +85,23 @@
|
||||
static PINRemoteImageManager *sharedPINRemoteImageManager = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
||||
#if PIN_ANIMATED_AVAILABLE
|
||||
// Check that Carthage users have linked both PINRemoteImage & PINCache by testing for one file each
|
||||
if (!(NSClassFromString(@"PINRemoteImageManager"))) {
|
||||
NSException *e = [NSException
|
||||
exceptionWithName:@"FrameworkSetupException"
|
||||
reason:@"Missing the path to the PINRemoteImage framework."
|
||||
userInfo:nil];
|
||||
@throw e;
|
||||
}
|
||||
if (!(NSClassFromString(@"PINCache"))) {
|
||||
NSException *e = [NSException
|
||||
exceptionWithName:@"FrameworkSetupException"
|
||||
reason:@"Missing the path to the PINCache framework."
|
||||
userInfo:nil];
|
||||
@throw e;
|
||||
}
|
||||
sharedPINRemoteImageManager = [[PINRemoteImageManager alloc] initWithSessionConfiguration:nil alternativeRepresentationProvider:self];
|
||||
#else
|
||||
sharedPINRemoteImageManager = [[PINRemoteImageManager alloc] initWithSessionConfiguration:nil];
|
||||
|
||||
@@ -146,6 +146,13 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (void)rangeController:(ASRangeController * )rangeController didEndUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion;
|
||||
|
||||
/**
|
||||
* Completed updates to cell node addition and removal.
|
||||
*
|
||||
* @param rangeController Sender.
|
||||
*/
|
||||
- (void)didCompleteUpdatesInRangeController:(ASRangeController *)rangeController;
|
||||
|
||||
/**
|
||||
* Called for nodes insertion.
|
||||
*
|
||||
|
||||
@@ -333,6 +333,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
|
||||
[modifiedIndexPaths sortUsingSelector:@selector(compare:)];
|
||||
NSLog(@"Range update complete; modifiedIndexPaths: %@", [self descriptionWithIndexPaths:modifiedIndexPaths]);
|
||||
#endif
|
||||
[_delegate didCompleteUpdatesInRangeController:self];
|
||||
}
|
||||
|
||||
#pragma mark - Notification observers
|
||||
|
||||
@@ -327,7 +327,7 @@ ASAsyncTransactionQueue & ASAsyncTransactionQueue::instance()
|
||||
_callbackQueue = callbackQueue;
|
||||
_completionBlock = [completionBlock copy];
|
||||
|
||||
_state = ASAsyncTransactionStateOpen;
|
||||
__atomic_store_n(&_state, ASAsyncTransactionStateOpen, __ATOMIC_SEQ_CST);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -335,7 +335,7 @@ ASAsyncTransactionQueue & ASAsyncTransactionQueue::instance()
|
||||
- (void)dealloc
|
||||
{
|
||||
// Uncommitted transactions break our guarantees about releasing completion blocks on callbackQueue.
|
||||
ASDisplayNodeAssert(_state != ASAsyncTransactionStateOpen, @"Uncommitted ASAsyncTransactions are not allowed");
|
||||
ASDisplayNodeAssert(__atomic_load_n(&_state, __ATOMIC_SEQ_CST) != ASAsyncTransactionStateOpen, @"Uncommitted ASAsyncTransactions are not allowed");
|
||||
if (_group) {
|
||||
_group->release();
|
||||
}
|
||||
@@ -360,7 +360,7 @@ ASAsyncTransactionQueue & ASAsyncTransactionQueue::instance()
|
||||
completion:(asyncdisplaykit_async_transaction_operation_completion_block_t)completion
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssert(_state == ASAsyncTransactionStateOpen, @"You can only add operations to open transactions");
|
||||
ASDisplayNodeAssert(__atomic_load_n(&_state, __ATOMIC_SEQ_CST) == ASAsyncTransactionStateOpen, @"You can only add operations to open transactions");
|
||||
|
||||
[self _ensureTransactionData];
|
||||
|
||||
@@ -368,7 +368,7 @@ ASAsyncTransactionQueue & ASAsyncTransactionQueue::instance()
|
||||
[_operations addObject:operation];
|
||||
_group->schedule(priority, queue, ^{
|
||||
@autoreleasepool {
|
||||
if (_state != ASAsyncTransactionStateCanceled) {
|
||||
if (__atomic_load_n(&_state, __ATOMIC_SEQ_CST) != ASAsyncTransactionStateCanceled) {
|
||||
_group->enter();
|
||||
block(^(id<NSObject> value){
|
||||
operation.value = value;
|
||||
@@ -395,7 +395,7 @@ ASAsyncTransactionQueue & ASAsyncTransactionQueue::instance()
|
||||
completion:(asyncdisplaykit_async_transaction_operation_completion_block_t)completion
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssert(_state == ASAsyncTransactionStateOpen, @"You can only add operations to open transactions");
|
||||
ASDisplayNodeAssert(__atomic_load_n(&_state, __ATOMIC_SEQ_CST) == ASAsyncTransactionStateOpen, @"You can only add operations to open transactions");
|
||||
|
||||
[self _ensureTransactionData];
|
||||
|
||||
@@ -403,7 +403,7 @@ ASAsyncTransactionQueue & ASAsyncTransactionQueue::instance()
|
||||
[_operations addObject:operation];
|
||||
_group->schedule(priority, queue, ^{
|
||||
@autoreleasepool {
|
||||
if (_state != ASAsyncTransactionStateCanceled) {
|
||||
if (__atomic_load_n(&_state, __ATOMIC_SEQ_CST) != ASAsyncTransactionStateCanceled) {
|
||||
operation.value = block();
|
||||
}
|
||||
}
|
||||
@@ -422,15 +422,15 @@ ASAsyncTransactionQueue & ASAsyncTransactionQueue::instance()
|
||||
- (void)cancel
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssert(_state != ASAsyncTransactionStateOpen, @"You can only cancel a committed or already-canceled transaction");
|
||||
_state = ASAsyncTransactionStateCanceled;
|
||||
ASDisplayNodeAssert(__atomic_load_n(&_state, __ATOMIC_SEQ_CST) != ASAsyncTransactionStateOpen, @"You can only cancel a committed or already-canceled transaction");
|
||||
__atomic_store_n(&_state, ASAsyncTransactionStateCanceled, __ATOMIC_SEQ_CST);
|
||||
}
|
||||
|
||||
- (void)commit
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssert(_state == ASAsyncTransactionStateOpen, @"You cannot double-commit a transaction");
|
||||
_state = ASAsyncTransactionStateCommitted;
|
||||
ASDisplayNodeAssert(__atomic_load_n(&_state, __ATOMIC_SEQ_CST) == ASAsyncTransactionStateOpen, @"You cannot double-commit a transaction");
|
||||
__atomic_store_n(&_state, ASAsyncTransactionStateCommitted, __ATOMIC_SEQ_CST);
|
||||
|
||||
if ([_operations count] == 0) {
|
||||
// Fast path: if a transaction was opened, but no operations were added, execute completion block synchronously.
|
||||
@@ -451,8 +451,8 @@ ASAsyncTransactionQueue & ASAsyncTransactionQueue::instance()
|
||||
|
||||
- (void)completeTransaction
|
||||
{
|
||||
if (_state != ASAsyncTransactionStateComplete) {
|
||||
BOOL isCanceled = (_state == ASAsyncTransactionStateCanceled);
|
||||
if (__atomic_load_n(&_state, __ATOMIC_SEQ_CST) != ASAsyncTransactionStateComplete) {
|
||||
BOOL isCanceled = (__atomic_load_n(&_state, __ATOMIC_SEQ_CST) == ASAsyncTransactionStateCanceled);
|
||||
for (ASDisplayNodeAsyncTransactionOperation *operation in _operations) {
|
||||
[operation callAndReleaseCompletionBlock:isCanceled];
|
||||
}
|
||||
@@ -460,7 +460,7 @@ ASAsyncTransactionQueue & ASAsyncTransactionQueue::instance()
|
||||
// Always set _state to Complete, even if we were cancelled, to block any extraneous
|
||||
// calls to this method that may have been scheduled for the next runloop
|
||||
// (e.g. if we needed to force one in this runloop with -waitUntilComplete, but another was already scheduled)
|
||||
_state = ASAsyncTransactionStateComplete;
|
||||
__atomic_store_n(&_state, ASAsyncTransactionStateComplete, __ATOMIC_SEQ_CST);
|
||||
|
||||
if (_completionBlock) {
|
||||
_completionBlock(self, isCanceled);
|
||||
@@ -471,7 +471,7 @@ ASAsyncTransactionQueue & ASAsyncTransactionQueue::instance()
|
||||
- (void)waitUntilComplete
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
if (_state != ASAsyncTransactionStateComplete) {
|
||||
if (__atomic_load_n(&_state, __ATOMIC_SEQ_CST) != ASAsyncTransactionStateComplete) {
|
||||
if (_group) {
|
||||
ASDisplayNodeAssertTrue(_callbackQueue == dispatch_get_main_queue());
|
||||
_group->wait();
|
||||
@@ -481,9 +481,9 @@ ASAsyncTransactionQueue & ASAsyncTransactionQueue::instance()
|
||||
// commit ourselves via the group to avoid double-committing the transaction.
|
||||
// This is only necessary when forcing display work to complete before allowing the runloop
|
||||
// to continue, e.g. in the implementation of -[ASDisplayNode recursivelyEnsureDisplay].
|
||||
if (_state == ASAsyncTransactionStateOpen) {
|
||||
if (__atomic_load_n(&_state, __ATOMIC_SEQ_CST) == ASAsyncTransactionStateOpen) {
|
||||
[_ASAsyncTransactionGroup commit];
|
||||
ASDisplayNodeAssert(_state != ASAsyncTransactionStateOpen, @"Transaction should not be open after committing group");
|
||||
ASDisplayNodeAssert(__atomic_load_n(&_state, __ATOMIC_SEQ_CST) != ASAsyncTransactionStateOpen, @"Transaction should not be open after committing group");
|
||||
}
|
||||
// If we needed to commit the group above, -completeTransaction may have already been run.
|
||||
// It is designed to accommodate this by checking _state to ensure it is not complete.
|
||||
@@ -508,7 +508,7 @@ ASAsyncTransactionQueue & ASAsyncTransactionQueue::instance()
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<_ASAsyncTransaction: %p - _state = %lu, _group = %p, _operations = %@>", self, (unsigned long)_state, _group, _operations];
|
||||
return [NSString stringWithFormat:@"<_ASAsyncTransaction: %p - _state = %lu, _group = %p, _operations = %@>", self, (unsigned long)__atomic_load_n(&_state, __ATOMIC_SEQ_CST), _group, _operations];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
#import "ASBaseDefines.h"
|
||||
#import "ASLayout.h"
|
||||
|
||||
static NSString * const kBackgroundChildKey = @"kBackgroundChildKey";
|
||||
static NSUInteger const kForegroundChildIndex = 0;
|
||||
static NSUInteger const kBackgroundChildIndex = 1;
|
||||
|
||||
@interface ASBackgroundLayoutSpec ()
|
||||
@end
|
||||
@@ -28,7 +29,7 @@ static NSString * const kBackgroundChildKey = @"kBackgroundChildKey";
|
||||
}
|
||||
|
||||
ASDisplayNodeAssertNotNil(child, @"Child cannot be nil");
|
||||
[self setChild:child];
|
||||
[self setChild:child forIndex:kForegroundChildIndex];
|
||||
self.background = background;
|
||||
return self;
|
||||
}
|
||||
@@ -63,12 +64,12 @@ static NSString * const kBackgroundChildKey = @"kBackgroundChildKey";
|
||||
|
||||
- (void)setBackground:(id<ASLayoutable>)background
|
||||
{
|
||||
[super setChild:background forIdentifier:kBackgroundChildKey];
|
||||
[super setChild:background forIndex:kBackgroundChildIndex];
|
||||
}
|
||||
|
||||
- (id<ASLayoutable>)background
|
||||
{
|
||||
return [super childForIdentifier:kBackgroundChildKey];
|
||||
return [super childForIndex:kBackgroundChildIndex];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -40,10 +40,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
* only require a single child.
|
||||
*
|
||||
* For layout specs that require a known number of children (ASBackgroundLayoutSpec, for example)
|
||||
* a subclass should use this method to set the "primary" child. It can then use setChild:forIdentifier:
|
||||
* to set any other required children. Ideally a subclass would hide this from the user, and use the
|
||||
* setChild:forIdentifier: internally. For example, ASBackgroundLayoutSpec exposes a backgroundChild
|
||||
* property that behind the scenes is calling setChild:forIdentifier:.
|
||||
* a subclass should use this method to set the "primary" child. This is actually the same as calling
|
||||
* setChild:forIdentifier:0. All other children should be set by defining convenience methods
|
||||
* that call setChild:forIdentifier behind the scenes.
|
||||
*/
|
||||
- (void)setChild:(id<ASLayoutable>)child;
|
||||
|
||||
@@ -52,19 +51,19 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*
|
||||
* @param child A child to be added.
|
||||
*
|
||||
* @param identifier An identifier associated with the child.
|
||||
* @param index An index associated with the child.
|
||||
*
|
||||
* @discussion Every ASLayoutSpec must act on at least one child. The ASLayoutSpec base class takes the
|
||||
* responsibility of holding on to the spec children. Some layout specs, like ASInsetLayoutSpec,
|
||||
* only require a single child.
|
||||
*
|
||||
* For layout specs that require a known number of children (ASBackgroundLayoutSpec, for example)
|
||||
* a subclass should use the setChild method to set the "primary" child. It can then use this method
|
||||
* a subclass can use the setChild method to set the "primary" child. It should then use this method
|
||||
* to set any other required children. Ideally a subclass would hide this from the user, and use the
|
||||
* setChild:forIdentifier: internally. For example, ASBackgroundLayoutSpec exposes a backgroundChild
|
||||
* property that behind the scenes is calling setChild:forIdentifier:.
|
||||
* setChild:forIndex: internally. For example, ASBackgroundLayoutSpec exposes a backgroundChild
|
||||
* property that behind the scenes is calling setChild:forIndex:.
|
||||
*/
|
||||
- (void)setChild:(id<ASLayoutable>)child forIdentifier:(NSString *)identifier;
|
||||
- (void)setChild:(id<ASLayoutable>)child forIndex:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
* Adds childen to this layout spec.
|
||||
@@ -94,11 +93,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (nullable id<ASLayoutable>)child;
|
||||
|
||||
/**
|
||||
* Returns the child added to this layout spec using the given identifier.
|
||||
* Returns the child added to this layout spec using the given index.
|
||||
*
|
||||
* @param identifier An identifier associated withe the child.
|
||||
* @param index An identifier associated withe the child.
|
||||
*/
|
||||
- (nullable id<ASLayoutable>)childForIdentifier:(NSString *)identifier;
|
||||
- (nullable id<ASLayoutable>)childForIndex:(NSUInteger)index;
|
||||
|
||||
/**
|
||||
* Returns all children added to this layout spec.
|
||||
|
||||
@@ -20,14 +20,15 @@
|
||||
#import "ASTraitCollection.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
#import <map>
|
||||
#import <vector>
|
||||
|
||||
typedef std::map<unsigned long, id<ASLayoutable>, std::less<unsigned long>> ASChildMap;
|
||||
|
||||
@interface ASLayoutSpec() {
|
||||
ASEnvironmentState _environmentState;
|
||||
ASDN::RecursiveMutex _propertyLock;
|
||||
|
||||
NSArray *_children;
|
||||
NSMutableDictionary *_childrenWithIdentifier;
|
||||
ASChildMap _children;
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -45,7 +46,6 @@
|
||||
}
|
||||
_isMutable = YES;
|
||||
_environmentState = ASEnvironmentStateMakeDefault();
|
||||
_children = [NSArray array];
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -102,14 +102,6 @@
|
||||
return child;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)childrenWithIdentifier
|
||||
{
|
||||
if (!_childrenWithIdentifier) {
|
||||
_childrenWithIdentifier = [NSMutableDictionary dictionary];
|
||||
}
|
||||
return _childrenWithIdentifier;
|
||||
}
|
||||
|
||||
- (void)setParent:(id<ASLayoutable>)parent
|
||||
{
|
||||
// FIXME: Locking should be evaluated here. _parent is not widely used yet, though.
|
||||
@@ -126,34 +118,23 @@
|
||||
if (child) {
|
||||
id<ASLayoutable> finalLayoutable = [self layoutableToAddFromLayoutable:child];
|
||||
if (finalLayoutable) {
|
||||
_children = @[finalLayoutable];
|
||||
_children[0] = finalLayoutable;
|
||||
[self propagateUpLayoutable:finalLayoutable];
|
||||
}
|
||||
} else {
|
||||
// remove the only child
|
||||
_children = [NSArray array];
|
||||
_children.erase(0);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setChild:(id<ASLayoutable>)child forIdentifier:(NSString *)identifier
|
||||
- (void)setChild:(id<ASLayoutable>)child forIndex:(NSUInteger)index
|
||||
{
|
||||
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
|
||||
if (child) {
|
||||
id<ASLayoutable> finalLayoutable = [self layoutableToAddFromLayoutable:child];
|
||||
self.childrenWithIdentifier[identifier] = finalLayoutable;
|
||||
if (finalLayoutable) {
|
||||
_children = [_children arrayByAddingObject:finalLayoutable];
|
||||
}
|
||||
_children[index] = finalLayoutable;
|
||||
} else {
|
||||
id<ASLayoutable> oldChild = self.childrenWithIdentifier[identifier];
|
||||
if (oldChild) {
|
||||
self.childrenWithIdentifier[identifier] = nil;
|
||||
NSMutableArray *mutableChildren = [_children mutableCopy];
|
||||
[mutableChildren removeObject:oldChild];
|
||||
_children = [mutableChildren copy];
|
||||
}
|
||||
_children.erase(index);
|
||||
}
|
||||
|
||||
// TODO: Should we propagate up the layoutable at it could happen that multiple children will propagated up their
|
||||
// layout options and one child will overwrite values from another child
|
||||
// [self propagateUpLayoutable:finalLayoutable];
|
||||
@@ -163,32 +144,33 @@
|
||||
{
|
||||
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
|
||||
|
||||
std::vector<id<ASLayoutable>> finalChildren;
|
||||
for (id<ASLayoutable> child in children) {
|
||||
finalChildren.push_back([self layoutableToAddFromLayoutable:child]);
|
||||
}
|
||||
|
||||
_children = nil;
|
||||
if (finalChildren.size() > 0) {
|
||||
_children = [NSArray arrayWithObjects:&finalChildren[0] count:finalChildren.size()];
|
||||
} else {
|
||||
_children = [NSArray array];
|
||||
}
|
||||
_children.clear();
|
||||
[children enumerateObjectsUsingBlock:^(id<ASLayoutable> _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
_children[idx] = obj;
|
||||
}];
|
||||
}
|
||||
|
||||
- (id<ASLayoutable>)childForIdentifier:(NSString *)identifier
|
||||
- (id<ASLayoutable>)childForIndex:(NSUInteger)index
|
||||
{
|
||||
return self.childrenWithIdentifier[identifier];
|
||||
if (index < _children.size()) {
|
||||
return _children[index];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id<ASLayoutable>)child
|
||||
{
|
||||
return [_children firstObject];
|
||||
return _children[0];
|
||||
}
|
||||
|
||||
- (NSArray *)children
|
||||
{
|
||||
return _children;
|
||||
std::vector<ASLayout *> children;
|
||||
for (ASChildMap::iterator it = _children.begin(); it != _children.end(); ++it ) {
|
||||
children.push_back(it->second);
|
||||
}
|
||||
|
||||
return [NSArray arrayWithObjects:&children[0] count:children.size()];
|
||||
}
|
||||
|
||||
#pragma mark - ASEnvironment
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
#import "ASBaseDefines.h"
|
||||
#import "ASLayout.h"
|
||||
|
||||
static NSString * const kOverlayChildKey = @"kOverlayChildKey";
|
||||
static NSUInteger const kUnderlayChildIndex = 0;
|
||||
static NSUInteger const kOverlayChildIndex = 1;
|
||||
|
||||
@implementation ASOverlayLayoutSpec
|
||||
|
||||
@@ -25,7 +26,7 @@ static NSString * const kOverlayChildKey = @"kOverlayChildKey";
|
||||
}
|
||||
ASDisplayNodeAssertNotNil(child, @"Child that will be overlayed on shouldn't be nil");
|
||||
self.overlay = overlay;
|
||||
[self setChild:child];
|
||||
[self setChild:child forIndex:kUnderlayChildIndex];
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -36,12 +37,12 @@ static NSString * const kOverlayChildKey = @"kOverlayChildKey";
|
||||
|
||||
- (void)setOverlay:(id<ASLayoutable>)overlay
|
||||
{
|
||||
[super setChild:overlay forIdentifier:kOverlayChildKey];
|
||||
[super setChild:overlay forIndex:kOverlayChildIndex];
|
||||
}
|
||||
|
||||
- (id<ASLayoutable>)overlay
|
||||
{
|
||||
return [super childForIdentifier:kOverlayChildKey];
|
||||
return [super childForIndex:kOverlayChildIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -75,6 +75,20 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
|
||||
unsigned displaySuspended:1;
|
||||
unsigned shouldAnimateSizeChanges:1;
|
||||
unsigned hasCustomDrawingPriority:1;
|
||||
|
||||
// Wrapped view handling
|
||||
|
||||
// The layer contents should not be cleared in case the node is wrapping a UIImageView.UIImageView is specifically
|
||||
// optimized for performance and does not use the usual way to provide the contents of the CALayer via the
|
||||
// CALayerDelegate method that backs the UIImageView.
|
||||
unsigned canClearContentsOfLayer:1;
|
||||
|
||||
// Prevent calling setNeedsDisplay on a layer that backs a UIImageView. Usually calling setNeedsDisplay on a CALayer
|
||||
// triggers a recreation of the contents of layer unfortunately calling it on a CALayer that backs a UIImageView
|
||||
// it goes trough the normal flow to assign the contents to a layer via the CALayerDelegate methods. Unfortunately
|
||||
// UIImageView does not do recreate the layer contents the usual way, it actually does not implement some of the
|
||||
// methods at all instead it throws away the contents of the layer and nothing will show up.
|
||||
unsigned canCallNeedsDisplayOfLayer:1;
|
||||
|
||||
// whether custom drawing is enabled
|
||||
unsigned implementsInstanceDrawRect:1;
|
||||
|
||||
@@ -48,9 +48,12 @@
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
[self calculateSubnodeOperationsIfNeeded];
|
||||
for (NSUInteger i = 0; i < [_insertedSubnodes count]; i++) {
|
||||
|
||||
NSUInteger i = 0;
|
||||
for (ASDisplayNode *node in _insertedSubnodes) {
|
||||
NSUInteger p = _insertedSubnodePositions[i];
|
||||
[_node insertSubnode:_insertedSubnodes[i] atIndex:p];
|
||||
[_node insertSubnode:node atIndex:p];
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +61,8 @@
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
[self calculateSubnodeOperationsIfNeeded];
|
||||
for (NSUInteger i = 0; i < [_removedSubnodes count]; i++) {
|
||||
[_removedSubnodes[i] removeFromSupernode];
|
||||
for (ASDisplayNode *subnode in _removedSubnodes) {
|
||||
[subnode removeFromSupernode];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
#import "ASAssert.h"
|
||||
#import "ASMultidimensionalArrayUtils.h"
|
||||
|
||||
// Import UIKit to get [NSIndexPath indexPathForItem:inSection:] which uses
|
||||
// static memory addresses rather than allocating new index path objects.
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#pragma mark - Internal Methods
|
||||
|
||||
static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray *mutableArray,
|
||||
@@ -25,8 +29,10 @@ static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray
|
||||
}
|
||||
|
||||
if (curIndexPath.length < dimension - 1) {
|
||||
for (int i = 0; i < mutableArray.count; i++) {
|
||||
ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(mutableArray[i], indexPaths, curIdx, [curIndexPath indexPathByAddingIndex:i], dimension, updateBlock);
|
||||
NSInteger i = 0;
|
||||
for (NSMutableArray *subarray in mutableArray) {
|
||||
ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(subarray, indexPaths, curIdx, [curIndexPath indexPathByAddingIndex:i], dimension, updateBlock);
|
||||
i += 1;
|
||||
}
|
||||
} else {
|
||||
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
|
||||
@@ -41,7 +47,7 @@ static void ASRecursivelyUpdateMultidimensionalArrayAtIndexPaths(NSMutableArray
|
||||
}
|
||||
}
|
||||
|
||||
static void ASRecursivelyFindIndexPathsForMultidimensionalArray(NSObject *obj, NSIndexPath *curIndexPath, NSMutableArray *res)
|
||||
static void ASRecursivelyFindIndexPathsForMultidimensionalArray(NSObject *obj, NSIndexPath *curIndexPath, NSMutableArray <NSIndexPath *>*res)
|
||||
{
|
||||
if (![obj isKindOfClass:[NSArray class]]) {
|
||||
[res addObject:curIndexPath];
|
||||
@@ -72,7 +78,12 @@ static BOOL ASElementExistsAtIndexPathForMultidimensionalArray(NSArray *array, N
|
||||
NSUInteger indexesLength = indexLength - 1;
|
||||
NSUInteger indexes[indexesLength];
|
||||
[indexPath getIndexes:indexes range:NSMakeRange(1, indexesLength)];
|
||||
NSIndexPath *indexPathByRemovingFirstIndex = [NSIndexPath indexPathWithIndexes:indexes length:indexesLength];
|
||||
NSIndexPath *indexPathByRemovingFirstIndex;
|
||||
if (indexesLength == 2) {
|
||||
indexPathByRemovingFirstIndex = [NSIndexPath indexPathForItem:indexes[1] inSection:indexes[0]];
|
||||
} else {
|
||||
indexPathByRemovingFirstIndex = [NSIndexPath indexPathWithIndexes:indexes length:indexesLength];
|
||||
}
|
||||
|
||||
return ASElementExistsAtIndexPathForMultidimensionalArray(array[firstIndex], indexPathByRemovingFirstIndex);
|
||||
}
|
||||
@@ -184,9 +195,8 @@ NSArray *ASIndexPathsForTwoDimensionalArray(NSArray <NSArray *>* twoDimensionalA
|
||||
NSUInteger section = 0;
|
||||
for (NSArray *subarray in twoDimensionalArray) {
|
||||
ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray<NSArray *> *");
|
||||
NSUInteger itemCount = subarray.count;
|
||||
for (NSUInteger item = 0; item < itemCount; item++) {
|
||||
[result addObject:[NSIndexPath indexPathWithIndexes:(const NSUInteger []){ section, item } length:2]];
|
||||
for (NSUInteger item = 0; item < subarray.count; item++) {
|
||||
[result addObject:[NSIndexPath indexPathForItem:item inSection:section]];
|
||||
}
|
||||
section++;
|
||||
}
|
||||
|
||||
@@ -442,7 +442,7 @@ typedef enum {
|
||||
-(BOOL)_tryRetain { \
|
||||
__typeof__(_rc_ivar) _prev; \
|
||||
do { \
|
||||
_prev = _rc_ivar; \
|
||||
_prev = __atomic_load_n(&_rc_ivar, __ATOMIC_SEQ_CST);; \
|
||||
if (_prev & 1) { \
|
||||
return 0; \
|
||||
} else if (_prev == -2) { \
|
||||
@@ -454,12 +454,13 @@ typedef enum {
|
||||
return 1; \
|
||||
} \
|
||||
-(BOOL)_isDeallocating { \
|
||||
if (_rc_ivar == -2) { \
|
||||
__typeof__(_rc_ivar) _prev = __atomic_load_n(&_rc_ivar, __ATOMIC_SEQ_CST); \
|
||||
if (_prev == -2) { \
|
||||
return 1; \
|
||||
} else if (_rc_ivar < -2) { \
|
||||
} else if (_prev < -2) { \
|
||||
__builtin_trap(); /* BUG: over-release elsewhere */ \
|
||||
} \
|
||||
return _rc_ivar & 1; \
|
||||
return _prev & 1; \
|
||||
}
|
||||
|
||||
#define _OBJC_SUPPORTED_INLINE_REFCNT_LOGIC(_rc_ivar, _dealloc2main) \
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
//
|
||||
|
||||
#import "ASTextKitContext.h"
|
||||
#import "ASThread.h"
|
||||
|
||||
#import "ASLayoutManager.h"
|
||||
|
||||
#import <mutex>
|
||||
|
||||
@implementation ASTextKitContext
|
||||
{
|
||||
// All TextKit operations (even non-mutative ones) must be executed serially.
|
||||
ASDN::Mutex _textKitMutex;
|
||||
std::mutex _textKitMutex;
|
||||
|
||||
NSLayoutManager *_layoutManager;
|
||||
NSTextStorage *_textStorage;
|
||||
@@ -35,8 +35,8 @@
|
||||
{
|
||||
if (self = [super init]) {
|
||||
// Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock.
|
||||
static ASDN::Mutex __staticMutex;
|
||||
ASDN::MutexLocker l(__staticMutex);
|
||||
static std::mutex __static_mutex;
|
||||
std::lock_guard<std::mutex> l(__static_mutex);
|
||||
// Create the TextKit component stack with our default configuration.
|
||||
if (textStorageCreationBlock) {
|
||||
_textStorage = textStorageCreationBlock(attributedString);
|
||||
@@ -60,13 +60,13 @@
|
||||
|
||||
- (CGSize)constrainedSize
|
||||
{
|
||||
ASDN::MutexLocker l(_textKitMutex);
|
||||
std::lock_guard<std::mutex> l(_textKitMutex);
|
||||
return _textContainer.size;
|
||||
}
|
||||
|
||||
- (void)setConstrainedSize:(CGSize)constrainedSize
|
||||
{
|
||||
ASDN::MutexLocker l(_textKitMutex);
|
||||
std::lock_guard<std::mutex> l(_textKitMutex);
|
||||
_textContainer.size = constrainedSize;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
NSTextStorage *,
|
||||
NSTextContainer *))block
|
||||
{
|
||||
ASDN::MutexLocker l(_textKitMutex);
|
||||
std::lock_guard<std::mutex> l(_textKitMutex);
|
||||
block(_layoutManager, _textStorage, _textContainer);
|
||||
}
|
||||
|
||||
|
||||
@@ -227,7 +227,12 @@ static _ASDisplayLayerTestDelegateClassModes _class_modes;
|
||||
// DANGER: Don't use the delegate as the parameters in real code; this is not thread-safe and just for accounting in unit tests!
|
||||
+ (void)drawRect:(CGRect)bounds withParameters:(_ASDisplayLayerTestDelegate *)delegate isCancelled:(asdisplaynode_iscancelled_block_t)sentinelBlock isRasterizing:(BOOL)isRasterizing
|
||||
{
|
||||
delegate->_drawRectCount++;
|
||||
__atomic_add_fetch(&delegate->_drawRectCount, 1, __ATOMIC_SEQ_CST);
|
||||
}
|
||||
|
||||
- (NSUInteger)drawRectCount
|
||||
{
|
||||
return(__atomic_load_n(&_drawRectCount, __ATOMIC_SEQ_CST));
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
@@ -267,9 +272,9 @@ static _ASDisplayLayerTestDelegateClassModes _class_modes;
|
||||
// make sure we don't lock up the tests indefinitely; fail after 1 sec by using an async barrier
|
||||
__block BOOL didHitBarrier = NO;
|
||||
dispatch_barrier_async([_ASDisplayLayer displayQueue], ^{
|
||||
didHitBarrier = YES;
|
||||
__atomic_store_n(&didHitBarrier, YES, __ATOMIC_SEQ_CST);
|
||||
});
|
||||
XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{ return didHitBarrier; }));
|
||||
XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{ return __atomic_load_n(&didHitBarrier, __ATOMIC_SEQ_CST); }));
|
||||
}
|
||||
|
||||
- (void)waitForLayer:(_ASDisplayLayerTestLayer *)layer asyncDisplayCount:(NSUInteger)count
|
||||
|
||||
@@ -60,11 +60,11 @@ static dispatch_block_t modifyMethodByAddingPrologueBlockAndReturnCleanupBlock(C
|
||||
@end
|
||||
|
||||
// Conveniences for making nodes named a certain way
|
||||
#define DeclareNodeNamed(n) ASDisplayNode *n = [[ASDisplayNode alloc] init]; n.name = @#n
|
||||
#define DeclareNodeNamed(n) ASDisplayNode *n = [[[ASDisplayNode alloc] init] autorelease]; n.name = @#n
|
||||
#define DeclareViewNamed(v) UIView *v = viewWithName(@#v)
|
||||
|
||||
static UIView *viewWithName(NSString *name) {
|
||||
ASDisplayNode *n = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *n = [[[ASDisplayNode alloc] init] autorelease];
|
||||
n.name = name;
|
||||
return n.view;
|
||||
}
|
||||
@@ -130,7 +130,7 @@ static UIView *viewWithName(NSString *name) {
|
||||
- (void)checkAppearanceMethodsCalledWithRootNodeInWindowLayerBacked:(BOOL)isLayerBacked
|
||||
{
|
||||
// ASDisplayNode visibility does not change if modifying a hierarchy that is not in a window. So create one and add the superview to it.
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectZero];
|
||||
UIWindow *window = [[[UIWindow alloc] initWithFrame:CGRectZero] autorelease];
|
||||
|
||||
DeclareNodeNamed(n);
|
||||
DeclareViewNamed(superview);
|
||||
@@ -162,15 +162,12 @@ static UIView *viewWithName(NSString *name) {
|
||||
|
||||
XCTAssertEqual([_willEnterHierarchyCounts countForObject:n], 1u, @"willEnterHierarchy not called when node's view added to hierarchy");
|
||||
XCTAssertEqual([_didExitHierarchyCounts countForObject:n], 1u, @"didExitHierarchy erroneously called");
|
||||
|
||||
[superview release];
|
||||
[window release];
|
||||
}
|
||||
|
||||
- (void)checkManualAppearanceViewLoaded:(BOOL)isViewLoaded layerBacked:(BOOL)isLayerBacked
|
||||
{
|
||||
// ASDisplayNode visibility does not change if modifying a hierarchy that is not in a window. So create one and add the superview to it.
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectZero];
|
||||
UIWindow *window = [[[UIWindow alloc] initWithFrame:CGRectZero] autorelease];
|
||||
|
||||
DeclareNodeNamed(parent);
|
||||
DeclareNodeNamed(a);
|
||||
@@ -263,13 +260,13 @@ static UIView *viewWithName(NSString *name) {
|
||||
- (void)testSynchronousIntermediaryView
|
||||
{
|
||||
// Parent is a wrapper node for a scrollview
|
||||
ASDisplayNode *parentSynchronousNode = [[ASDisplayNode alloc] initWithViewClass:[UIScrollView class]];
|
||||
ASDisplayNode *parentSynchronousNode = [[[ASDisplayNode alloc] initWithViewClass:[UIScrollView class]] autorelease];
|
||||
DeclareNodeNamed(layerBackedNode);
|
||||
DeclareNodeNamed(viewBackedNode);
|
||||
|
||||
layerBackedNode.layerBacked = YES;
|
||||
|
||||
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectZero];
|
||||
UIWindow *window = [[[UIWindow alloc] initWithFrame:CGRectZero] autorelease];
|
||||
[parentSynchronousNode addSubnode:layerBackedNode];
|
||||
[parentSynchronousNode addSubnode:viewBackedNode];
|
||||
|
||||
@@ -303,11 +300,6 @@ static UIView *viewWithName(NSString *name) {
|
||||
XCTAssertFalse(parentSynchronousNode.inHierarchy, @"Should not have changed");
|
||||
XCTAssertFalse(layerBackedNode.inHierarchy, @"Should have been marked invisible when synchronous superview was removed from the window");
|
||||
XCTAssertFalse(viewBackedNode.inHierarchy, @"Should have been marked invisible when synchronous superview was removed from the window");
|
||||
|
||||
[window release];
|
||||
[parentSynchronousNode release];
|
||||
[layerBackedNode release];
|
||||
[viewBackedNode release];
|
||||
}
|
||||
|
||||
- (void)checkMoveAcrossHierarchyLayerBacked:(BOOL)isLayerBacked useManualCalls:(BOOL)useManualDisable useNodeAPI:(BOOL)useNodeAPI
|
||||
|
||||
@@ -21,18 +21,18 @@
|
||||
#import "ASCellNode.h"
|
||||
|
||||
// Conveniences for making nodes named a certain way
|
||||
#define DeclareNodeNamed(n) ASDisplayNode *n = [[ASDisplayNode alloc] init]; n.name = @#n
|
||||
#define DeclareNodeNamed(n) ASDisplayNode *n = [[[ASDisplayNode alloc] init] autorelease]; n.name = @#n
|
||||
#define DeclareViewNamed(v) UIView *v = viewWithName(@#v)
|
||||
#define DeclareLayerNamed(l) CALayer *l = layerWithName(@#l)
|
||||
|
||||
static UIView *viewWithName(NSString *name) {
|
||||
ASDisplayNode *n = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *n = [[[ASDisplayNode alloc] init] autorelease];
|
||||
n.name = name;
|
||||
return n.view;
|
||||
}
|
||||
|
||||
static CALayer *layerWithName(NSString *name) {
|
||||
ASDisplayNode *n = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *n = [[[ASDisplayNode alloc] init] autorelease];
|
||||
n.layerBacked = YES;
|
||||
n.name = name;
|
||||
return n.layer;
|
||||
@@ -144,6 +144,12 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
{
|
||||
if (_willDeallocBlock) {
|
||||
_willDeallocBlock(self);
|
||||
[_willDeallocBlock release];
|
||||
_willDeallocBlock = nil;
|
||||
}
|
||||
if (_calculateSizeBlock) {
|
||||
[_calculateSizeBlock release];
|
||||
_calculateSizeBlock = nil;
|
||||
}
|
||||
[super dealloc];
|
||||
}
|
||||
@@ -214,19 +220,19 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
}
|
||||
|
||||
- (void)testOverriddenFirstResponderBehavior {
|
||||
ASTestDisplayNode *node = [[ASTestResponderNode alloc] init];
|
||||
ASTestDisplayNode *node = [[[ASTestResponderNode alloc] init] autorelease];
|
||||
XCTAssertTrue([node canBecomeFirstResponder]);
|
||||
XCTAssertTrue([node becomeFirstResponder]);
|
||||
}
|
||||
|
||||
- (void)testDefaultFirstResponderBehavior {
|
||||
ASTestDisplayNode *node = [[ASTestDisplayNode alloc] init];
|
||||
ASTestDisplayNode *node = [[[ASTestDisplayNode alloc] init] autorelease];
|
||||
XCTAssertFalse([node canBecomeFirstResponder]);
|
||||
XCTAssertFalse([node becomeFirstResponder]);
|
||||
}
|
||||
|
||||
- (void)testLayerBackedFirstResponderBehavior {
|
||||
ASTestDisplayNode *node = [[ASTestResponderNode alloc] init];
|
||||
ASTestDisplayNode *node = [[[ASTestResponderNode alloc] init] autorelease];
|
||||
node.layerBacked = YES;
|
||||
XCTAssertTrue([node canBecomeFirstResponder]);
|
||||
XCTAssertFalse([node becomeFirstResponder]);
|
||||
@@ -250,6 +256,8 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
[self executeOffThread:^{
|
||||
node = [[ASDisplayNode alloc] init];
|
||||
}];
|
||||
// executeOffThread: blocks until the background thread finishes executing.
|
||||
node = [node autorelease]; // XXX This is very bad style.
|
||||
|
||||
UIView *view = node.view;
|
||||
XCTAssertNotNil(view, @"Getting node's view on-thread should succeed.");
|
||||
@@ -257,7 +265,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
|
||||
- (void)testNodeCreatedOffThreadWithExistingView
|
||||
{
|
||||
UIView *view = [[UIDisplayNodeTestView alloc] init];
|
||||
UIView *view = [[[UIDisplayNodeTestView alloc] init] autorelease];
|
||||
|
||||
__block ASDisplayNode *node = nil;
|
||||
[self executeOffThread:^{
|
||||
@@ -265,6 +273,8 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
return view;
|
||||
}];
|
||||
}];
|
||||
// executeOffThread: blocks until the background thread finishes executing.
|
||||
node = [node autorelease]; // XXX This is very bad style.
|
||||
|
||||
XCTAssertFalse(node.layerBacked, @"Can't be layer backed");
|
||||
XCTAssertTrue(node.synchronous, @"Node with plain view should be synchronous");
|
||||
@@ -283,6 +293,9 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
return view;
|
||||
}];
|
||||
}];
|
||||
// executeOffThread: blocks until the background thread finishes executing.
|
||||
view = [view autorelease]; // XXX This is very bad style.
|
||||
node = [node autorelease]; // XXX This is very bad style.
|
||||
|
||||
XCTAssertNil(view, @"View block should not be invoked yet");
|
||||
[node view];
|
||||
@@ -293,10 +306,10 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
|
||||
- (void)testNodeCreatedWithLazyAsyncView
|
||||
{
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{
|
||||
ASDisplayNode *node = [[[ASDisplayNode alloc] initWithViewBlock:^UIView *{
|
||||
XCTAssertTrue([NSThread isMainThread], @"View block must run on the main queue");
|
||||
return [[_ASDisplayView alloc] init];
|
||||
}];
|
||||
return [[[_ASDisplayView alloc] init] autorelease];
|
||||
}] autorelease];
|
||||
|
||||
XCTAssertThrows([node view], @"Externally provided views should be synchronous");
|
||||
XCTAssertTrue(node.synchronous, @"Node with externally provided view should be synchronous");
|
||||
@@ -631,7 +644,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
// Perform parallel updates of a standard UIView/CALayer and an ASDisplayNode and ensure they are equivalent.
|
||||
- (void)testDeriveFrameFromBoundsPositionAnchorPoint
|
||||
{
|
||||
UIView *plainView = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
UIView *plainView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
|
||||
plainView.layer.anchorPoint = CGPointMake(0.25f, 0.75f);
|
||||
plainView.layer.position = CGPointMake(10, 20);
|
||||
plainView.layer.bounds = CGRectMake(0, 0, 60, 80);
|
||||
@@ -643,6 +656,8 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
node.bounds = CGRectMake(0, 0, 60, 80);
|
||||
node.position = CGPointMake(10, 20);
|
||||
}];
|
||||
// executeOffThread: blocks until the background thread finishes executing.
|
||||
node = [node autorelease]; // XXX This is very bad style.
|
||||
|
||||
XCTAssertTrue(CGRectEqualToRect(plainView.frame, node.frame), @"Node frame should match UIView frame before realization.");
|
||||
XCTAssertTrue(CGRectEqualToRect(plainView.frame, node.view.frame), @"Realized view frame should match UIView frame.");
|
||||
@@ -651,7 +666,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
// Perform parallel updates of a standard UIView/CALayer and an ASDisplayNode and ensure they are equivalent.
|
||||
- (void)testSetFrameSetsBoundsPosition
|
||||
{
|
||||
UIView *plainView = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
UIView *plainView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
|
||||
plainView.layer.anchorPoint = CGPointMake(0.25f, 0.75f);
|
||||
plainView.layer.frame = CGRectMake(10, 20, 60, 80);
|
||||
|
||||
@@ -661,6 +676,8 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
node.anchorPoint = CGPointMake(0.25f, 0.75f);
|
||||
node.frame = CGRectMake(10, 20, 60, 80);
|
||||
}];
|
||||
// executeOffThread: blocks until the background thread finishes executing.
|
||||
node = [node autorelease]; // XXX This is very bad style.
|
||||
|
||||
XCTAssertTrue(CGPointEqualToPoint(plainView.layer.position, node.position), @"Node position should match UIView position before realization.");
|
||||
XCTAssertTrue(CGRectEqualToRect(plainView.layer.bounds, node.bounds), @"Node bounds should match UIView bounds before realization.");
|
||||
@@ -921,7 +938,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
||||
|
||||
- (void)testDisplayNodePointConversionOnDeepHierarchies
|
||||
{
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node = [[[ASDisplayNode alloc] init] autorelease];
|
||||
|
||||
// 7 deep (six below root); each one positioned at position = (1, 1)
|
||||
_addTonsOfSubnodes(node, 2, 6, ^(ASDisplayNode *createdNode) {
|
||||
@@ -1108,7 +1125,7 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
||||
|
||||
- (void)testSubnodes
|
||||
{
|
||||
ASDisplayNode *parent = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *parent = [[[ASDisplayNode alloc] init] autorelease];
|
||||
ASDisplayNode *nilNode = nil;
|
||||
XCTAssertNoThrow([parent addSubnode:nilNode], @"Don't try to add nil, but we'll deal.");
|
||||
XCTAssertNoThrow([parent addSubnode:parent], @"Not good, test that we recover");
|
||||
@@ -1211,11 +1228,6 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
||||
XCTAssertNodesHaveParent(nilParent, a,d);
|
||||
|
||||
//TODO: assert that things deallocate immediately and don't have latent autoreleases in here
|
||||
[parent release];
|
||||
[a release];
|
||||
[b release];
|
||||
[c release];
|
||||
[d release];
|
||||
}
|
||||
|
||||
- (void)testInsertSubnodeAtIndexView
|
||||
@@ -1344,17 +1356,12 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
||||
XCTAssertNodesHaveParent(nilParent, d);
|
||||
|
||||
//TODO: assert that things deallocate immediately and don't have latent autoreleases in here
|
||||
[parent release];
|
||||
[a release];
|
||||
[b release];
|
||||
[c release];
|
||||
[d release];
|
||||
}
|
||||
|
||||
// This tests our resiliancy to having other views and layers inserted into our view or layer
|
||||
- (void)testInsertSubviewAtIndexWithMeddlingViewsAndLayersViewBacked
|
||||
{
|
||||
ASDisplayNode *parent = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *parent = [[[ASDisplayNode alloc] init] autorelease];
|
||||
|
||||
DeclareNodeNamed(a);
|
||||
DeclareNodeNamed(b);
|
||||
@@ -1389,12 +1396,6 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
||||
XCTAssertEqual(4u, parent.layer.sublayers.count, @"Should have the right sublayer count");
|
||||
|
||||
//TODO: assert that things deallocate immediately and don't have latent autoreleases in here
|
||||
[parent release];
|
||||
[a release];
|
||||
[b release];
|
||||
[c release];
|
||||
[d release];
|
||||
[e release];
|
||||
}
|
||||
|
||||
- (void)testAppleBugInsertSubview
|
||||
@@ -1467,11 +1468,6 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
||||
XCTAssertEqual(4u, parent.layer.sublayers.count, @"Should have the right sublayer count");
|
||||
|
||||
//TODO: assert that things deallocate immediately and don't have latent autoreleases in here
|
||||
[parent release];
|
||||
[a release];
|
||||
[b release];
|
||||
[c release];
|
||||
[d release];
|
||||
}
|
||||
|
||||
|
||||
@@ -1550,10 +1546,6 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
||||
XCTAssertNodesHaveParent(parent, a, c, b);
|
||||
|
||||
//TODO: assert that things deallocate immediately and don't have latent autoreleases in here
|
||||
[parent release];
|
||||
[a release];
|
||||
[b release];
|
||||
[c release];
|
||||
}
|
||||
|
||||
- (void)testInsertSubnodeAboveWithView
|
||||
@@ -1632,10 +1624,57 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
||||
XCTAssertNodesHaveParent(parent, a, c, b);
|
||||
|
||||
//TODO: assert that things deallocate immediately and don't have latent autoreleases in here
|
||||
[parent release];
|
||||
[a release];
|
||||
[b release];
|
||||
[c release];
|
||||
}
|
||||
|
||||
- (void)testRemoveFromViewBackedLoadedSupernode
|
||||
{
|
||||
DeclareNodeNamed(a);
|
||||
DeclareNodeNamed(b);
|
||||
[b addSubnode:a];
|
||||
[a view];
|
||||
[b view];
|
||||
XCTAssertNodesLoaded(a, b);
|
||||
XCTAssertEqual(a.supernode, b);
|
||||
XCTAssertEqual(a.view.superview, b.view);
|
||||
|
||||
[a removeFromSupernode];
|
||||
XCTAssertNil(a.supernode);
|
||||
XCTAssertNil(a.view.superview);
|
||||
}
|
||||
|
||||
- (void)testRemoveFromLayerBackedLoadedSupernode
|
||||
{
|
||||
DeclareNodeNamed(a);
|
||||
a.layerBacked = YES;
|
||||
DeclareNodeNamed(b);
|
||||
b.layerBacked = YES;
|
||||
[b addSubnode:a];
|
||||
[a layer];
|
||||
[b layer];
|
||||
XCTAssertNodesLoaded(a, b);
|
||||
XCTAssertEqual(a.supernode, b);
|
||||
XCTAssertEqual(a.layer.superlayer, b.layer);
|
||||
|
||||
[a removeFromSupernode];
|
||||
XCTAssertNil(a.supernode);
|
||||
XCTAssertNil(a.layer.superlayer);
|
||||
}
|
||||
|
||||
- (void)testRemoveLayerBackedFromViewBackedLoadedSupernode
|
||||
{
|
||||
DeclareNodeNamed(a);
|
||||
a.layerBacked = YES;
|
||||
DeclareNodeNamed(b);
|
||||
[b addSubnode:a];
|
||||
[a layer];
|
||||
[b view];
|
||||
XCTAssertNodesLoaded(a, b);
|
||||
XCTAssertEqual(a.supernode, b);
|
||||
XCTAssertEqual(a.layer.superlayer, b.layer);
|
||||
|
||||
[a removeFromSupernode];
|
||||
XCTAssertNil(a.supernode);
|
||||
XCTAssertNil(a.layer.superlayer);
|
||||
}
|
||||
|
||||
- (void)testSubnodeAddedBeforeLoadingExternalView
|
||||
@@ -1651,6 +1690,9 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
||||
child = [[ASDisplayNode alloc] init];
|
||||
[parent addSubnode:child];
|
||||
}];
|
||||
// executeOffThread: blocks until the background thread finishes executing.
|
||||
parent = [parent autorelease]; // XXX This is very bad style.
|
||||
child = [child autorelease]; // XXX This is very bad style.
|
||||
|
||||
XCTAssertEqual(1, parent.subnodes.count, @"Parent should have 1 subnode");
|
||||
XCTAssertEqualObjects(parent, child.supernode, @"Child has the wrong parent");
|
||||
@@ -1663,14 +1705,14 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
||||
|
||||
- (void)testSubnodeAddedAfterLoadingExternalView
|
||||
{
|
||||
UIView *view = [[UIDisplayNodeTestView alloc] init];
|
||||
ASDisplayNode *parent = [[ASDisplayNode alloc] initWithViewBlock:^{
|
||||
UIView *view = [[[UIDisplayNodeTestView alloc] init] autorelease];
|
||||
ASDisplayNode *parent = [[[ASDisplayNode alloc] initWithViewBlock:^{
|
||||
return view;
|
||||
}];
|
||||
}] autorelease];
|
||||
|
||||
[parent view];
|
||||
|
||||
ASDisplayNode *child = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *child = [[[ASDisplayNode alloc] init] autorelease];
|
||||
[parent addSubnode:child];
|
||||
|
||||
XCTAssertEqual(1, parent.subnodes.count, @"Parent should have 1 subnode");
|
||||
@@ -1800,7 +1842,7 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
||||
|
||||
- (void)testInitWithViewClass
|
||||
{
|
||||
ASDisplayNode *scrollNode = [[ASDisplayNode alloc] initWithViewClass:[UIScrollView class]];
|
||||
ASDisplayNode *scrollNode = [[[ASDisplayNode alloc] initWithViewClass:[UIScrollView class]] autorelease];
|
||||
|
||||
XCTAssertFalse(scrollNode.isLayerBacked, @"Can't be layer backed");
|
||||
XCTAssertFalse(scrollNode.nodeLoaded, @"Shouldn't have a view yet");
|
||||
@@ -1815,7 +1857,7 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
||||
|
||||
- (void)testInitWithLayerClass
|
||||
{
|
||||
ASDisplayNode *transformNode = [[ASDisplayNode alloc] initWithLayerClass:[CATransformLayer class]];
|
||||
ASDisplayNode *transformNode = [[[ASDisplayNode alloc] initWithLayerClass:[CATransformLayer class]] autorelease];
|
||||
|
||||
XCTAssertTrue(transformNode.isLayerBacked, @"Created with layer class => should be layer-backed by default");
|
||||
XCTAssertFalse(transformNode.nodeLoaded, @"Shouldn't have a view yet");
|
||||
@@ -1898,7 +1940,7 @@ static bool stringContainsPointer(NSString *description, const void *p) {
|
||||
|
||||
- (void)testBounds
|
||||
{
|
||||
ASDisplayNode *node = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node = [[[ASDisplayNode alloc] init] autorelease];
|
||||
node.bounds = CGRectMake(1, 2, 3, 4);
|
||||
node.frame = CGRectMake(5, 6, 7, 8);
|
||||
|
||||
@@ -1910,7 +1952,7 @@ static bool stringContainsPointer(NSString *description, const void *p) {
|
||||
|
||||
- (void)testDidEnterDisplayIsCalledWhenNodesEnterDisplayRange
|
||||
{
|
||||
ASTestDisplayNode *node = [[ASTestDisplayNode alloc] init];
|
||||
ASTestDisplayNode *node = [[[ASTestDisplayNode alloc] init] autorelease];
|
||||
|
||||
[node recursivelySetInterfaceState:ASInterfaceStateDisplay];
|
||||
|
||||
@@ -1919,7 +1961,7 @@ static bool stringContainsPointer(NSString *description, const void *p) {
|
||||
|
||||
- (void)testDidExitDisplayIsCalledWhenNodesExitDisplayRange
|
||||
{
|
||||
ASTestDisplayNode *node = [[ASTestDisplayNode alloc] init];
|
||||
ASTestDisplayNode *node = [[[ASTestDisplayNode alloc] init] autorelease];
|
||||
|
||||
[node recursivelySetInterfaceState:ASInterfaceStateDisplay];
|
||||
[node recursivelySetInterfaceState:ASInterfaceStateFetchData];
|
||||
@@ -1929,7 +1971,7 @@ static bool stringContainsPointer(NSString *description, const void *p) {
|
||||
|
||||
- (void)testDidEnterFetchDataIsCalledWhenNodesEnterFetchDataRange
|
||||
{
|
||||
ASTestDisplayNode *node = [[ASTestDisplayNode alloc] init];
|
||||
ASTestDisplayNode *node = [[[ASTestDisplayNode alloc] init] autorelease];
|
||||
|
||||
[node recursivelySetInterfaceState:ASInterfaceStateFetchData];
|
||||
|
||||
@@ -1938,7 +1980,7 @@ static bool stringContainsPointer(NSString *description, const void *p) {
|
||||
|
||||
- (void)testDidExitFetchDataIsCalledWhenNodesExitFetchDataRange
|
||||
{
|
||||
ASTestDisplayNode *node = [[ASTestDisplayNode alloc] init];
|
||||
ASTestDisplayNode *node = [[[ASTestDisplayNode alloc] init] autorelease];
|
||||
|
||||
[node recursivelySetInterfaceState:ASInterfaceStateFetchData];
|
||||
[node recursivelySetInterfaceState:ASInterfaceStateDisplay];
|
||||
|
||||
@@ -90,8 +90,22 @@ static BOOL ASRunRunLoopUntilBlockIsTrue(BOOL (^block)())
|
||||
{
|
||||
[super setUp];
|
||||
|
||||
_mockCache = [OCMockObject mockForProtocol:@protocol(ASImageCacheProtocol)];
|
||||
_mockDownloader = [OCMockObject mockForProtocol:@protocol(ASImageDownloaderProtocol)];
|
||||
_mockCache = [[OCMockObject mockForProtocol:@protocol(ASImageCacheProtocol)] retain];
|
||||
_mockDownloader = [[OCMockObject mockForProtocol:@protocol(ASImageDownloaderProtocol)] retain];
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
{
|
||||
if(_mockCache) {
|
||||
[_mockCache release];
|
||||
_mockCache = nil;
|
||||
}
|
||||
if(_mockDownloader) {
|
||||
[_mockDownloader release];
|
||||
_mockDownloader = nil;
|
||||
}
|
||||
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (void)testDataSourceImageMethod
|
||||
|
||||
@@ -408,8 +408,7 @@
|
||||
{
|
||||
CGSize tableViewSize = CGSizeMake(100, 500);
|
||||
ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height)
|
||||
style:UITableViewStylePlain
|
||||
asyncDataFetching:YES];
|
||||
style:UITableViewStylePlain];
|
||||
ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new];
|
||||
|
||||
tableView.asyncDelegate = dataSource;
|
||||
|
||||
562
AsyncDisplayKitTests/ASTableViewThrashTests.m
Normal file
562
AsyncDisplayKitTests/ASTableViewThrashTests.m
Normal file
@@ -0,0 +1,562 @@
|
||||
//
|
||||
// ASTableViewThrashTests.m
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Adlai Holler on 6/21/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
@import XCTest;
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
// Set to 1 to use UITableView and see if the issue still exists.
|
||||
#define USE_UIKIT_REFERENCE 0
|
||||
|
||||
#if USE_UIKIT_REFERENCE
|
||||
#define TableView UITableView
|
||||
#define kCellReuseID @"ASThrashTestCellReuseID"
|
||||
#else
|
||||
#define TableView ASTableView
|
||||
#endif
|
||||
|
||||
#define kInitialSectionCount 20
|
||||
#define kInitialItemCount 20
|
||||
#define kMinimumItemCount 5
|
||||
#define kMinimumSectionCount 3
|
||||
#define kFickleness 0.1
|
||||
#define kThrashingIterationCount 100
|
||||
|
||||
static NSString *ASThrashArrayDescription(NSArray *array) {
|
||||
NSMutableString *str = [NSMutableString stringWithString:@"(\n"];
|
||||
NSInteger i = 0;
|
||||
for (id obj in array) {
|
||||
[str appendFormat:@"\t[%ld]: \"%@\",\n", i, obj];
|
||||
i += 1;
|
||||
}
|
||||
[str appendString:@")"];
|
||||
return str;
|
||||
}
|
||||
|
||||
static volatile int32_t ASThrashTestItemNextID = 1;
|
||||
@interface ASThrashTestItem: NSObject <NSSecureCoding>
|
||||
@property (nonatomic, readonly) NSInteger itemID;
|
||||
|
||||
- (CGFloat)rowHeight;
|
||||
@end
|
||||
|
||||
@implementation ASThrashTestItem
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_itemID = OSAtomicIncrement32(&ASThrashTestItemNextID);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_itemID = [aDecoder decodeIntegerForKey:@"itemID"];
|
||||
NSAssert(_itemID > 0, @"Failed to decode %@", self);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[aCoder encodeInteger:_itemID forKey:@"itemID"];
|
||||
}
|
||||
|
||||
+ (NSMutableArray <ASThrashTestItem *> *)itemsWithCount:(NSInteger)count {
|
||||
NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
|
||||
for (NSInteger i = 0; i < count; i += 1) {
|
||||
[result addObject:[[ASThrashTestItem alloc] init]];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (CGFloat)rowHeight {
|
||||
return (self.itemID % 400) ?: 44;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<Item %lu>", (unsigned long)_itemID];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASThrashTestSection: NSObject <NSCopying, NSSecureCoding>
|
||||
@property (nonatomic, strong, readonly) NSMutableArray *items;
|
||||
@property (nonatomic, readonly) NSInteger sectionID;
|
||||
|
||||
- (CGFloat)headerHeight;
|
||||
@end
|
||||
|
||||
static volatile int32_t ASThrashTestSectionNextID = 1;
|
||||
@implementation ASThrashTestSection
|
||||
|
||||
/// Create an array of sections with the given count
|
||||
+ (NSMutableArray <ASThrashTestSection *> *)sectionsWithCount:(NSInteger)count {
|
||||
NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
|
||||
for (NSInteger i = 0; i < count; i += 1) {
|
||||
[result addObject:[[ASThrashTestSection alloc] initWithCount:kInitialItemCount]];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCount:(NSInteger)count {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_sectionID = OSAtomicIncrement32(&ASThrashTestSectionNextID);
|
||||
_items = [ASThrashTestItem itemsWithCount:count];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
return [self initWithCount:0];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_items = [aDecoder decodeObjectOfClass:[NSArray class] forKey:@"items"];
|
||||
_sectionID = [aDecoder decodeIntegerForKey:@"sectionID"];
|
||||
NSAssert(_sectionID > 0, @"Failed to decode %@", self);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[aCoder encodeObject:_items forKey:@"items"];
|
||||
[aCoder encodeInteger:_sectionID forKey:@"sectionID"];
|
||||
}
|
||||
|
||||
- (CGFloat)headerHeight {
|
||||
return self.sectionID % 400 ?: 44;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<Section %lu: itemCount=%lu>", (unsigned long)_sectionID, (unsigned long)self.items.count];
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
ASThrashTestSection *copy = [[ASThrashTestSection alloc] init];
|
||||
copy->_sectionID = _sectionID;
|
||||
copy->_items = [_items mutableCopy];
|
||||
return copy;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
if ([object isKindOfClass:[ASThrashTestSection class]]) {
|
||||
return [(ASThrashTestSection *)object sectionID] == _sectionID;
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#if !USE_UIKIT_REFERENCE
|
||||
@interface ASThrashTestNode: ASCellNode
|
||||
@property (nonatomic, strong) ASThrashTestItem *item;
|
||||
@end
|
||||
|
||||
@implementation ASThrashTestNode
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
@interface ASThrashDataSource: NSObject
|
||||
#if USE_UIKIT_REFERENCE
|
||||
<UITableViewDataSource, UITableViewDelegate>
|
||||
#else
|
||||
<ASTableDataSource, ASTableDelegate>
|
||||
#endif
|
||||
|
||||
@property (nonatomic, strong, readonly) UIWindow *window;
|
||||
@property (nonatomic, strong, readonly) TableView *tableView;
|
||||
@property (nonatomic, strong) NSArray <ASThrashTestSection *> *data;
|
||||
@end
|
||||
|
||||
|
||||
@implementation ASThrashDataSource
|
||||
|
||||
- (instancetype)initWithData:(NSArray <ASThrashTestSection *> *)data {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_data = [[NSArray alloc] initWithArray:data copyItems:YES];
|
||||
_window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
_tableView = [[TableView alloc] initWithFrame:_window.bounds style:UITableViewStylePlain];
|
||||
[_window addSubview:_tableView];
|
||||
#if USE_UIKIT_REFERENCE
|
||||
_tableView.dataSource = self;
|
||||
_tableView.delegate = self;
|
||||
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kCellReuseID];
|
||||
#else
|
||||
_tableView.asyncDelegate = self;
|
||||
_tableView.asyncDataSource = self;
|
||||
[_tableView reloadDataImmediately];
|
||||
#endif
|
||||
[_tableView layoutIfNeeded];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
||||
return self.data[section].items.count;
|
||||
}
|
||||
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||||
return self.data.count;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
|
||||
return self.data[section].headerHeight;
|
||||
}
|
||||
|
||||
#if USE_UIKIT_REFERENCE
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
return [tableView dequeueReusableCellWithIdentifier:kCellReuseID forIndexPath:indexPath];
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
ASThrashTestItem *item = self.data[indexPath.section].items[indexPath.item];
|
||||
return item.rowHeight;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
- (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
ASThrashTestItem *item = self.data[indexPath.section].items[indexPath.item];
|
||||
return ^{
|
||||
ASThrashTestNode *node = [[ASThrashTestNode alloc] init];
|
||||
node.item = item;
|
||||
return node;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation NSIndexSet (ASThrashHelpers)
|
||||
|
||||
- (NSArray <NSIndexPath *> *)indexPathsInSection:(NSInteger)section {
|
||||
NSMutableArray *result = [NSMutableArray arrayWithCapacity:self.count];
|
||||
[self enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
[result addObject:[NSIndexPath indexPathForItem:idx inSection:section]];
|
||||
}];
|
||||
return result;
|
||||
}
|
||||
|
||||
/// `insertMode` means that for each index selected, the max goes up by one.
|
||||
+ (NSMutableIndexSet *)randomIndexesLessThan:(NSInteger)max probability:(float)probability insertMode:(BOOL)insertMode {
|
||||
NSMutableIndexSet *indexes = [[NSMutableIndexSet alloc] init];
|
||||
u_int32_t cutoff = probability * 100;
|
||||
for (NSInteger i = 0; i < max; i++) {
|
||||
if (arc4random_uniform(100) < cutoff) {
|
||||
[indexes addIndex:i];
|
||||
if (insertMode) {
|
||||
max += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return indexes;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static NSInteger ASThrashUpdateCurrentSerializationVersion = 1;
|
||||
|
||||
@interface ASThrashUpdate : NSObject <NSSecureCoding>
|
||||
@property (nonatomic, strong, readonly) NSArray<ASThrashTestSection *> *oldData;
|
||||
@property (nonatomic, strong, readonly) NSMutableArray<ASThrashTestSection *> *data;
|
||||
@property (nonatomic, strong, readonly) NSMutableIndexSet *deletedSectionIndexes;
|
||||
@property (nonatomic, strong, readonly) NSMutableIndexSet *replacedSectionIndexes;
|
||||
/// The sections used to replace the replaced sections.
|
||||
@property (nonatomic, strong, readonly) NSMutableArray<ASThrashTestSection *> *replacingSections;
|
||||
@property (nonatomic, strong, readonly) NSMutableIndexSet *insertedSectionIndexes;
|
||||
@property (nonatomic, strong, readonly) NSMutableArray<ASThrashTestSection *> *insertedSections;
|
||||
@property (nonatomic, strong, readonly) NSMutableArray<NSMutableIndexSet *> *deletedItemIndexes;
|
||||
@property (nonatomic, strong, readonly) NSMutableArray<NSMutableIndexSet *> *replacedItemIndexes;
|
||||
/// The items used to replace the replaced items.
|
||||
@property (nonatomic, strong, readonly) NSMutableArray<NSArray <ASThrashTestItem *> *> *replacingItems;
|
||||
@property (nonatomic, strong, readonly) NSMutableArray<NSMutableIndexSet *> *insertedItemIndexes;
|
||||
@property (nonatomic, strong, readonly) NSMutableArray<NSArray <ASThrashTestItem *> *> *insertedItems;
|
||||
|
||||
- (instancetype)initWithData:(NSArray<ASThrashTestSection *> *)data;
|
||||
|
||||
+ (ASThrashUpdate *)thrashUpdateWithBase64String:(NSString *)base64;
|
||||
- (NSString *)base64Representation;
|
||||
@end
|
||||
|
||||
@implementation ASThrashUpdate
|
||||
|
||||
- (instancetype)initWithData:(NSArray<ASThrashTestSection *> *)data {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_data = [[NSMutableArray alloc] initWithArray:data copyItems:YES];
|
||||
_oldData = [[NSArray alloc] initWithArray:data copyItems:YES];
|
||||
|
||||
_deletedItemIndexes = [NSMutableArray array];
|
||||
_replacedItemIndexes = [NSMutableArray array];
|
||||
_insertedItemIndexes = [NSMutableArray array];
|
||||
_replacingItems = [NSMutableArray array];
|
||||
_insertedItems = [NSMutableArray array];
|
||||
|
||||
// Randomly reload some items
|
||||
for (ASThrashTestSection *section in _data) {
|
||||
NSMutableIndexSet *indexes = [NSIndexSet randomIndexesLessThan:section.items.count probability:kFickleness insertMode:NO];
|
||||
NSArray *newItems = [ASThrashTestItem itemsWithCount:indexes.count];
|
||||
[section.items replaceObjectsAtIndexes:indexes withObjects:newItems];
|
||||
[_replacingItems addObject:newItems];
|
||||
[_replacedItemIndexes addObject:indexes];
|
||||
}
|
||||
|
||||
// Randomly replace some sections
|
||||
_replacedSectionIndexes = [NSIndexSet randomIndexesLessThan:_data.count probability:kFickleness insertMode:NO];
|
||||
_replacingSections = [ASThrashTestSection sectionsWithCount:_replacedSectionIndexes.count];
|
||||
[_data replaceObjectsAtIndexes:_replacedSectionIndexes withObjects:_replacingSections];
|
||||
|
||||
// Randomly delete some items
|
||||
[_data enumerateObjectsUsingBlock:^(ASThrashTestSection * _Nonnull section, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
if (section.items.count >= kMinimumItemCount) {
|
||||
NSMutableIndexSet *indexes = [NSIndexSet randomIndexesLessThan:section.items.count probability:kFickleness insertMode:NO];
|
||||
|
||||
/// Cannot reload & delete the same item.
|
||||
[indexes removeIndexes:_replacedItemIndexes[idx]];
|
||||
|
||||
[section.items removeObjectsAtIndexes:indexes];
|
||||
[_deletedItemIndexes addObject:indexes];
|
||||
} else {
|
||||
[_deletedItemIndexes addObject:[NSMutableIndexSet indexSet]];
|
||||
}
|
||||
}];
|
||||
|
||||
// Randomly delete some sections
|
||||
if (_data.count >= kMinimumSectionCount) {
|
||||
_deletedSectionIndexes = [NSIndexSet randomIndexesLessThan:_data.count probability:kFickleness insertMode:NO];
|
||||
} else {
|
||||
_deletedSectionIndexes = [NSMutableIndexSet indexSet];
|
||||
}
|
||||
// Cannot replace & delete the same section.
|
||||
[_deletedSectionIndexes removeIndexes:_replacedSectionIndexes];
|
||||
|
||||
// Cannot delete/replace item in deleted/replaced section
|
||||
[_deletedSectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
[_replacedItemIndexes[idx] removeAllIndexes];
|
||||
[_deletedItemIndexes[idx] removeAllIndexes];
|
||||
}];
|
||||
[_replacedSectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
[_replacedItemIndexes[idx] removeAllIndexes];
|
||||
[_deletedItemIndexes[idx] removeAllIndexes];
|
||||
}];
|
||||
[_data removeObjectsAtIndexes:_deletedSectionIndexes];
|
||||
|
||||
// Randomly insert some sections
|
||||
_insertedSectionIndexes = [NSIndexSet randomIndexesLessThan:(_data.count + 1) probability:kFickleness insertMode:YES];
|
||||
_insertedSections = [ASThrashTestSection sectionsWithCount:_insertedSectionIndexes.count];
|
||||
[_data insertObjects:_insertedSections atIndexes:_insertedSectionIndexes];
|
||||
|
||||
// Randomly insert some items
|
||||
for (ASThrashTestSection *section in _data) {
|
||||
// Only insert items into the old sections – not replaced/inserted sections.
|
||||
if ([_oldData containsObject:section]) {
|
||||
NSMutableIndexSet *indexes = [NSIndexSet randomIndexesLessThan:(section.items.count + 1) probability:kFickleness insertMode:YES];
|
||||
NSArray *newItems = [ASThrashTestItem itemsWithCount:indexes.count];
|
||||
[section.items insertObjects:newItems atIndexes:indexes];
|
||||
[_insertedItems addObject:newItems];
|
||||
[_insertedItemIndexes addObject:indexes];
|
||||
} else {
|
||||
[_insertedItems addObject:@[]];
|
||||
[_insertedItemIndexes addObject:[NSMutableIndexSet indexSet]];
|
||||
}
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (ASThrashUpdate *)thrashUpdateWithBase64String:(NSString *)base64 {
|
||||
return [NSKeyedUnarchiver unarchiveObjectWithData:[[NSData alloc] initWithBase64EncodedString:base64 options:kNilOptions]];
|
||||
}
|
||||
|
||||
- (NSString *)base64Representation {
|
||||
return [[NSKeyedArchiver archivedDataWithRootObject:self] base64EncodedStringWithOptions:kNilOptions];
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
NSDictionary *dict = [self dictionaryWithValuesForKeys:@[
|
||||
@"oldData",
|
||||
@"data",
|
||||
@"deletedSectionIndexes",
|
||||
@"replacedSectionIndexes",
|
||||
@"replacingSections",
|
||||
@"insertedSectionIndexes",
|
||||
@"insertedSections",
|
||||
@"deletedItemIndexes",
|
||||
@"replacedItemIndexes",
|
||||
@"replacingItems",
|
||||
@"insertedItemIndexes",
|
||||
@"insertedItems"
|
||||
]];
|
||||
[aCoder encodeObject:dict forKey:@"_dict"];
|
||||
[aCoder encodeInteger:ASThrashUpdateCurrentSerializationVersion forKey:@"_version"];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
NSAssert(ASThrashUpdateCurrentSerializationVersion == [aDecoder decodeIntegerForKey:@"_version"], @"This thrash update was archived from a different version and can't be read. Sorry.");
|
||||
NSDictionary *dict = [aDecoder decodeObjectOfClass:[NSDictionary class] forKey:@"_dict"];
|
||||
[self setValuesForKeysWithDictionary:dict];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<ASThrashUpdate %p:\nOld data: %@\nDeleted items: %@\nDeleted sections: %@\nReplaced items: %@\nReplaced sections: %@\nInserted items: %@\nInserted sections: %@\nNew data: %@>", self, ASThrashArrayDescription(_oldData), ASThrashArrayDescription(_deletedItemIndexes), _deletedSectionIndexes, ASThrashArrayDescription(_replacedItemIndexes), _replacedSectionIndexes, ASThrashArrayDescription(_insertedItemIndexes), _insertedSectionIndexes, ASThrashArrayDescription(_data)];
|
||||
}
|
||||
|
||||
- (NSString *)logFriendlyBase64Representation {
|
||||
return [NSString stringWithFormat:@"\n\n**********\nBase64 Representation:\n**********\n%@\n**********\nEnd Base64 Representation\n**********", self.base64Representation];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASTableViewThrashTests: XCTestCase
|
||||
@end
|
||||
|
||||
@implementation ASTableViewThrashTests {
|
||||
// The current update, which will be logged in case of a failure.
|
||||
ASThrashUpdate *_update;
|
||||
}
|
||||
|
||||
#pragma mark Overrides
|
||||
|
||||
- (void)tearDown {
|
||||
_update = nil;
|
||||
}
|
||||
|
||||
// NOTE: Despite the documentation, this is not always called if an exception is caught.
|
||||
- (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)filePath atLine:(NSUInteger)lineNumber expected:(BOOL)expected {
|
||||
[self logCurrentUpdateIfNeeded];
|
||||
[super recordFailureWithDescription:description inFile:filePath atLine:lineNumber expected:expected];
|
||||
}
|
||||
|
||||
#pragma mark Test Methods
|
||||
|
||||
- (void)testInitialDataRead {
|
||||
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initWithData:[ASThrashTestSection sectionsWithCount:kInitialSectionCount]];
|
||||
[self verifyDataSource:ds];
|
||||
}
|
||||
|
||||
/// Replays the Base64 representation of an ASThrashUpdate from "ASThrashTestRecordedCase" file
|
||||
- (void)DISABLED_testRecordedThrashCase {
|
||||
NSURL *caseURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"ASThrashTestRecordedCase" withExtension:nil subdirectory:@"TestResources"];
|
||||
NSString *base64 = [NSString stringWithContentsOfURL:caseURL encoding:NSUTF8StringEncoding error:NULL];
|
||||
|
||||
_update = [ASThrashUpdate thrashUpdateWithBase64String:base64];
|
||||
if (_update == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initWithData:_update.oldData];
|
||||
[self applyUpdate:_update toDataSource:ds];
|
||||
[self verifyDataSource:ds];
|
||||
}
|
||||
|
||||
- (void)DISABLED_testThrashingWildly {
|
||||
for (NSInteger i = 0; i < kThrashingIterationCount; i++) {
|
||||
[self setUp];
|
||||
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initWithData:[ASThrashTestSection sectionsWithCount:kInitialSectionCount]];
|
||||
_update = [[ASThrashUpdate alloc] initWithData:ds.data];
|
||||
|
||||
[self applyUpdate:_update toDataSource:ds];
|
||||
[self verifyDataSource:ds];
|
||||
[self tearDown];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Helpers
|
||||
|
||||
- (void)logCurrentUpdateIfNeeded {
|
||||
if (_update != nil) {
|
||||
NSLog(@"Failed update %@: %@", _update, _update.logFriendlyBase64Representation);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applyUpdate:(ASThrashUpdate *)update toDataSource:(ASThrashDataSource *)dataSource {
|
||||
TableView *tableView = dataSource.tableView;
|
||||
|
||||
[tableView beginUpdates];
|
||||
dataSource.data = update.data;
|
||||
|
||||
[tableView insertSections:update.insertedSectionIndexes withRowAnimation:UITableViewRowAnimationNone];
|
||||
|
||||
[tableView deleteSections:update.deletedSectionIndexes withRowAnimation:UITableViewRowAnimationNone];
|
||||
|
||||
[tableView reloadSections:update.replacedSectionIndexes withRowAnimation:UITableViewRowAnimationNone];
|
||||
|
||||
[update.insertedItemIndexes enumerateObjectsUsingBlock:^(NSMutableIndexSet * _Nonnull indexes, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
NSArray *indexPaths = [indexes indexPathsInSection:idx];
|
||||
[tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
|
||||
}];
|
||||
|
||||
[update.deletedItemIndexes enumerateObjectsUsingBlock:^(NSMutableIndexSet * _Nonnull indexes, NSUInteger sec, BOOL * _Nonnull stop) {
|
||||
NSArray *indexPaths = [indexes indexPathsInSection:sec];
|
||||
[tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
|
||||
}];
|
||||
|
||||
[update.replacedItemIndexes enumerateObjectsUsingBlock:^(NSMutableIndexSet * _Nonnull indexes, NSUInteger sec, BOOL * _Nonnull stop) {
|
||||
NSArray *indexPaths = [indexes indexPathsInSection:sec];
|
||||
[tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
|
||||
}];
|
||||
@try {
|
||||
[tableView endUpdates];
|
||||
#if !USE_UIKIT_REFERENCE
|
||||
[tableView waitUntilAllUpdatesAreCommitted];
|
||||
#endif
|
||||
} @catch (NSException *exception) {
|
||||
[self logCurrentUpdateIfNeeded];
|
||||
@throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)verifyDataSource:(ASThrashDataSource *)ds {
|
||||
TableView *tableView = ds.tableView;
|
||||
NSArray <ASThrashTestSection *> *data = [ds data];
|
||||
XCTAssertEqual(data.count, tableView.numberOfSections);
|
||||
for (NSInteger i = 0; i < tableView.numberOfSections; i++) {
|
||||
XCTAssertEqual([tableView numberOfRowsInSection:i], data[i].items.count);
|
||||
XCTAssertEqual([tableView rectForHeaderInSection:i].size.height, data[i].headerHeight);
|
||||
|
||||
for (NSInteger j = 0; j < [tableView numberOfRowsInSection:i]; j++) {
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i];
|
||||
ASThrashTestItem *item = data[i].items[j];
|
||||
#if USE_UIKIT_REFERENCE
|
||||
XCTAssertEqual([tableView rectForRowAtIndexPath:indexPath].size.height, item.rowHeight);
|
||||
#else
|
||||
ASThrashTestNode *node = (ASThrashTestNode *)[tableView nodeForRowAtIndexPath:indexPath];
|
||||
XCTAssertEqual(node.item, item);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -15,7 +15,7 @@
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||
|
||||
@interface ASVideoNodeTests : XCTestCase
|
||||
@interface ASVideoNodeTests : XCTestCase <ASVideoNodeDelegate>
|
||||
{
|
||||
ASVideoNode *_videoNode;
|
||||
AVURLAsset *_firstAsset;
|
||||
@@ -25,10 +25,18 @@
|
||||
}
|
||||
@end
|
||||
|
||||
@interface ASNetworkImageNode () {
|
||||
@public __weak id<ASNetworkImageNodeDelegate> _delegate;
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@interface ASVideoNode () {
|
||||
ASDisplayNode *_playerNode;
|
||||
AVPlayer *_player;
|
||||
}
|
||||
|
||||
|
||||
@property (atomic, readwrite) ASInterfaceState interfaceState;
|
||||
@property (atomic, readonly) ASDisplayNode *spinner;
|
||||
@property (atomic, readwrite) ASDisplayNode *playerNode;
|
||||
@@ -405,4 +413,18 @@
|
||||
XCTAssertNil(_videoNode.image);
|
||||
}
|
||||
|
||||
- (void)testDelegateProperlySetForClassHierarchy
|
||||
{
|
||||
_videoNode.delegate = self;
|
||||
|
||||
XCTAssertTrue([_videoNode.delegate conformsToProtocol:@protocol(ASVideoNodeDelegate)]);
|
||||
XCTAssertTrue([_videoNode.delegate conformsToProtocol:@protocol(ASNetworkImageNodeDelegate)]);
|
||||
XCTAssertTrue([((ASNetworkImageNode*)_videoNode).delegate conformsToProtocol:@protocol(ASNetworkImageNodeDelegate)]);
|
||||
XCTAssertTrue([((ASNetworkImageNode*)_videoNode)->_delegate conformsToProtocol:@protocol(ASNetworkImageNodeDelegate)]);
|
||||
|
||||
XCTAssertEqual(_videoNode.delegate, self);
|
||||
XCTAssertEqual(((ASNetworkImageNode*)_videoNode).delegate, self);
|
||||
XCTAssertEqual(((ASNetworkImageNode*)_videoNode)->_delegate, self);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
File diff suppressed because one or more lines are too long
89
build.sh
89
build.sh
@@ -36,21 +36,102 @@ if [ "$MODE" = "tests" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$MODE" = "examples" ]; then
|
||||
if [ "$MODE" = "examples-pt1" ]; then
|
||||
echo "Verifying that all AsyncDisplayKit examples compile."
|
||||
|
||||
for example in examples/*/; do
|
||||
echo "Building $example."
|
||||
for example in $((find ./examples -type d -maxdepth 1 \( ! -iname ".*" \)) | head -6 | head); do
|
||||
echo "Building (examples-pt1) $example."
|
||||
|
||||
if [ -f "${example}/Podfile" ]; then
|
||||
echo "Using CocoaPods"
|
||||
pod install --project-directory=$example
|
||||
|
||||
set -o pipefail && xcodebuild \
|
||||
-workspace "${example}Sample.xcworkspace" \
|
||||
-workspace "${example}/Sample.xcworkspace" \
|
||||
-scheme Sample \
|
||||
-sdk "$SDK" \
|
||||
-destination "$PLATFORM" \
|
||||
-derivedDataPath ~/ \
|
||||
build | xcpretty $FORMATTER
|
||||
elif [ -f "${example}/Cartfile" ]; then
|
||||
echo "Using Carthage"
|
||||
local_repo=`pwd`
|
||||
current_branch=`git rev-parse --abbrev-ref HEAD`
|
||||
cd $example
|
||||
|
||||
echo "git \"file://${local_repo}\" \"${current_branch}\"" > "Cartfile"
|
||||
carthage update --platform iOS
|
||||
|
||||
set -o pipefail && xcodebuild \
|
||||
-project "Sample.xcodeproj" \
|
||||
-scheme Sample \
|
||||
-sdk "$SDK" \
|
||||
-destination "$PLATFORM" \
|
||||
build | xcpretty $FORMATTER
|
||||
|
||||
cd ../..
|
||||
fi
|
||||
done
|
||||
trap - EXIT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$MODE" = "examples-pt2" ]; then
|
||||
echo "Verifying that all AsyncDisplayKit examples compile."
|
||||
|
||||
for example in $((find ./examples -type d -maxdepth 1 \( ! -iname ".*" \)) | head -12 | tail -6 | head); do
|
||||
echo "Building $example (examples-pt2)."
|
||||
|
||||
if [ -f "${example}/Podfile" ]; then
|
||||
echo "Using CocoaPods"
|
||||
pod install --project-directory=$example
|
||||
|
||||
set -o pipefail && xcodebuild \
|
||||
-workspace "${example}/Sample.xcworkspace" \
|
||||
-scheme Sample \
|
||||
-sdk "$SDK" \
|
||||
-destination "$PLATFORM" \
|
||||
-derivedDataPath ~/ \
|
||||
build | xcpretty $FORMATTER
|
||||
elif [ -f "${example}/Cartfile" ]; then
|
||||
echo "Using Carthage"
|
||||
local_repo=`pwd`
|
||||
current_branch=`git rev-parse --abbrev-ref HEAD`
|
||||
cd $example
|
||||
|
||||
echo "git \"file://${local_repo}\" \"${current_branch}\"" > "Cartfile"
|
||||
carthage update --platform iOS
|
||||
|
||||
set -o pipefail && xcodebuild \
|
||||
-project "Sample.xcodeproj" \
|
||||
-scheme Sample \
|
||||
-sdk "$SDK" \
|
||||
-destination "$PLATFORM" \
|
||||
build | xcpretty $FORMATTER
|
||||
|
||||
cd ../..
|
||||
fi
|
||||
done
|
||||
trap - EXIT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$MODE" = "examples-pt3" ]; then
|
||||
echo "Verifying that all AsyncDisplayKit examples compile."
|
||||
|
||||
for example in $((find ./examples -type d -maxdepth 1 \( ! -iname ".*" \)) | head -7 | head); do
|
||||
echo "Building $example (examples-pt3)."
|
||||
|
||||
if [ -f "${example}/Podfile" ]; then
|
||||
echo "Using CocoaPods"
|
||||
pod install --project-directory=$example
|
||||
|
||||
set -o pipefail && xcodebuild \
|
||||
-workspace "${example}/Sample.xcworkspace" \
|
||||
-scheme Sample \
|
||||
-sdk "$SDK" \
|
||||
-destination "$PLATFORM" \
|
||||
-derivedDataPath ~/ \
|
||||
build | xcpretty $FORMATTER
|
||||
elif [ -f "${example}/Cartfile" ]; then
|
||||
echo "Using Carthage"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
git "file:///Users/scottg/code/AsyncDisplayKit" "master"
|
||||
1
examples_extra/CarthageBuildTest/Cartfile
Normal file
1
examples_extra/CarthageBuildTest/Cartfile
Normal file
@@ -0,0 +1 @@
|
||||
github "facebook/AsyncDisplayKit" "master"
|
||||
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8150" systemVersion="15A204g" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8122"/>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
@@ -15,7 +16,6 @@
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<animations/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
@@ -1,13 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6211" systemVersion="14A298i" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6204"/>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController">
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
@@ -14,6 +14,8 @@
|
||||
871BB3591C7C98B1005CF62A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 871BB3581C7C98B1005CF62A /* Assets.xcassets */; };
|
||||
871BB35C1C7C98B1005CF62A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 871BB35A1C7C98B1005CF62A /* LaunchScreen.storyboard */; };
|
||||
871BB3651C7C99B0005CF62A /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 871BB3641C7C99B0005CF62A /* AsyncDisplayKit.framework */; };
|
||||
DEAE185D1D1A504A0083FAD0 /* PINCache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEAE185B1D1A504A0083FAD0 /* PINCache.framework */; };
|
||||
DEAE185E1D1A504A0083FAD0 /* PINRemoteImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEAE185C1D1A504A0083FAD0 /* PINRemoteImage.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@@ -28,6 +30,8 @@
|
||||
871BB35B1C7C98B1005CF62A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
871BB35D1C7C98B1005CF62A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
871BB3641C7C99B0005CF62A /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AsyncDisplayKit.framework; path = Carthage/Build/iOS/AsyncDisplayKit.framework; sourceTree = SOURCE_ROOT; };
|
||||
DEAE185B1D1A504A0083FAD0 /* PINCache.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PINCache.framework; path = ../Carthage/Build/iOS/PINCache.framework; sourceTree = "<group>"; };
|
||||
DEAE185C1D1A504A0083FAD0 /* PINRemoteImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PINRemoteImage.framework; path = ../Carthage/Build/iOS/PINRemoteImage.framework; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -36,6 +40,8 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
871BB3651C7C99B0005CF62A /* AsyncDisplayKit.framework in Frameworks */,
|
||||
DEAE185D1D1A504A0083FAD0 /* PINCache.framework in Frameworks */,
|
||||
DEAE185E1D1A504A0083FAD0 /* PINRemoteImage.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -87,6 +93,8 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
871BB3641C7C99B0005CF62A /* AsyncDisplayKit.framework */,
|
||||
DEAE185B1D1A504A0083FAD0 /* PINCache.framework */,
|
||||
DEAE185C1D1A504A0083FAD0 /* PINRemoteImage.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -126,7 +134,7 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 871BB3441C7C98B1005CF62A /* Build configuration list for PBXProject "CarthageExample" */;
|
||||
buildConfigurationList = 871BB3441C7C98B1005CF62A /* Build configuration list for PBXProject "Sample" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
@@ -325,7 +333,7 @@
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
871BB3441C7C98B1005CF62A /* Build configuration list for PBXProject "CarthageExample" */ = {
|
||||
871BB3441C7C98B1005CF62A /* Build configuration list for PBXProject "Sample" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
871BB35E1C7C98B1005CF62A /* Debug */,
|
||||
Reference in New Issue
Block a user