mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
Merge in downstream changes.
Introduce `ASTableView`, a UITableView subclass that uses `ASCellNode` instead of UITableViewCell. Add working range support via `ASRangeController`, which observes the visible range, maintains a working range, and handles most ASDK machinery. ASRangeController is loosely-enough coupled that it should be easily adapted to UICollectionView if that's desired in the future. Notable considerations in the ASRangeController architecture: * There's no sense rewriting UITableView -- the real win comes from using nodes instead of UITableViewCells (easily parallelisable computation, large number of cells vs. few table views, etc.). So, use a UITableView with empty cells, using UITableViewCell's contentView as a host for arbitrary node hierarchies. * Instead of lazy-loading cells the instant they're needed by UITableView, load them in advance. Preload a substantial number of nodes in the direction of scroll, as well as a small buffer in the other direction. * Maintain compatibility with UITableView's API, with one primary change -- consumer code yields configured ASCellNodes, not UITableViewCells. * Don't use -tableView:heightForRowAtIndexPath:. Nodes already compute their preferred sizes and cache results for use at layout-time, so ASTableView uses their calculatedSizes directly. * Corollary: ASTableView is only aware of nodes that have been sized. This means that, if a cell appears onscreen, it has layout data and can display a "realistic placeholder", e.g. by making its subnodes' background colour grey. Other improvements: * Remove dead references and update headers (fixes #7, #20). * Rename `-[ASDisplayNode sizeToFit:]` to `-measure:` and fix `constrainedSizeForCalulatedSize` typo (fixes #15). * Rename `-willAppear` and `-didDisappear` to `-willEnterHierarchy` and `-didExitHierarchy`. Remove `-willDisappear` -- it was redundant, and there was no counterpart `-didAppear`. * Rename `viewLoaded` to `nodeLoaded`.
This commit is contained in:
@@ -7,6 +7,13 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
055F1A3419ABD3E3004DAFF1 /* ASTableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3219ABD3E3004DAFF1 /* ASTableView.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
|
055F1A3519ABD3E3004DAFF1 /* ASTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 055F1A3319ABD3E3004DAFF1 /* ASTableView.m */; };
|
||||||
|
055F1A3819ABD413004DAFF1 /* ASRangeController.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3619ABD413004DAFF1 /* ASRangeController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
|
055F1A3919ABD413004DAFF1 /* ASRangeController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 055F1A3719ABD413004DAFF1 /* ASRangeController.mm */; };
|
||||||
|
055F1A3C19ABD43F004DAFF1 /* ASCellNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
|
055F1A3D19ABD43F004DAFF1 /* ASCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 055F1A3B19ABD43F004DAFF1 /* ASCellNode.m */; };
|
||||||
|
0574D5E219C110940097DC25 /* ASTableViewProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 0574D5E119C110610097DC25 /* ASTableViewProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
058D09B0195D04C000B7D73C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; };
|
058D09B0195D04C000B7D73C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; };
|
||||||
058D09BE195D04C000B7D73C /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09BD195D04C000B7D73C /* XCTest.framework */; };
|
058D09BE195D04C000B7D73C /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09BD195D04C000B7D73C /* XCTest.framework */; };
|
||||||
058D09BF195D04C000B7D73C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; };
|
058D09BF195D04C000B7D73C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; };
|
||||||
@@ -138,6 +145,14 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
053011A719B9882B00A9F2D0 /* ASRangeControllerInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASRangeControllerInternal.h; sourceTree = "<group>"; };
|
||||||
|
055F1A3219ABD3E3004DAFF1 /* ASTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableView.h; sourceTree = "<group>"; };
|
||||||
|
055F1A3319ABD3E3004DAFF1 /* ASTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableView.m; sourceTree = "<group>"; };
|
||||||
|
055F1A3619ABD413004DAFF1 /* ASRangeController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeController.h; sourceTree = "<group>"; };
|
||||||
|
055F1A3719ABD413004DAFF1 /* ASRangeController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRangeController.mm; sourceTree = "<group>"; };
|
||||||
|
055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCellNode.h; sourceTree = "<group>"; };
|
||||||
|
055F1A3B19ABD43F004DAFF1 /* ASCellNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCellNode.m; sourceTree = "<group>"; };
|
||||||
|
0574D5E119C110610097DC25 /* ASTableViewProtocols.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASTableViewProtocols.h; sourceTree = "<group>"; };
|
||||||
058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAsyncDisplayKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAsyncDisplayKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
058D09AF195D04C000B7D73C /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
058D09AF195D04C000B7D73C /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
||||||
058D09B3195D04C000B7D73C /* AsyncDisplayKit-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AsyncDisplayKit-Prefix.pch"; sourceTree = "<group>"; };
|
058D09B3195D04C000B7D73C /* AsyncDisplayKit-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AsyncDisplayKit-Prefix.pch"; sourceTree = "<group>"; };
|
||||||
@@ -295,6 +310,11 @@
|
|||||||
058D09E0195D050800B7D73C /* ASTextNode.mm */,
|
058D09E0195D050800B7D73C /* ASTextNode.mm */,
|
||||||
058D09DD195D050800B7D73C /* ASImageNode.h */,
|
058D09DD195D050800B7D73C /* ASImageNode.h */,
|
||||||
058D09DE195D050800B7D73C /* ASImageNode.mm */,
|
058D09DE195D050800B7D73C /* ASImageNode.mm */,
|
||||||
|
055F1A3219ABD3E3004DAFF1 /* ASTableView.h */,
|
||||||
|
0574D5E119C110610097DC25 /* ASTableViewProtocols.h */,
|
||||||
|
055F1A3319ABD3E3004DAFF1 /* ASTableView.m */,
|
||||||
|
055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */,
|
||||||
|
055F1A3B19ABD43F004DAFF1 /* ASCellNode.m */,
|
||||||
058D09E1195D050800B7D73C /* Details */,
|
058D09E1195D050800B7D73C /* Details */,
|
||||||
058D0A01195D050800B7D73C /* Private */,
|
058D0A01195D050800B7D73C /* Private */,
|
||||||
058D09B2195D04C000B7D73C /* Supporting Files */,
|
058D09B2195D04C000B7D73C /* Supporting Files */,
|
||||||
@@ -364,6 +384,8 @@
|
|||||||
058D09F4195D050800B7D73C /* ASTextNodeWordKerner.m */,
|
058D09F4195D050800B7D73C /* ASTextNodeWordKerner.m */,
|
||||||
058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */,
|
058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */,
|
||||||
058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m */,
|
058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m */,
|
||||||
|
055F1A3619ABD413004DAFF1 /* ASRangeController.h */,
|
||||||
|
055F1A3719ABD413004DAFF1 /* ASRangeController.mm */,
|
||||||
058D09F7195D050800B7D73C /* Transactions */,
|
058D09F7195D050800B7D73C /* Transactions */,
|
||||||
058D09FF195D050800B7D73C /* UIView+ASConvenience.h */,
|
058D09FF195D050800B7D73C /* UIView+ASConvenience.h */,
|
||||||
058D0A00195D050800B7D73C /* UIView+ASConvenience.m */,
|
058D0A00195D050800B7D73C /* UIView+ASConvenience.m */,
|
||||||
@@ -402,6 +424,7 @@
|
|||||||
058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */,
|
058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */,
|
||||||
058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */,
|
058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */,
|
||||||
058D0A0F195D050800B7D73C /* ASImageProtocols.h */,
|
058D0A0F195D050800B7D73C /* ASImageProtocols.h */,
|
||||||
|
053011A719B9882B00A9F2D0 /* ASRangeControllerInternal.h */,
|
||||||
058D0A10195D050800B7D73C /* ASSentinel.h */,
|
058D0A10195D050800B7D73C /* ASSentinel.h */,
|
||||||
058D0A11195D050800B7D73C /* ASSentinel.m */,
|
058D0A11195D050800B7D73C /* ASSentinel.m */,
|
||||||
);
|
);
|
||||||
@@ -437,6 +460,10 @@
|
|||||||
058D0A50195D05CB00B7D73C /* ASImageNode.mm in Headers */,
|
058D0A50195D05CB00B7D73C /* ASImageNode.mm in Headers */,
|
||||||
058D0A51195D05CB00B7D73C /* ASTextNode.h in Headers */,
|
058D0A51195D05CB00B7D73C /* ASTextNode.h in Headers */,
|
||||||
058D0A52195D05CB00B7D73C /* ASTextNode.mm in Headers */,
|
058D0A52195D05CB00B7D73C /* ASTextNode.mm in Headers */,
|
||||||
|
055F1A3819ABD413004DAFF1 /* ASRangeController.h in Headers */,
|
||||||
|
055F1A3419ABD3E3004DAFF1 /* ASTableView.h in Headers */,
|
||||||
|
0574D5E219C110940097DC25 /* ASTableViewProtocols.h in Headers */,
|
||||||
|
055F1A3C19ABD43F004DAFF1 /* ASCellNode.h in Headers */,
|
||||||
058D0A53195D05DC00B7D73C /* _ASDisplayLayer.h in Headers */,
|
058D0A53195D05DC00B7D73C /* _ASDisplayLayer.h in Headers */,
|
||||||
058D0A54195D05DC00B7D73C /* _ASDisplayLayer.mm in Headers */,
|
058D0A54195D05DC00B7D73C /* _ASDisplayLayer.mm in Headers */,
|
||||||
058D0A55195D05DC00B7D73C /* _ASDisplayView.h in Headers */,
|
058D0A55195D05DC00B7D73C /* _ASDisplayView.h in Headers */,
|
||||||
@@ -615,6 +642,7 @@
|
|||||||
058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */,
|
058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */,
|
||||||
058D0A15195D050800B7D73C /* ASDisplayNodeExtras.mm in Sources */,
|
058D0A15195D050800B7D73C /* ASDisplayNodeExtras.mm in Sources */,
|
||||||
058D0A1F195D050800B7D73C /* ASTextNodeTextKitHelpers.mm in Sources */,
|
058D0A1F195D050800B7D73C /* ASTextNodeTextKitHelpers.mm in Sources */,
|
||||||
|
055F1A3519ABD3E3004DAFF1 /* ASTableView.m in Sources */,
|
||||||
058D0A1D195D050800B7D73C /* ASTextNodeRenderer.mm in Sources */,
|
058D0A1D195D050800B7D73C /* ASTextNodeRenderer.mm in Sources */,
|
||||||
058D0A2A195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm in Sources */,
|
058D0A2A195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm in Sources */,
|
||||||
058D0A20195D050800B7D73C /* ASTextNodeWordKerner.m in Sources */,
|
058D0A20195D050800B7D73C /* ASTextNodeWordKerner.m in Sources */,
|
||||||
@@ -626,6 +654,7 @@
|
|||||||
058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */,
|
058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */,
|
||||||
058D0A2B195D050800B7D73C /* ASImageNode+CGExtras.m in Sources */,
|
058D0A2B195D050800B7D73C /* ASImageNode+CGExtras.m in Sources */,
|
||||||
058D0A24195D050800B7D73C /* _ASAsyncTransactionGroup.m in Sources */,
|
058D0A24195D050800B7D73C /* _ASAsyncTransactionGroup.m in Sources */,
|
||||||
|
055F1A3D19ABD43F004DAFF1 /* ASCellNode.m in Sources */,
|
||||||
058D0A1C195D050800B7D73C /* ASTextNodeCoreTextAdditions.m in Sources */,
|
058D0A1C195D050800B7D73C /* ASTextNodeCoreTextAdditions.m in Sources */,
|
||||||
058D0A13195D050800B7D73C /* ASControlNode.m in Sources */,
|
058D0A13195D050800B7D73C /* ASControlNode.m in Sources */,
|
||||||
058D0A19195D050800B7D73C /* _ASDisplayView.mm in Sources */,
|
058D0A19195D050800B7D73C /* _ASDisplayView.mm in Sources */,
|
||||||
@@ -634,6 +663,7 @@
|
|||||||
058D0A16195D050800B7D73C /* ASImageNode.mm in Sources */,
|
058D0A16195D050800B7D73C /* ASImageNode.mm in Sources */,
|
||||||
058D0A29195D050800B7D73C /* ASDisplayNode+DebugTiming.mm in Sources */,
|
058D0A29195D050800B7D73C /* ASDisplayNode+DebugTiming.mm in Sources */,
|
||||||
058D0A22195D050800B7D73C /* _ASAsyncTransaction.m in Sources */,
|
058D0A22195D050800B7D73C /* _ASAsyncTransaction.m in Sources */,
|
||||||
|
055F1A3919ABD413004DAFF1 /* ASRangeController.mm in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
22
AsyncDisplayKit/ASCellNode.h
Normal file
22
AsyncDisplayKit/ASCellNode.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/* Copyright (c) 2014-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||||
|
|
||||||
|
@interface ASCellNode : ASDisplayNode
|
||||||
|
|
||||||
|
// TODO expose some UITableViewCell things for configuration, eg, separator style
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@interface ASTextCellNode : ASCellNode
|
||||||
|
|
||||||
|
@property (nonatomic, copy) NSString *text;
|
||||||
|
|
||||||
|
@end
|
||||||
109
AsyncDisplayKit/ASCellNode.m
Normal file
109
AsyncDisplayKit/ASCellNode.m
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/* Copyright (c) 2014-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "ASCellNode.h"
|
||||||
|
|
||||||
|
#import "ASDisplayNode+Subclasses.h"
|
||||||
|
#import "ASRangeControllerInternal.h"
|
||||||
|
#import "ASTextNode.h"
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark ASCellNode
|
||||||
|
|
||||||
|
@interface ASCellNode () {
|
||||||
|
// used by ASRangeController machinery
|
||||||
|
NSIndexPath *_asyncdisplaykit_indexPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@implementation ASCellNode
|
||||||
|
|
||||||
|
- (instancetype)init
|
||||||
|
{
|
||||||
|
if (!(self = [super init]))
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO consider making this property an associated object in ASRangeController.mm
|
||||||
|
- (NSIndexPath *)asyncdisplaykit_indexPath
|
||||||
|
{
|
||||||
|
return _asyncdisplaykit_indexPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setAsyncdisplaykit_indexPath:(NSIndexPath *)asyncdisplaykit_indexPath
|
||||||
|
{
|
||||||
|
if (_asyncdisplaykit_indexPath == asyncdisplaykit_indexPath)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_asyncdisplaykit_indexPath = [asyncdisplaykit_indexPath copy];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark ASTextCellNode
|
||||||
|
|
||||||
|
@interface ASTextCellNode () {
|
||||||
|
NSString *_text;
|
||||||
|
ASTextNode *_textNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@implementation ASTextCellNode
|
||||||
|
|
||||||
|
static const CGFloat kHorizontalPadding = 15.0f;
|
||||||
|
static const CGFloat kVerticalPadding = 11.0f;
|
||||||
|
static const CGFloat kFontSize = 18.0f;
|
||||||
|
|
||||||
|
- (instancetype)init
|
||||||
|
{
|
||||||
|
if (!(self = [super init]))
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
_textNode = [[ASTextNode alloc] init];
|
||||||
|
[self addSubnode:_textNode];
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
|
||||||
|
{
|
||||||
|
CGSize availableSize = CGSizeMake(constrainedSize.width - 2 * kHorizontalPadding,
|
||||||
|
constrainedSize.height - 2 * kVerticalPadding);
|
||||||
|
CGSize textNodeSize = [_textNode measure:availableSize];
|
||||||
|
|
||||||
|
return CGSizeMake(ceilf(2 * kHorizontalPadding + textNodeSize.width),
|
||||||
|
ceilf(2 * kVerticalPadding + textNodeSize.height));
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)layout
|
||||||
|
{
|
||||||
|
_textNode.frame = CGRectInset(self.bounds, kHorizontalPadding, kVerticalPadding);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setText:(NSString *)text
|
||||||
|
{
|
||||||
|
if (_text == text)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_text = [text copy];
|
||||||
|
_textNode.attributedString = [[NSAttributedString alloc] initWithString:_text
|
||||||
|
attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kFontSize]}];
|
||||||
|
|
||||||
|
[self invalidateCalculatedSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -8,90 +8,268 @@
|
|||||||
|
|
||||||
#import <pthread.h>
|
#import <pthread.h>
|
||||||
|
|
||||||
|
#import <AsyncDisplayKit/_ASDisplayLayer.h>
|
||||||
#import <AsyncDisplayKit/ASAssert.h>
|
#import <AsyncDisplayKit/ASAssert.h>
|
||||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||||
#import <AsyncDisplayKit/ASThread.h>
|
#import <AsyncDisplayKit/ASThread.h>
|
||||||
|
|
||||||
//
|
|
||||||
// The following methods either must or can be overriden by subclasses of ASDisplayNode.
|
/**
|
||||||
// These methods should never be called directly by other classes.
|
* The subclass header _ASDisplayNode+Subclasses_ defines the following methods that either must or can be overriden by
|
||||||
//
|
* subclasses of ASDisplayNode.
|
||||||
|
*
|
||||||
|
* These methods should never be called directly by other classes.
|
||||||
|
*
|
||||||
|
* ## Drawing
|
||||||
|
*
|
||||||
|
* Implement one of +displayAsyncLayer:parameters:isCancelled: or +drawRect:withParameters:isCancelled: to provide
|
||||||
|
* drawing for your node.
|
||||||
|
*
|
||||||
|
* Use -drawParametersForAsyncLayer: to copy any properties that are involved in drawing into an immutable object for
|
||||||
|
* use on the display queue. The display and drawRect implementations *MUST* be thread-safe, as they can be called on
|
||||||
|
* the displayQueue (asynchronously) or the main thread (synchronously/displayImmediately).
|
||||||
|
*
|
||||||
|
* Class methods that require passing in copies of the values are used to minimize the need for locking around instance
|
||||||
|
* variable access, and the possibility of the asynchronous display pass grabbing an inconsistent state across multiple
|
||||||
|
* variables.
|
||||||
|
*/
|
||||||
|
|
||||||
@interface ASDisplayNode (ASDisplayNodeSubclasses)
|
@interface ASDisplayNode (ASDisplayNodeSubclasses)
|
||||||
|
|
||||||
// the view class to use when creating a new display node instance. Defaults to _ASDisplayView.
|
|
||||||
+ (Class)viewClass;
|
|
||||||
|
|
||||||
// Returns YES if a cache node, defaults to NO
|
/** @name View Configuration */
|
||||||
@property (nonatomic, assign, readonly, getter=isCacheNode) BOOL cacheNode;
|
|
||||||
|
|
||||||
// Returns array of cached strict descendants (excludes self). if this is not a cacheNode, returns nil
|
|
||||||
@property (nonatomic, copy, readonly) NSArray *cachedNodes;
|
|
||||||
|
|
||||||
// Returns the parent cache node, if any. node caching must be enabled
|
|
||||||
@property (nonatomic, assign, readonly) ASDisplayNode *superCacheNode;
|
|
||||||
|
|
||||||
// Called on the main thread immediately after self.view is created. Best time to add gesture recognizers to the view.
|
|
||||||
- (void)didLoad;
|
|
||||||
|
|
||||||
// Called on the main thread by the view's -layoutSubviews. Layout all subnodes or subviews in this method.
|
|
||||||
- (void)layout;
|
|
||||||
|
|
||||||
// Called on the main thread by the view's -layoutSubviews, after -layout. Gives a chance for subclasses to perform actions after the subclass and superclass have finished laying out.
|
|
||||||
- (void)layoutDidFinish;
|
|
||||||
|
|
||||||
// Subclasses that override should expect this method to be called on a non-main thread. The returned size is cached by
|
|
||||||
// ASDisplayNode for quick access during -layout, via -calculatedSize. Other expensive work that needs to be done
|
|
||||||
// before display can be performed here, and using ivars to cache any valuable intermediate results is encouraged. This
|
|
||||||
// method should not be called directly outside of ASDisplayNode; use -sizeToFit: or -calculatedSize instead.
|
|
||||||
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize;
|
|
||||||
|
|
||||||
// Subclasses should call this method to invalidate the previously measured and cached size for the display node, when the contents of
|
|
||||||
// the node change in such a way as to require measuring it again.
|
|
||||||
- (void)invalidateCalculatedSize;
|
|
||||||
|
|
||||||
// Subclasses should implement -display if the layer's contents will be set directly to an arbitrary buffer (e.g. decoded JPEG).
|
|
||||||
// Called on a background thread, some time after the view has been created. This method is called if -drawInContext: is not implemented.
|
|
||||||
- (void)display;
|
|
||||||
|
|
||||||
// Subclasses should implement if a backing store / context is desired. Called on a background thread, some time after the view has been created.
|
|
||||||
- (void)drawInContext:(CGContextRef)ctx;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@abstract Indicates that the receiver has finished displaying.
|
* @return The view class to use when creating a new display node instance. Defaults to _ASDisplayView.
|
||||||
@discussion Subclasses may override this method to be notified when display (asynchronous or synchronous) has completed.
|
*/
|
||||||
|
+ (Class)viewClass;
|
||||||
|
|
||||||
|
|
||||||
|
/** @name Properties */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract The scale factor to apply to the rendering.
|
||||||
|
*
|
||||||
|
* @discussion Use setNeedsDisplayAtScale: to set a value and then after display, the display node will set the layer's
|
||||||
|
* contentsScale. This is to prevent jumps when re-rasterizing at a different contentsScale.
|
||||||
|
* Read this property if you need to know the future contentsScale of your layer, eg in drawParameters.
|
||||||
|
*
|
||||||
|
* @see setNeedsDisplayAtScale:
|
||||||
|
*/
|
||||||
|
@property (nonatomic, assign, readonly) CGFloat contentsScaleForDisplay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Whether the view or layer of this display node is currently in a window
|
||||||
|
*/
|
||||||
|
@property (nonatomic, readonly, assign, getter=isInWindow) BOOL inWindow;
|
||||||
|
|
||||||
|
|
||||||
|
/** @name View Lifecycle */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Called on the main thread immediately after self.view is created.
|
||||||
|
*
|
||||||
|
* @discussion This is the best time to add gesture recognizers to the view.
|
||||||
|
*/
|
||||||
|
- (void)didLoad;
|
||||||
|
|
||||||
|
|
||||||
|
/** @name Layout */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Called on the main thread by the view's -layoutSubviews.
|
||||||
|
*
|
||||||
|
* @discussion Subclasses override this method to layout all subnodes or subviews.
|
||||||
|
*/
|
||||||
|
- (void)layout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Called on the main thread by the view's -layoutSubviews, after -layout.
|
||||||
|
*
|
||||||
|
* @discussion Gives a chance for subclasses to perform actions after the subclass and superclass have finished laying
|
||||||
|
* out.
|
||||||
|
*/
|
||||||
|
- (void)layoutDidFinish;
|
||||||
|
|
||||||
|
|
||||||
|
/** @name Sizing */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Return the calculated size.
|
||||||
|
*
|
||||||
|
* @discussion Subclasses that override should expect this method to be called on a non-main thread. The returned size
|
||||||
|
* is cached by ASDisplayNode for quick access during -layout, via -calculatedSize. Other expensive work that needs to
|
||||||
|
* be done before display can be performed here, and using ivars to cache any valuable intermediate results is
|
||||||
|
* encouraged.
|
||||||
|
*
|
||||||
|
* @note This method should not be called directly outside of ASDisplayNode; use -measure: or -calculatedSize instead.
|
||||||
|
*/
|
||||||
|
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Invalidate previously measured and cached size.
|
||||||
|
*
|
||||||
|
* @discussion Subclasses should call this method to invalidate the previously measured and cached size for the display
|
||||||
|
* node, when the contents of the node change in such a way as to require measuring it again.
|
||||||
|
*/
|
||||||
|
- (void)invalidateCalculatedSize;
|
||||||
|
|
||||||
|
|
||||||
|
/** @name Drawing */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Delegate method to draw layer contents into a CGBitmapContext. The current UIGraphics context will be set
|
||||||
|
* to an appropriate context.
|
||||||
|
*
|
||||||
|
* @param parameters An object describing all of the properties you need to draw. Return this from
|
||||||
|
* -drawParametersForAsyncLayer:
|
||||||
|
* @param isCancelled Execute this block to check whether the current drawing operation has been cancelled to avoid
|
||||||
|
* unnecessary work. A return value of YES means cancel drawing and return.
|
||||||
|
* @param isRasterizing YES if the layer is being rasterized into another layer, in which case drawRect: probably wants
|
||||||
|
* to avoid doing things like filling its bounds with a zero-alpha color to clear the backing store.
|
||||||
|
*
|
||||||
|
* @note Called on the display queue and/or main queue (MUST BE THREAD SAFE)
|
||||||
|
*/
|
||||||
|
+ (void)drawRect:(CGRect)bounds
|
||||||
|
withParameters:(id<NSObject>)parameters
|
||||||
|
isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock
|
||||||
|
isRasterizing:(BOOL)isRasterizing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Delegate override to provide new layer contents as a UIImage.
|
||||||
|
*
|
||||||
|
* @param parameters An object describing all of the properties you need to draw. Return this from
|
||||||
|
* -drawParametersForAsyncLayer:
|
||||||
|
* @param isCancelled Execute this block to check whether the current drawing operation has been cancelled to avoid
|
||||||
|
* unnecessary work. A return value of YES means cancel drawing and return.
|
||||||
|
*
|
||||||
|
* @return A UIImage with contents that are ready to display on the main thread. Make sure that the image is already
|
||||||
|
* decoded before returning it here.
|
||||||
|
*
|
||||||
|
* @note Called on the display queue and/or main queue (MUST BE THREAD SAFE)
|
||||||
|
*/
|
||||||
|
+ (UIImage *)displayWithParameters:(id<NSObject>)parameters
|
||||||
|
isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Delegate override for drawParameters
|
||||||
|
*
|
||||||
|
* @note Called on the main thread only
|
||||||
|
*/
|
||||||
|
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Indicates that the receiver has finished displaying.
|
||||||
|
*
|
||||||
|
* @discussion Subclasses may override this method to be notified when display (asynchronous or synchronous) has
|
||||||
|
* completed.
|
||||||
*/
|
*/
|
||||||
- (void)displayDidFinish;
|
- (void)displayDidFinish;
|
||||||
|
|
||||||
- (void)asyncdisplaykit_asyncTransactionContainerStateDidChange;
|
|
||||||
|
|
||||||
// Subclasses may optionally implement the touch handling methods.
|
/**
|
||||||
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
|
* @abstract Marks the receiver's bounds as needing to be redrawn, with a scale value.
|
||||||
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
|
*
|
||||||
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
|
* @discussion Subclasses should override this if they don't want their contentsScale changed.
|
||||||
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
|
*
|
||||||
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
|
* @note This changes an internal property.
|
||||||
|
* @see contentsScaleForDisplay
|
||||||
// Override to make this node respond differently to touches: hide touches from subviews, send all touches to certain subviews (hit area maximizing), etc.
|
*/
|
||||||
// Returns a UIView, not ASDisplayNode, for two reasons:
|
|
||||||
// 1) allows sending events to plain UIViews that don't have attached nodes, 2) hitTest: is never called before the views are created.
|
|
||||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
|
|
||||||
|
|
||||||
// Subclasses should override this if they don't want their contentsScale changed. This changes an internal property
|
|
||||||
- (void)setNeedsDisplayAtScale:(CGFloat)contentsScale;
|
- (void)setNeedsDisplayAtScale:(CGFloat)contentsScale;
|
||||||
|
|
||||||
// Recursively calls setNeedsDisplayAtScale: on subnodes. Note that only the node tree is walked, not the view or layer trees.
|
/**
|
||||||
// Subclasses may override this if they require modifying the scale set on their child nodes.
|
* @abstract Recursively calls setNeedsDisplayAtScale: on subnodes.
|
||||||
|
*
|
||||||
|
* @discussion Subclasses may override this if they require modifying the scale set on their child nodes.
|
||||||
|
*
|
||||||
|
* @note Only the node tree is walked, not the view or layer trees.
|
||||||
|
*
|
||||||
|
* @see setNeedsDisplayAtScale:
|
||||||
|
* @see contentsScaleForDisplay
|
||||||
|
*/
|
||||||
- (void)recursivelySetNeedsDisplayAtScale:(CGFloat)contentsScale;
|
- (void)recursivelySetNeedsDisplayAtScale:(CGFloat)contentsScale;
|
||||||
|
|
||||||
// Use setNeedsDisplayAtScale: and then after display, the display node will set the layer's contentsScale. This is to prevent jumps when re-rasterizing at a different contentsScale.
|
|
||||||
// Read this property if you need to know the future contentsScale of your layer, eg in drawParameters
|
|
||||||
@property (nonatomic, assign, readonly) CGFloat contentsScaleForDisplay;
|
|
||||||
|
|
||||||
// Whether the view or layer of this display node is currently in a window
|
/** @name Touch handling */
|
||||||
@property (nonatomic, readonly, assign, getter=isInWindow) BOOL inWindow;
|
|
||||||
|
|
||||||
// The function that gets called for each display node in -recursiveDescription
|
|
||||||
|
/**
|
||||||
|
* @abstract Tells the node when touches began in its view.
|
||||||
|
*
|
||||||
|
* @param touches A set of UITouch instances.
|
||||||
|
* @param event A UIEvent associated with the touch.
|
||||||
|
*/
|
||||||
|
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Tells the node when touches moved in its view.
|
||||||
|
*
|
||||||
|
* @param touches A set of UITouch instances.
|
||||||
|
* @param event A UIEvent associated with the touch.
|
||||||
|
*/
|
||||||
|
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Tells the node when touches ended in its view.
|
||||||
|
*
|
||||||
|
* @param touches A set of UITouch instances.
|
||||||
|
* @param event A UIEvent associated with the touch.
|
||||||
|
*/
|
||||||
|
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Tells the node when touches was cancelled in its view.
|
||||||
|
*
|
||||||
|
* @param touches A set of UITouch instances.
|
||||||
|
* @param event A UIEvent associated with the touch.
|
||||||
|
*/
|
||||||
|
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
|
||||||
|
|
||||||
|
|
||||||
|
/** @name Managing Gesture Recognizers */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Asks the node if a gesture recognizer should continue tracking touches.
|
||||||
|
*
|
||||||
|
* @param gestureRecognizer A gesture recognizer trying to recognize a gesture.
|
||||||
|
*/
|
||||||
|
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
|
||||||
|
|
||||||
|
|
||||||
|
/** @name Hit Testing */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Returns the view that contains the point.
|
||||||
|
*
|
||||||
|
* @discussion Override to make this node respond differently to touches: (e.g. hide touches from subviews, send all
|
||||||
|
* touches to certain subviews (hit area maximizing), etc.)
|
||||||
|
*
|
||||||
|
* @param point A point specified in the node's local coordinate system (bounds).
|
||||||
|
* @param event The event that warranted a call to this method.
|
||||||
|
*
|
||||||
|
* @return Returns a UIView, not ASDisplayNode, for two reasons:
|
||||||
|
* 1) allows sending events to plain UIViews that don't have attached nodes,
|
||||||
|
* 2) hitTest: is never called before the views are created.
|
||||||
|
*/
|
||||||
|
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
|
||||||
|
|
||||||
|
|
||||||
|
/** Description */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @abstract Return a description of the node
|
||||||
|
*
|
||||||
|
* @discussion The function that gets called for each display node in -recursiveDescription
|
||||||
|
*/
|
||||||
- (NSString *)descriptionForRecursiveDescription;
|
- (NSString *)descriptionForRecursiveDescription;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -102,5 +280,5 @@
|
|||||||
- (ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass;
|
- (ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
#define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).isViewLoaded, @"Incorrect display node thread affinity")
|
#define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity")
|
||||||
#define ASDisplayNodeCAssertThreadAffinity(viewNode) ASDisplayNodeCAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).isViewLoaded, @"Incorrect display node thread affinity")
|
#define ASDisplayNodeCAssertThreadAffinity(viewNode) ASDisplayNodeCAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity")
|
||||||
|
|||||||
@@ -16,9 +16,7 @@
|
|||||||
* An `ASDisplayNode` is an abstraction over `UIView` and `CALayer` that allows you to perform calculations about a view
|
* An `ASDisplayNode` is an abstraction over `UIView` and `CALayer` that allows you to perform calculations about a view
|
||||||
* hierarchy off the main thread, and could do rendering off the main thread as well.
|
* hierarchy off the main thread, and could do rendering off the main thread as well.
|
||||||
*
|
*
|
||||||
* The node API is designed to be as similar as possible to `UIView`.
|
* The node API is designed to be as similar as possible to `UIView`. See the README for examples.
|
||||||
*
|
|
||||||
* TODO add more details + example
|
|
||||||
*
|
*
|
||||||
* ## Subclassing
|
* ## Subclassing
|
||||||
*
|
*
|
||||||
@@ -97,11 +95,11 @@
|
|||||||
@property (nonatomic, readonly, retain) UIView *view;
|
@property (nonatomic, readonly, retain) UIView *view;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract Returns whether a view is loaded.
|
* @abstract Returns whether a node's backing view or layer is loaded.
|
||||||
*
|
*
|
||||||
* @return YES if a view is loaded, or if isLayerBacked is YES and layer is not nil; NO otherwise.
|
* @return YES if a view is loaded, or if isLayerBacked is YES and layer is not nil; NO otherwise.
|
||||||
*/
|
*/
|
||||||
@property (atomic, readonly, assign, getter=isViewLoaded) BOOL viewLoaded; //TODO Rename to isBackingLoaded?
|
@property (atomic, readonly, assign, getter=isNodeLoaded) BOOL nodeLoaded;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract Returns whether the node rely on a layer instead of a view.
|
* @abstract Returns whether the node rely on a layer instead of a view.
|
||||||
@@ -140,13 +138,13 @@
|
|||||||
*
|
*
|
||||||
* @see calculateSizeThatFits:
|
* @see calculateSizeThatFits:
|
||||||
*/
|
*/
|
||||||
- (CGSize)sizeToFit:(CGSize)constrainedSize; //TODO UIView names it sizeThatFits ("that" instead of "to")
|
- (CGSize)measure:(CGSize)constrainedSize;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract Return the calculated size.
|
* @abstract Return the calculated size.
|
||||||
*
|
*
|
||||||
* @discussion Ideal for use by subclasses in -layout, having already prompted their subnodes to calculate their size by
|
* @discussion Ideal for use by subclasses in -layout, having already prompted their subnodes to calculate their size by
|
||||||
* calling -sizeToFit: on them in -calculateSizeThatFits:.
|
* calling -measure: on them in -calculateSizeThatFits:.
|
||||||
*
|
*
|
||||||
* @return Size already calculated by calculateSizeThatFits:.
|
* @return Size already calculated by calculateSizeThatFits:.
|
||||||
*
|
*
|
||||||
@@ -159,7 +157,7 @@
|
|||||||
*
|
*
|
||||||
* @return The constrained size used by calculateSizeThatFits:.
|
* @return The constrained size used by calculateSizeThatFits:.
|
||||||
*/
|
*/
|
||||||
@property (nonatomic, readonly, assign) CGSize constrainedSizeForCalulatedSize;
|
@property (nonatomic, readonly, assign) CGSize constrainedSizeForCalculatedSize;
|
||||||
|
|
||||||
|
|
||||||
/** @name Managing the nodes hierarchy */
|
/** @name Managing the nodes hierarchy */
|
||||||
@@ -269,16 +267,11 @@
|
|||||||
/** @name Observing node-related changes */
|
/** @name Observing node-related changes */
|
||||||
|
|
||||||
|
|
||||||
// TODO rename these to the UIView selectors, willMoveToSuperview etc
|
|
||||||
|
|
||||||
// Called just before the view is added to a superview.
|
// Called just before the view is added to a superview.
|
||||||
- (void)willAppear;
|
- (void)willEnterHierarchy;
|
||||||
|
|
||||||
// Called after the view is removed from the window
|
// Called after the view is removed from the window.
|
||||||
- (void)willDisappear;
|
- (void)didExitHierarchy;
|
||||||
|
|
||||||
// Called after the view is removed from the window
|
|
||||||
- (void)didDisappear;
|
|
||||||
|
|
||||||
|
|
||||||
/** @name Drawing and Updating the View */
|
/** @name Drawing and Updating the View */
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector)
|
|||||||
|
|
||||||
// Subclasses should never override these
|
// Subclasses should never override these
|
||||||
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedSize)), @"Subclass %@ must not override calculatedSize method", NSStringFromClass(self));
|
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedSize)), @"Subclass %@ must not override calculatedSize method", NSStringFromClass(self));
|
||||||
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(sizeToFit:)), @"Subclass %@ must not override sizeToFit method", NSStringFromClass(self));
|
ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(measure:)), @"Subclass %@ must not override measure method", NSStringFromClass(self));
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (BOOL)layerBackedNodesEnabled
|
+ (BOOL)layerBackedNodesEnabled
|
||||||
@@ -291,7 +291,7 @@ _OBJC_SUPPORTED_INLINE_REFCNT_WITH_DEALLOC2MAIN(_retainCount);
|
|||||||
return [_layer isKindOfClass:[_ASDisplayLayer class]] ? (_ASDisplayLayer *)_layer : nil;
|
return [_layer isKindOfClass:[_ASDisplayLayer class]] ? (_ASDisplayLayer *)_layer : nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isViewLoaded
|
- (BOOL)isNodeLoaded
|
||||||
{
|
{
|
||||||
ASDN::MutexLocker l(_propertyLock);
|
ASDN::MutexLocker l(_propertyLock);
|
||||||
return (_view != nil || (_flags.isLayerBacked && _layer != nil));
|
return (_view != nil || (_flags.isLayerBacked && _layer != nil));
|
||||||
@@ -326,7 +326,7 @@ _OBJC_SUPPORTED_INLINE_REFCNT_WITH_DEALLOC2MAIN(_retainCount);
|
|||||||
|
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
|
|
||||||
- (CGSize)sizeToFit:(CGSize)constrainedSize
|
- (CGSize)measure:(CGSize)constrainedSize
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssertThreadAffinity(self);
|
ASDisplayNodeAssertThreadAffinity(self);
|
||||||
|
|
||||||
@@ -429,7 +429,7 @@ _OBJC_SUPPORTED_INLINE_REFCNT_WITH_DEALLOC2MAIN(_retainCount);
|
|||||||
ASDisplayNodeAssertMainThread();
|
ASDisplayNodeAssertMainThread();
|
||||||
ASDN::MutexLocker l(_propertyLock);
|
ASDN::MutexLocker l(_propertyLock);
|
||||||
if (CGRectEqualToRect(_layer.bounds, CGRectZero))
|
if (CGRectEqualToRect(_layer.bounds, CGRectZero))
|
||||||
return; // Performing layout on a zero-bounds view often results in frame calculations with negative sizes after applying margins, which will cause sizeToFit: on subnodes to assert.
|
return; // Performing layout on a zero-bounds view often results in frame calculations with negative sizes after applying margins, which will cause measure: on subnodes to assert.
|
||||||
[self layout];
|
[self layout];
|
||||||
[self layoutDidFinish];
|
[self layoutDidFinish];
|
||||||
}
|
}
|
||||||
@@ -634,7 +634,7 @@ static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASD
|
|||||||
|
|
||||||
[_subnodes addObject:subnode];
|
[_subnodes addObject:subnode];
|
||||||
|
|
||||||
if (self.isViewLoaded) {
|
if (self.nodeLoaded) {
|
||||||
// If this node has a view or layer, force the subnode to also create its view or layer and add it to the hierarchy here.
|
// If this node has a view or layer, force the subnode to also create its view or layer and add it to the hierarchy here.
|
||||||
// Otherwise there is no way for the subnode's view or layer to enter the hierarchy, except recursing down all
|
// Otherwise there is no way for the subnode's view or layer to enter the hierarchy, except recursing down all
|
||||||
// subnodes on the main thread after the node tree has been created but before the first display (which
|
// subnodes on the main thread after the node tree has been created but before the first display (which
|
||||||
@@ -728,7 +728,7 @@ static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASD
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ASDisplayNodeAssert(!(self.isViewLoaded && !oldSubnode.isViewLoaded), @"ASDisplayNode corruption bug. We have view loaded, but child node does not.");
|
ASDisplayNodeAssert(!(self.nodeLoaded && !oldSubnode.nodeLoaded), @"ASDisplayNode corruption bug. We have view loaded, but child node does not.");
|
||||||
ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode");
|
ASDisplayNodeAssert(_subnodes, @"You should have subnodes if you have a subnode");
|
||||||
|
|
||||||
NSInteger subnodeIndex = [_subnodes indexOfObjectIdenticalTo:oldSubnode];
|
NSInteger subnodeIndex = [_subnodes indexOfObjectIdenticalTo:oldSubnode];
|
||||||
@@ -866,7 +866,7 @@ static NSInteger incrementIfFound(NSInteger i) {
|
|||||||
- (void)_addSubnodeSubviewOrSublayer:(ASDisplayNode *)subnode
|
- (void)_addSubnodeSubviewOrSublayer:(ASDisplayNode *)subnode
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssertMainThread();
|
ASDisplayNodeAssertMainThread();
|
||||||
ASDisplayNodeAssert(self.isViewLoaded, @"_addSubnodeSubview: should never be called before our own view is created");
|
ASDisplayNodeAssert(self.nodeLoaded, @"_addSubnodeSubview: should never be called before our own view is created");
|
||||||
|
|
||||||
BOOL canUseViewAPI = !self.isLayerBacked && !subnode.isLayerBacked;
|
BOOL canUseViewAPI = !self.isLayerBacked && !subnode.isLayerBacked;
|
||||||
if (canUseViewAPI) {
|
if (canUseViewAPI) {
|
||||||
@@ -978,9 +978,9 @@ static NSInteger incrementIfFound(NSInteger i) {
|
|||||||
_flags.isInAppear = YES;
|
_flags.isInAppear = YES;
|
||||||
if (self.shouldRasterizeDescendants) {
|
if (self.shouldRasterizeDescendants) {
|
||||||
// Nodes that are descendants of a rasterized container do not have views or layers, and so cannot receive visibility notifications directly via orderIn/orderOut CALayer actions. Manually send visibility notifications to rasterized descendants.
|
// Nodes that are descendants of a rasterized container do not have views or layers, and so cannot receive visibility notifications directly via orderIn/orderOut CALayer actions. Manually send visibility notifications to rasterized descendants.
|
||||||
[self _recursiveWillAppear];
|
[self _recursiveWillEnterHierarchy];
|
||||||
} else {
|
} else {
|
||||||
[self willAppear];
|
[self willEnterHierarchy];
|
||||||
}
|
}
|
||||||
_flags.isInAppear = NO;
|
_flags.isInAppear = NO;
|
||||||
}
|
}
|
||||||
@@ -998,64 +998,41 @@ static NSInteger incrementIfFound(NSInteger i) {
|
|||||||
_flags.isInDisappear = YES;
|
_flags.isInDisappear = YES;
|
||||||
if (self.shouldRasterizeDescendants) {
|
if (self.shouldRasterizeDescendants) {
|
||||||
// Nodes that are descendants of a rasterized container do not have views or layers, and so cannot receive visibility notifications directly via orderIn/orderOut CALayer actions. Manually send visibility notifications to rasterized descendants.
|
// Nodes that are descendants of a rasterized container do not have views or layers, and so cannot receive visibility notifications directly via orderIn/orderOut CALayer actions. Manually send visibility notifications to rasterized descendants.
|
||||||
[self _recursiveWillDisappear];
|
[self _recursiveDidExitHierarchy];
|
||||||
} else {
|
} else {
|
||||||
[self willDisappear];
|
[self didExitHierarchy];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.shouldRasterizeDescendants) {
|
|
||||||
// Nodes that are descendants of a rasterized container do not have views or layers, and so cannot receive visibility notifications directly via orderIn/orderOut CALayer actions. Manually send visibility notifications to rasterized descendants.
|
|
||||||
[self _recursiveDidDisappear];
|
|
||||||
} else {
|
|
||||||
[self didDisappear];
|
|
||||||
}
|
|
||||||
|
|
||||||
_flags.isInDisappear = NO;
|
_flags.isInDisappear = NO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)_recursiveWillAppear
|
- (void)_recursiveWillEnterHierarchy
|
||||||
{
|
{
|
||||||
if (_flags.visibilityNotificationsDisabled) {
|
if (_flags.visibilityNotificationsDisabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_flags.isInAppear = YES;
|
_flags.isInAppear = YES;
|
||||||
[self willAppear];
|
[self willEnterHierarchy];
|
||||||
_flags.isInAppear = NO;
|
_flags.isInAppear = NO;
|
||||||
|
|
||||||
for (ASDisplayNode *subnode in self.subnodes) {
|
for (ASDisplayNode *subnode in self.subnodes) {
|
||||||
[subnode _recursiveWillAppear];
|
[subnode _recursiveWillEnterHierarchy];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)_recursiveWillDisappear
|
- (void)_recursiveDidExitHierarchy
|
||||||
{
|
{
|
||||||
if (_flags.visibilityNotificationsDisabled) {
|
if (_flags.visibilityNotificationsDisabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_flags.isInDisappear = YES;
|
_flags.isInDisappear = YES;
|
||||||
[self willDisappear];
|
[self didExitHierarchy];
|
||||||
_flags.isInDisappear = NO;
|
_flags.isInDisappear = NO;
|
||||||
|
|
||||||
for (ASDisplayNode *subnode in self.subnodes) {
|
for (ASDisplayNode *subnode in self.subnodes) {
|
||||||
[subnode _recursiveWillDisappear];
|
[subnode _recursiveDidExitHierarchy];
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)_recursiveDidDisappear
|
|
||||||
{
|
|
||||||
if (_flags.visibilityNotificationsDisabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_flags.isInDisappear = YES;
|
|
||||||
[self didDisappear];
|
|
||||||
_flags.isInDisappear = NO;
|
|
||||||
|
|
||||||
for (ASDisplayNode *subnode in self.subnodes) {
|
|
||||||
[subnode _recursiveDidDisappear];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1100,7 +1077,7 @@ static NSInteger incrementIfFound(NSInteger i) {
|
|||||||
return _size;
|
return _size;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CGSize)constrainedSizeForCalulatedSize
|
- (CGSize)constrainedSizeForCalculatedSize
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssertThreadAffinity(self);
|
ASDisplayNodeAssertThreadAffinity(self);
|
||||||
return _constrainedSize;
|
return _constrainedSize;
|
||||||
@@ -1109,7 +1086,7 @@ static NSInteger incrementIfFound(NSInteger i) {
|
|||||||
- (void)invalidateCalculatedSize
|
- (void)invalidateCalculatedSize
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssertThreadAffinity(self);
|
ASDisplayNodeAssertThreadAffinity(self);
|
||||||
// This will cause -sizeToFit: to actually compute the size instead of returning the previously cached size
|
// This will cause -measure: to actually compute the size instead of returning the previously cached size
|
||||||
_flags.sizeCalculated = NO;
|
_flags.sizeCalculated = NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1118,24 +1095,17 @@ static NSInteger incrementIfFound(NSInteger i) {
|
|||||||
ASDisplayNodeAssertMainThread();
|
ASDisplayNodeAssertMainThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)willAppear
|
- (void)willEnterHierarchy
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssertMainThread();
|
ASDisplayNodeAssertMainThread();
|
||||||
ASDisplayNodeAssert(_flags.isInAppear, @"You should never call -willAppear directly. Appearance is automatically managed by ASDisplayNode");
|
ASDisplayNodeAssert(_flags.isInAppear, @"You should never call -willEnterHierarchy directly. Appearance is automatically managed by ASDisplayNode");
|
||||||
ASDisplayNodeAssert(!_flags.isInDisappear, @"ASDisplayNode inconsistency. __appear and __disappear are mutually exclusive");
|
ASDisplayNodeAssert(!_flags.isInDisappear, @"ASDisplayNode inconsistency. __appear and __disappear are mutually exclusive");
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)willDisappear
|
- (void)didExitHierarchy
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssertMainThread();
|
ASDisplayNodeAssertMainThread();
|
||||||
ASDisplayNodeAssert(_flags.isInDisappear, @"You should never call -willDisappear directly. Appearance is automatically managed by ASDisplayNode");
|
ASDisplayNodeAssert(_flags.isInDisappear, @"You should never call -didExitHierarchy directly. Appearance is automatically managed by ASDisplayNode");
|
||||||
ASDisplayNodeAssert(!_flags.isInAppear, @"ASDisplayNode inconsistency. __appear and __disappear are mutually exclusive");
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)didDisappear
|
|
||||||
{
|
|
||||||
ASDisplayNodeAssertMainThread();
|
|
||||||
ASDisplayNodeAssert(_flags.isInDisappear, @"You should never call -didDisappear directly. Appearance is automatically managed by ASDisplayNode");
|
|
||||||
ASDisplayNodeAssert(!_flags.isInAppear, @"ASDisplayNode inconsistency. __appear and __disappear are mutually exclusive");
|
ASDisplayNodeAssert(!_flags.isInAppear, @"ASDisplayNode inconsistency. __appear and __disappear are mutually exclusive");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1273,7 +1243,7 @@ static NSInteger incrementIfFound(NSInteger i) {
|
|||||||
- (void)_applyPendingStateToViewOrLayer
|
- (void)_applyPendingStateToViewOrLayer
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssertMainThread();
|
ASDisplayNodeAssertMainThread();
|
||||||
ASDisplayNodeAssert(self.isViewLoaded, @"must have a view or layer");
|
ASDisplayNodeAssert(self.nodeLoaded, @"must have a view or layer");
|
||||||
|
|
||||||
// If no view/layer properties were set before the view/layer were created, _pendingViewState will be nil and the default values
|
// If no view/layer properties were set before the view/layer were created, _pendingViewState will be nil and the default values
|
||||||
// for the view/layer are still valid.
|
// for the view/layer are still valid.
|
||||||
@@ -1330,7 +1300,7 @@ static NSInteger incrementIfFound(NSInteger i) {
|
|||||||
static void _recursiveSetPreventOrCancelDisplay(ASDisplayNode *node, CALayer *layer, BOOL flag)
|
static void _recursiveSetPreventOrCancelDisplay(ASDisplayNode *node, CALayer *layer, BOOL flag)
|
||||||
{
|
{
|
||||||
// If there is no layer, but node whose its view is loaded, then we can traverse down its layer hierarchy. Otherwise we must stick to the node hierarchy to avoid loading views prematurely. Note that for nodes that haven't loaded their views, they can't possibly have subviews/sublayers, so we don't need to traverse the layer hierarchy for them.
|
// If there is no layer, but node whose its view is loaded, then we can traverse down its layer hierarchy. Otherwise we must stick to the node hierarchy to avoid loading views prematurely. Note that for nodes that haven't loaded their views, they can't possibly have subviews/sublayers, so we don't need to traverse the layer hierarchy for them.
|
||||||
if (!layer && node && node.isViewLoaded) {
|
if (!layer && node && node.nodeLoaded) {
|
||||||
layer = node.layer;
|
layer = node.layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1439,7 +1409,7 @@ static void _recursiveSetPreventOrCancelDisplay(ASDisplayNode *node, CALayer *la
|
|||||||
if (sentinel.value != sentinelValue)
|
if (sentinel.value != sentinelValue)
|
||||||
return dispatch_async(dispatch_get_main_queue(), ^{ completion(nil); });
|
return dispatch_async(dispatch_get_main_queue(), ^{ completion(nil); });
|
||||||
|
|
||||||
[self sizeToFit:bounds.size];
|
[self measure:bounds.size];
|
||||||
|
|
||||||
// Check sentinel after, bail early
|
// Check sentinel after, bail early
|
||||||
if (sentinel.value != sentinelValue)
|
if (sentinel.value != sentinelValue)
|
||||||
@@ -1457,7 +1427,7 @@ static void _recursiveSetPreventOrCancelDisplay(ASDisplayNode *node, CALayer *la
|
|||||||
{
|
{
|
||||||
|
|
||||||
ASDisplayNodeAssert(old.supernode == self, @"Must replace something that is actually a subnode. You passed: %@", old);
|
ASDisplayNodeAssert(old.supernode == self, @"Must replace something that is actually a subnode. You passed: %@", old);
|
||||||
ASDisplayNodeAssert(!replacement.isViewLoaded, @"Can't async size something that already has a view, since we currently have no way to convert a viewed node into a viewless one...");
|
ASDisplayNodeAssert(!replacement.nodeLoaded, @"Can't async size something that already has a view, since we currently have no way to convert a viewed node into a viewless one...");
|
||||||
|
|
||||||
// If we're already marked for replacement, cancel the pending request
|
// If we're already marked for replacement, cancel the pending request
|
||||||
ASSentinel *sentinel = [old _asyncReplaceSentinel];
|
ASSentinel *sentinel = [old _asyncReplaceSentinel];
|
||||||
@@ -1480,7 +1450,7 @@ static void _recursiveSetPreventOrCancelDisplay(ASDisplayNode *node, CALayer *la
|
|||||||
replacementCompletedNode.alpha = 0.0;
|
replacementCompletedNode.alpha = 0.0;
|
||||||
replacementCompletedNode.asyncdisplaykit_asyncTransactionContainer = YES;
|
replacementCompletedNode.asyncdisplaykit_asyncTransactionContainer = YES;
|
||||||
|
|
||||||
ASDisplayNodeCAssert(replacementCompletedNode.isViewLoaded, @".layer shouldn't be the thing to load the view");
|
ASDisplayNodeCAssert(replacementCompletedNode.nodeLoaded, @".layer shouldn't be the thing to load the view");
|
||||||
|
|
||||||
[replacement.layer.asyncdisplaykit_asyncTransaction addCompletionBlock:^(id<NSObject> unused, BOOL canceled) {
|
[replacement.layer.asyncdisplaykit_asyncTransaction addCompletionBlock:^(id<NSObject> unused, BOOL canceled) {
|
||||||
ASDisplayNodeCAssertMainThread();
|
ASDisplayNodeCAssertMainThread();
|
||||||
|
|||||||
@@ -264,15 +264,15 @@
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)didDisappear
|
- (void)didExitHierarchy
|
||||||
{
|
{
|
||||||
self.contents = nil;
|
self.contents = nil;
|
||||||
[super didDisappear];
|
[super didExitHierarchy];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)willAppear
|
- (void)willEnterHierarchy
|
||||||
{
|
{
|
||||||
[super willAppear];
|
[super willEnterHierarchy];
|
||||||
|
|
||||||
if (!self.layer.contents)
|
if (!self.layer.contents)
|
||||||
[self setNeedsDisplay];
|
[self setNeedsDisplay];
|
||||||
@@ -365,7 +365,7 @@
|
|||||||
BOOL isCroppingImage = ((boundsSize.width < imageSize.width) || (boundsSize.height < imageSize.height));
|
BOOL isCroppingImage = ((boundsSize.width < imageSize.width) || (boundsSize.height < imageSize.height));
|
||||||
|
|
||||||
// Re-display if we need to.
|
// Re-display if we need to.
|
||||||
if (self.isViewLoaded && self.contentMode == UIViewContentModeScaleAspectFill && isCroppingImage)
|
if (self.nodeLoaded && self.contentMode == UIViewContentModeScaleAspectFill && isCroppingImage)
|
||||||
[self setNeedsDisplay];
|
[self setNeedsDisplay];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
109
AsyncDisplayKit/ASTableView.h
Normal file
109
AsyncDisplayKit/ASTableView.h
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/* Copyright (c) 2014-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
#import <AsyncDisplayKit/ASRangeController.h>
|
||||||
|
#import <AsyncDisplayKit/ASTableViewProtocols.h>
|
||||||
|
|
||||||
|
@class ASCellNode;
|
||||||
|
@protocol ASTableViewDataSource;
|
||||||
|
@protocol ASTableViewDelegate;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Node-based table view.
|
||||||
|
*
|
||||||
|
* ASTableView is a version of UITableView that uses nodes -- specifically, ASCellNode subclasses -- with asynchronous
|
||||||
|
* pre-rendering instead of synchronously loading UITableViewCells.
|
||||||
|
*/
|
||||||
|
@interface ASTableView : UITableView
|
||||||
|
|
||||||
|
@property (nonatomic, weak) id<ASTableViewDataSource> asyncDataSource;
|
||||||
|
@property (nonatomic, weak) id<ASTableViewDelegate> asyncDelegate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tuning parameters for the working range.
|
||||||
|
*
|
||||||
|
* Defaults to a trailing buffer of one screenful and a leading buffer of two screenfuls.
|
||||||
|
*/
|
||||||
|
@property (nonatomic, assign) ASRangeTuningParameters rangeTuningParameters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload everything from scratch, destroying the working range and all cached nodes.
|
||||||
|
*
|
||||||
|
* @warning This method is substantially more expensive than UITableView's version.
|
||||||
|
*/
|
||||||
|
- (void)reloadData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WARNING: ASTableView's update/editing support is not yet implemented. Use of these methods will fire an assertion.
|
||||||
|
*
|
||||||
|
* This initial version of ASTableView only supports appending nodes (see below). If you'd like to see full-fledged
|
||||||
|
* support for data source updates and interactive editing, please file a GitHub issue -- AsyncDisplayKit can do it,
|
||||||
|
* we just haven't built it out yet. :]
|
||||||
|
*/
|
||||||
|
//- (void)beginUpdates;
|
||||||
|
//- (void)endUpdates;
|
||||||
|
//
|
||||||
|
//- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
|
||||||
|
//- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
|
||||||
|
//- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
|
||||||
|
//- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection;
|
||||||
|
//
|
||||||
|
//- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
|
||||||
|
//- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
|
||||||
|
//- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
|
||||||
|
//- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath;
|
||||||
|
//
|
||||||
|
//- (void)setEditing:(BOOL)editing;
|
||||||
|
//- (void)setEditing:(BOOL)editing animated:(BOOL)animated;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append nodes.
|
||||||
|
*
|
||||||
|
* As with UITableView, the asyncDataSource must be updated to reflect the new nodes before this method is called.
|
||||||
|
*
|
||||||
|
* @param indexPaths Ordered array of index paths corresponding to the nodes to be added.
|
||||||
|
*/
|
||||||
|
- (void)appendNodesWithIndexPaths:(NSArray *)indexPaths;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a node-based UITableViewDataSource.
|
||||||
|
*/
|
||||||
|
@protocol ASTableViewDataSource <ASCommonTableViewDataSource, NSObject>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to -tableView:cellForRowAtIndexPath:.
|
||||||
|
*
|
||||||
|
* Return a node for display at this indexpath. Must be thread-safe (can be called on the main thread or a background
|
||||||
|
* queue) and should not implement reuse (it will be called once per row). Unlike UITableView's version, this method
|
||||||
|
* is not called when the row is about to display.
|
||||||
|
*/
|
||||||
|
- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a node-based UITableViewDelegate.
|
||||||
|
*
|
||||||
|
* Note that -tableView:heightForRowAtIndexPath: has been removed; instead, your custom ASCellNode subclasses are
|
||||||
|
* responsible for deciding their preferred onscreen height in -calculateSizeThatFits:.
|
||||||
|
*/
|
||||||
|
@protocol ASTableViewDelegate <ASCommonTableViewDelegate, NSObject>
|
||||||
|
|
||||||
|
@optional
|
||||||
|
|
||||||
|
- (void)tableView:(UITableView *)tableView willDisplayNodeForRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
- (void)tableView:(UITableView *)tableView didEndDisplayingNodeForRowAtIndexPath:(NSIndexPath*)indexPath;
|
||||||
|
|
||||||
|
@end
|
||||||
362
AsyncDisplayKit/ASTableView.m
Normal file
362
AsyncDisplayKit/ASTableView.m
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
/* Copyright (c) 2014-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "ASTableView.h"
|
||||||
|
|
||||||
|
#import "ASAssert.h"
|
||||||
|
#import "ASRangeController.h"
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark Proxying.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ASTableView intercepts and/or overrides a few of UITableView's critical data source and delegate methods.
|
||||||
|
*
|
||||||
|
* Any selector included in this function *MUST* be implemented by ASTableView.
|
||||||
|
*/
|
||||||
|
static BOOL _isInterceptedSelector(SEL sel)
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
// handled by ASTableView node<->cell machinery
|
||||||
|
sel == @selector(tableView:cellForRowAtIndexPath:) ||
|
||||||
|
sel == @selector(tableView:heightForRowAtIndexPath:) ||
|
||||||
|
|
||||||
|
// handled by ASRangeController
|
||||||
|
sel == @selector(numberOfSectionsInTableView:) ||
|
||||||
|
sel == @selector(tableView:numberOfRowsInSection:) ||
|
||||||
|
|
||||||
|
// used for ASRangeController visibility updates
|
||||||
|
sel == @selector(tableView:willDisplayCell:forRowAtIndexPath:) ||
|
||||||
|
sel == @selector(tableView:didEndDisplayingCell:forRowAtIndexPath:)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stand-in for UITableViewDataSource and UITableViewDelegate. Any method calls we intercept are routed to ASTableView;
|
||||||
|
* everything else leaves AsyncDisplayKit safely and arrives at the original intended data source and delegate.
|
||||||
|
*/
|
||||||
|
@interface _ASTableViewProxy : NSProxy
|
||||||
|
- (instancetype)initWithTarget:(id<NSObject>)target interceptor:(ASTableView *)interceptor;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation _ASTableViewProxy {
|
||||||
|
id<NSObject> _target;
|
||||||
|
ASTableView *_interceptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithTarget:(id<NSObject>)target interceptor:(ASTableView *)interceptor
|
||||||
|
{
|
||||||
|
// -[NSProxy init] is undefined
|
||||||
|
if (!self) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASDisplayNodeAssert(target, @"target must not be nil");
|
||||||
|
ASDisplayNodeAssert(interceptor, @"interceptor must not be nil");
|
||||||
|
|
||||||
|
_target = target;
|
||||||
|
_interceptor = interceptor;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)respondsToSelector:(SEL)aSelector
|
||||||
|
{
|
||||||
|
return (_isInterceptedSelector(aSelector) || [_target respondsToSelector:aSelector]);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)forwardingTargetForSelector:(SEL)aSelector
|
||||||
|
{
|
||||||
|
if (_isInterceptedSelector(aSelector)) {
|
||||||
|
return _interceptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [_target respondsToSelector:aSelector] ? _target : nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark ASCellNode<->UITableViewCell bridging.
|
||||||
|
|
||||||
|
@interface _ASTableViewCell : UITableViewCell
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation _ASTableViewCell
|
||||||
|
// TODO add assertions to prevent use of view-backed UITableViewCell properties (eg .textLabel)
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark ASTableView.
|
||||||
|
|
||||||
|
@interface ASTableView () <ASRangeControllerDelegate> {
|
||||||
|
_ASTableViewProxy *_proxyDataSource;
|
||||||
|
_ASTableViewProxy *_proxyDelegate;
|
||||||
|
|
||||||
|
ASRangeController *_rangeController;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation ASTableView
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark Lifecycle.
|
||||||
|
|
||||||
|
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style
|
||||||
|
{
|
||||||
|
if (!(self = [super initWithFrame:frame style:style]))
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
_rangeController = [[ASRangeController alloc] init];
|
||||||
|
_rangeController.delegate = self;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark Overrides.
|
||||||
|
|
||||||
|
- (void)reloadData
|
||||||
|
{
|
||||||
|
[_rangeController rebuildData];
|
||||||
|
[super reloadData];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setDataSource:(id<UITableViewDataSource>)dataSource
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssert(NO, @"ASTableView uses asyncDataSource, not UITableView's dataSource property.");
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setDelegate:(id<UITableViewDelegate>)delegate
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssert(NO, @"ASTableView uses asyncDelegate, not UITableView's delegate property.");
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setAsyncDataSource:(id<ASTableViewDataSource>)asyncDataSource
|
||||||
|
{
|
||||||
|
if (_asyncDataSource == asyncDataSource)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_asyncDataSource = asyncDataSource;
|
||||||
|
_proxyDataSource = [[_ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self];
|
||||||
|
super.dataSource = (id<UITableViewDataSource>)_proxyDataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setAsyncDelegate:(id<ASTableViewDelegate>)asyncDelegate
|
||||||
|
{
|
||||||
|
if (_asyncDelegate == asyncDelegate)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_asyncDelegate = asyncDelegate;
|
||||||
|
_proxyDelegate = [[_ASTableViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self];
|
||||||
|
super.delegate = (id<UITableViewDelegate>)_proxyDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (ASRangeTuningParameters)rangeTuningParameters
|
||||||
|
{
|
||||||
|
return _rangeController.tuningParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setRangeTuningParameters:(ASRangeTuningParameters)tuningParameters
|
||||||
|
{
|
||||||
|
_rangeController.tuningParameters = tuningParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)appendNodesWithIndexPaths:(NSArray *)indexPaths
|
||||||
|
{
|
||||||
|
[_rangeController appendNodesWithIndexPaths:indexPaths];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark Assertions.
|
||||||
|
|
||||||
|
- (void)throwUnimplementedException
|
||||||
|
{
|
||||||
|
[[NSException exceptionWithName:@"UnimplementedException"
|
||||||
|
reason:@"ASTableView's update/editing support is not yet implemented. Please see ASTableView.h."
|
||||||
|
userInfo:nil] raise];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)beginUpdates
|
||||||
|
{
|
||||||
|
[self throwUnimplementedException];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)endUpdates
|
||||||
|
{
|
||||||
|
[self throwUnimplementedException];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
|
||||||
|
{
|
||||||
|
[self throwUnimplementedException];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
|
||||||
|
{
|
||||||
|
[self throwUnimplementedException];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
|
||||||
|
{
|
||||||
|
[self throwUnimplementedException];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection
|
||||||
|
{
|
||||||
|
[self throwUnimplementedException];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
|
||||||
|
{
|
||||||
|
[self throwUnimplementedException];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
|
||||||
|
{
|
||||||
|
[self throwUnimplementedException];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
|
||||||
|
{
|
||||||
|
[self throwUnimplementedException];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
|
||||||
|
{
|
||||||
|
[self throwUnimplementedException];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setEditing:(BOOL)editing
|
||||||
|
{
|
||||||
|
[self throwUnimplementedException];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
|
||||||
|
{
|
||||||
|
[self throwUnimplementedException];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark Intercepted selectors.
|
||||||
|
|
||||||
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
static NSString *reuseIdentifier = @"_ASTableViewCell";
|
||||||
|
|
||||||
|
_ASTableViewCell *cell = [self dequeueReusableCellWithIdentifier:reuseIdentifier];
|
||||||
|
if (!cell) {
|
||||||
|
cell = [[_ASTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
|
||||||
|
}
|
||||||
|
|
||||||
|
[_rangeController configureContentView:cell.contentView forIndexPath:indexPath];
|
||||||
|
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
return [_rangeController calculatedSizeForNodeAtIndexPath:indexPath].height;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||||
|
{
|
||||||
|
return [_rangeController numberOfSizedSections];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||||
|
{
|
||||||
|
return [_rangeController numberOfSizedRowsInSection:section];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
[_rangeController visibleNodeIndexPathsDidChange];
|
||||||
|
|
||||||
|
if ([_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNodeForRowAtIndexPath:)]) {
|
||||||
|
[_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath
|
||||||
|
{
|
||||||
|
[_rangeController visibleNodeIndexPathsDidChange];
|
||||||
|
|
||||||
|
if ([_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNodeForRowAtIndexPath:)]) {
|
||||||
|
[_asyncDelegate tableView:self didEndDisplayingNodeForRowAtIndexPath:indexPath];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark ASRangeControllerDelegate.
|
||||||
|
|
||||||
|
- (NSArray *)rangeControllerVisibleNodeIndexPaths:(ASRangeController *)rangeController
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertMainThread();
|
||||||
|
return [self indexPathsForVisibleRows];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CGSize)rangeControllerViewportSize:(ASRangeController *)rangeController
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertMainThread();
|
||||||
|
return self.bounds.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSInteger)rangeControllerSections:(ASRangeController *)rangeController
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertMainThread();
|
||||||
|
return [_asyncDataSource numberOfSectionsInTableView:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSInteger)rangeController:(ASRangeController *)rangeController rowsInSection:(NSInteger)section
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertMainThread();
|
||||||
|
return [_asyncDataSource tableView:self numberOfRowsInSection:section];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (ASCellNode *)rangeController:(ASRangeController *)rangeController nodeForIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertNotMainThread();
|
||||||
|
return [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CGSize)rangeController:(ASRangeController *)rangeController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertNotMainThread();
|
||||||
|
return CGSizeMake(self.bounds.size.width, FLT_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)rangeController:(ASRangeController *)rangeController didSizeNodesWithIndexPaths:(NSArray *)indexPaths
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertMainThread();
|
||||||
|
|
||||||
|
[super beginUpdates];
|
||||||
|
|
||||||
|
// -insertRowsAtIndexPaths:: is insufficient; UITableView also needs to be notified of section changes
|
||||||
|
NSInteger sectionCount = [super numberOfSections];
|
||||||
|
NSInteger newSectionCount = [_rangeController numberOfSizedSections];
|
||||||
|
if (newSectionCount > sectionCount) {
|
||||||
|
NSRange range = NSMakeRange(sectionCount, newSectionCount - sectionCount);
|
||||||
|
NSIndexSet *sections = [NSIndexSet indexSetWithIndexesInRange:range];
|
||||||
|
[super insertSections:sections withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||||
|
}
|
||||||
|
|
||||||
|
[super insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
|
||||||
|
|
||||||
|
[super endUpdates];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@end
|
||||||
88
AsyncDisplayKit/ASTableViewProtocols.h
Normal file
88
AsyncDisplayKit/ASTableViewProtocols.h
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/* Copyright (c) 2014-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a subset of UITableViewDataSource.
|
||||||
|
*
|
||||||
|
* @see ASTableViewDataSource
|
||||||
|
*/
|
||||||
|
@protocol ASCommonTableViewDataSource <NSObject>
|
||||||
|
|
||||||
|
@required
|
||||||
|
|
||||||
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
|
||||||
|
|
||||||
|
@optional
|
||||||
|
|
||||||
|
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
|
||||||
|
|
||||||
|
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;
|
||||||
|
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section;
|
||||||
|
|
||||||
|
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
|
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
|
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView;
|
||||||
|
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index;
|
||||||
|
|
||||||
|
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
|
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a subset of UITableViewDelegate.
|
||||||
|
*
|
||||||
|
* @see ASTableViewDelegate
|
||||||
|
*/
|
||||||
|
@protocol ASCommonTableViewDelegate <NSObject, UIScrollViewDelegate>
|
||||||
|
|
||||||
|
@optional
|
||||||
|
|
||||||
|
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section;
|
||||||
|
- (void)tableView:(UITableView *)tableView willDisplayFooterView:(UIView *)view forSection:(NSInteger)section;
|
||||||
|
- (void)tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:(NSInteger)section;
|
||||||
|
- (void)tableView:(UITableView *)tableView didEndDisplayingFooterView:(UIView *)view forSection:(NSInteger)section;
|
||||||
|
|
||||||
|
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
|
||||||
|
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;
|
||||||
|
|
||||||
|
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section;
|
||||||
|
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section;
|
||||||
|
|
||||||
|
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
|
- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
- (void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
- (void)tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
|
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
- (NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
|
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
|
- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
|
- (void)tableView:(UITableView*)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
- (void)tableView:(UITableView*)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
|
- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath;
|
||||||
|
|
||||||
|
- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
|
- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender;
|
||||||
|
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender;
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -157,7 +157,7 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f)
|
|||||||
NSString *truncationString = [_composedTruncationString string];
|
NSString *truncationString = [_composedTruncationString string];
|
||||||
if (plainString.length > 50)
|
if (plainString.length > 50)
|
||||||
plainString = [[plainString substringToIndex:50] stringByAppendingString:@"\u2026"];
|
plainString = [[plainString substringToIndex:50] stringByAppendingString:@"\u2026"];
|
||||||
return [NSString stringWithFormat:@"<%@: %p; text = \"%@\"; truncation string = \"%@\"; frame = %@>", self.class, self, plainString, truncationString, self.isViewLoaded ? NSStringFromCGRect(self.layer.frame) : nil];
|
return [NSString stringWithFormat:@"<%@: %p; text = \"%@\"; truncation string = \"%@\"; frame = %@>", self.class, self, plainString, truncationString, self.nodeLoaded ? NSStringFromCGRect(self.layer.frame) : nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - ASDisplayNode
|
#pragma mark - ASDisplayNode
|
||||||
@@ -192,7 +192,7 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f)
|
|||||||
fminf(ceilPixelValue(renderSizePlusShadowPadding.height), constrainedSize.height));
|
fminf(ceilPixelValue(renderSizePlusShadowPadding.height), constrainedSize.height));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)willAppear
|
- (void)willEnterHierarchy
|
||||||
{
|
{
|
||||||
CALayer *layer = self.layer;
|
CALayer *layer = self.layer;
|
||||||
if (!layer.contents) {
|
if (!layer.contents) {
|
||||||
@@ -200,7 +200,7 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f)
|
|||||||
// set.
|
// set.
|
||||||
[layer setNeedsDisplay];
|
[layer setNeedsDisplay];
|
||||||
}
|
}
|
||||||
[super willAppear];
|
[super willEnterHierarchy];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)displayDidFinish
|
- (void)displayDidFinish
|
||||||
@@ -215,7 +215,7 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f)
|
|||||||
[self _invalidateRenderer];
|
[self _invalidateRenderer];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)didDisappear
|
- (void)didExitHierarchy
|
||||||
{
|
{
|
||||||
// We nil out the contents and kill our renderer to prevent the very large
|
// We nil out the contents and kill our renderer to prevent the very large
|
||||||
// memory overhead of maintaining these for all text nodes. They can be
|
// memory overhead of maintaining these for all text nodes. They can be
|
||||||
@@ -224,7 +224,7 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f)
|
|||||||
|
|
||||||
[self _invalidateRenderer];
|
[self _invalidateRenderer];
|
||||||
|
|
||||||
[super didDisappear];
|
[super didExitHierarchy];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)didLoad
|
- (void)didLoad
|
||||||
|
|||||||
@@ -7,8 +7,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import <AsyncDisplayKit/ASDisplayNode.h>
|
#import <AsyncDisplayKit/ASDisplayNode.h>
|
||||||
#import <ASyncDisplayKit/ASDisplayNodeExtras.h>
|
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||||
|
|
||||||
#import <AsyncDisplayKit/ASControlNode.h>
|
#import <AsyncDisplayKit/ASControlNode.h>
|
||||||
#import <AsyncDisplayKit/ASImageNode.h>
|
#import <AsyncDisplayKit/ASImageNode.h>
|
||||||
#import <ASyncDisplayKit/ASTextNode.h>
|
#import <AsyncDisplayKit/ASTextNode.h>
|
||||||
|
|
||||||
|
#import <AsyncDisplayKit/ASTableView.h>
|
||||||
|
#import <AsyncDisplayKit/ASCellNode.h>
|
||||||
|
|||||||
117
AsyncDisplayKit/Details/ASRangeController.h
Normal file
117
AsyncDisplayKit/Details/ASRangeController.h
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
/* Copyright (c) 2014-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
#import <AsyncDisplayKit/ASCellNode.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
// working range buffers, on either side of scroll
|
||||||
|
NSInteger trailingBufferScreenfuls;
|
||||||
|
NSInteger leadingBufferScreenfuls;
|
||||||
|
} ASRangeTuningParameters;
|
||||||
|
|
||||||
|
@protocol ASRangeControllerDelegate;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Working range controller.
|
||||||
|
*
|
||||||
|
* Used internally by ASTableView and potentially by a future ASCollectionView. Observes the visible range, maintains
|
||||||
|
* a working range, and is responsible for handling AsyncDisplayKit machinery (sizing cell nodes, enqueueing and
|
||||||
|
* cancelling their asynchronous layout and display, and so on).
|
||||||
|
*/
|
||||||
|
@interface ASRangeController : NSObject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the receiver that its delegate's data source has been set or changed. This is like -[UITableView reloadData]
|
||||||
|
* but drastically more expensive, as it destroys the working range and all cached nodes.
|
||||||
|
*/
|
||||||
|
- (void)rebuildData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the receiver that the visible range has been updated.
|
||||||
|
*
|
||||||
|
* @see -rangeControllerVisibleNodeIndexPaths:
|
||||||
|
*/
|
||||||
|
- (void)visibleNodeIndexPathsDidChange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ASTableView is only aware of nodes that have already been sized.
|
||||||
|
*
|
||||||
|
* Custom ASCellNode implementations are encouraged to have "realistic placeholders", since they can only be onscreen if
|
||||||
|
* they have enough data for layout. E.g., try setting all subnodes' background colours to [UIColor lightGrayColor].
|
||||||
|
*/
|
||||||
|
- (NSInteger)numberOfSizedSections;
|
||||||
|
- (NSInteger)numberOfSizedRowsInSection:(NSInteger)section;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the sized node for `indexPath` as a subview of `contentView`.
|
||||||
|
*/
|
||||||
|
- (void)configureContentView:(UIView *)contentView forIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query the sized node at `indexPath` for its calculatedSize.
|
||||||
|
*
|
||||||
|
* TODO: Currently we disallow direct access to ASCellNode outside ASRangeController since touching the node's view can
|
||||||
|
* break async display. We should expose the node anyway, possibly with an assertion guarding against external
|
||||||
|
* use of the view property, so ASCellNode can support configuration for UITableViewCell properties (selection
|
||||||
|
* style, separator style, etc.) and ASTableView can query that data.
|
||||||
|
*/
|
||||||
|
- (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the receiver that its data source has been updated to append the specified nodes.
|
||||||
|
*/
|
||||||
|
- (void)appendNodesWithIndexPaths:(NSArray *)indexPaths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegate and ultimate data source. Must not be nil.
|
||||||
|
*/
|
||||||
|
@property (nonatomic, weak) id<ASRangeControllerDelegate> delegate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tuning parameters for the working range.
|
||||||
|
*
|
||||||
|
* Defaults to a trailing buffer of one screenful and a leading buffer of two screenfuls.
|
||||||
|
*/
|
||||||
|
@property (nonatomic, assign) ASRangeTuningParameters tuningParameters;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@protocol ASRangeControllerDelegate <NSObject>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns an array of index paths corresponding to the nodes currently visible onscreen (i.e., the visible range).
|
||||||
|
*/
|
||||||
|
- (NSArray *)rangeControllerVisibleNodeIndexPaths:(ASRangeController *)rangeController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns the receiver's viewport size (i.e., the screen space occupied by the visible range).
|
||||||
|
*/
|
||||||
|
- (CGSize)rangeControllerViewportSize:(ASRangeController *)rangeController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forwarded to ASTableView's data source.
|
||||||
|
*/
|
||||||
|
- (NSInteger)rangeControllerSections:(ASRangeController *)rangeController;
|
||||||
|
- (NSInteger)rangeController:(ASRangeController *)rangeController rowsInSection:(NSInteger)section;
|
||||||
|
- (ASCellNode *)rangeController:(ASRangeController *)rangeController nodeForIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns sizing constraints for the node at `indexPath`.
|
||||||
|
*/
|
||||||
|
- (CGSize)rangeController:(ASRangeController *)rangeController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the receiver that the specified nodes have been sized and are ready for display.
|
||||||
|
*/
|
||||||
|
- (void)rangeController:(ASRangeController *)rangeController didSizeNodesWithIndexPaths:(NSArray *)indexPaths;
|
||||||
|
|
||||||
|
@end
|
||||||
618
AsyncDisplayKit/Details/ASRangeController.mm
Normal file
618
AsyncDisplayKit/Details/ASRangeController.mm
Normal file
@@ -0,0 +1,618 @@
|
|||||||
|
/* Copyright (c) 2014-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "ASRangeController.h"
|
||||||
|
|
||||||
|
#import "ASAssert.h"
|
||||||
|
#import "ASDisplayNodeExtras.h"
|
||||||
|
#import "ASDisplayNodeInternal.h"
|
||||||
|
#import "ASRangeControllerInternal.h"
|
||||||
|
|
||||||
|
typedef NS_ENUM(NSInteger, ASScrollDirection) {
|
||||||
|
ASScrollDirectionUp,
|
||||||
|
ASScrollDirectionDown,
|
||||||
|
};
|
||||||
|
|
||||||
|
@interface ASRangeController () {
|
||||||
|
// index path -> node mapping
|
||||||
|
NSMutableDictionary *_nodes;
|
||||||
|
|
||||||
|
// array of boxed CGSizes. _nodeSizes.count == the number of nodes that have been sized
|
||||||
|
// TODO optimise this, perhaps by making _nodes an array
|
||||||
|
NSMutableArray *_nodeSizes;
|
||||||
|
|
||||||
|
// consumer data source information
|
||||||
|
NSArray *_sectionCounts;
|
||||||
|
NSInteger _totalNodeCount;
|
||||||
|
|
||||||
|
// used for global <-> section.row mapping. _sectionOffsets[section] is the index at which the section starts
|
||||||
|
NSArray *_sectionOffsets;
|
||||||
|
|
||||||
|
// sized data source information
|
||||||
|
NSInteger _sizedNodeCount;
|
||||||
|
|
||||||
|
// ranges
|
||||||
|
BOOL _queuedRangeUpdate;
|
||||||
|
ASScrollDirection _scrollDirection;
|
||||||
|
NSRange _visibleRange;
|
||||||
|
NSRange _workingRange;
|
||||||
|
NSMutableOrderedSet *_workingIndexPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
@implementation ASRangeController
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark Lifecycle.
|
||||||
|
|
||||||
|
- (instancetype)init
|
||||||
|
{
|
||||||
|
if (!(self = [super init]))
|
||||||
|
return nil;
|
||||||
|
|
||||||
|
_tuningParameters = {
|
||||||
|
.trailingBufferScreenfuls = 1,
|
||||||
|
.leadingBufferScreenfuls = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (dispatch_queue_t)sizingQueue
|
||||||
|
{
|
||||||
|
static dispatch_queue_t sizingQueue = NULL;
|
||||||
|
static dispatch_once_t onceToken;
|
||||||
|
dispatch_once(&onceToken, ^{
|
||||||
|
sizingQueue = dispatch_queue_create("com.facebook.AsyncDisplayKit.ASRangeController.sizingQueue", DISPATCH_QUEUE_CONCURRENT);
|
||||||
|
// we use the highpri queue to prioritize UI rendering over other async operations
|
||||||
|
dispatch_set_target_queue(sizingQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
|
||||||
|
});
|
||||||
|
|
||||||
|
return sizingQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (UIView *)workingView
|
||||||
|
{
|
||||||
|
// we add nodes' views to this invisible window to start async rendering
|
||||||
|
static UIWindow *workingWindow = nil;
|
||||||
|
static dispatch_once_t onceToken;
|
||||||
|
dispatch_once(&onceToken, ^{
|
||||||
|
workingWindow = [[UIWindow alloc] initWithFrame:CGRectZero];
|
||||||
|
workingWindow.windowLevel = UIWindowLevelNormal - 1000;
|
||||||
|
workingWindow.userInteractionEnabled = NO;
|
||||||
|
workingWindow.clipsToBounds = YES;
|
||||||
|
workingWindow.hidden = YES;
|
||||||
|
});
|
||||||
|
|
||||||
|
return workingWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark Helpers.
|
||||||
|
|
||||||
|
static NSOrderedSet *ASCopySetMinusSet(NSOrderedSet *minuend, NSOrderedSet *subtrahend)
|
||||||
|
{
|
||||||
|
NSMutableOrderedSet *difference = [minuend mutableCopy];
|
||||||
|
[difference minusOrderedSet:subtrahend];
|
||||||
|
return difference;
|
||||||
|
}
|
||||||
|
|
||||||
|
// useful for debugging: working range, buffer sizes, and visible range
|
||||||
|
__attribute__((unused)) static NSString *ASWorkingRangeDebugDescription(NSRange workingRange, NSRange visibleRange)
|
||||||
|
{
|
||||||
|
NSInteger visibleRangeLastElement = NSMaxRange(visibleRange) - 1;
|
||||||
|
NSInteger workingRangeLastElement = NSMaxRange(workingRange) - 1;
|
||||||
|
return [NSString stringWithFormat:@"[%zd(%zd) [%zd, %zd] (%zd)%zd]",
|
||||||
|
workingRange.location,
|
||||||
|
visibleRange.location - workingRange.location,
|
||||||
|
visibleRange.location,
|
||||||
|
visibleRangeLastElement,
|
||||||
|
workingRangeLastElement - visibleRangeLastElement,
|
||||||
|
workingRangeLastElement];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark NSRange <-> NSIndexPath.
|
||||||
|
|
||||||
|
static BOOL ASRangeIsValid(NSRange range)
|
||||||
|
{
|
||||||
|
return range.location != NSNotFound && range.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSIndexPath *)indexPathForIndex:(NSInteger)index
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssert(index < _totalNodeCount, @"invalid argument");
|
||||||
|
|
||||||
|
for (NSInteger section = _sectionCounts.count - 1; section >= 0; section--) {
|
||||||
|
NSInteger offset = [_sectionOffsets[section] integerValue];
|
||||||
|
if (offset <= index) {
|
||||||
|
return [NSIndexPath indexPathForRow:index - offset inSection:section];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ASDisplayNodeAssert(NO, @"logic error");
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSArray *)indexPathsForRange:(NSRange)range
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssert(ASRangeIsValid(range) && NSMaxRange(range) <= _totalNodeCount, @"invalid argument");
|
||||||
|
|
||||||
|
NSMutableArray *result = [NSMutableArray arrayWithCapacity:range.length];
|
||||||
|
|
||||||
|
NSIndexPath *indexPath = [self indexPathForIndex:range.location];
|
||||||
|
for (NSInteger i = range.location; i < NSMaxRange(range); i++) {
|
||||||
|
[result addObject:indexPath];
|
||||||
|
|
||||||
|
if (indexPath.row + 1 >= [_sectionCounts[indexPath.section] integerValue]) {
|
||||||
|
indexPath = [NSIndexPath indexPathForRow:0 inSection:indexPath.section + 1];
|
||||||
|
} else {
|
||||||
|
indexPath = [NSIndexPath indexPathForRow:indexPath.row + 1 inSection:indexPath.section];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSInteger)indexForIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
NSInteger index = [_sectionOffsets[indexPath.section] integerValue] + indexPath.row;
|
||||||
|
ASDisplayNodeAssert(index < _totalNodeCount, @"invalid argument");
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark View manipulation.
|
||||||
|
|
||||||
|
- (void)discardNode:(ASCellNode *)node
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertMainThread();
|
||||||
|
ASDisplayNodeAssert(node, @"invalid argument");
|
||||||
|
|
||||||
|
NSInteger index = [self indexForIndexPath:node.asyncdisplaykit_indexPath];
|
||||||
|
if (NSLocationInRange(index, _workingRange)) {
|
||||||
|
// move the node's view to the working range area, so its rendering persists
|
||||||
|
[self moveNodeToWorkingView:node];
|
||||||
|
} else {
|
||||||
|
// this node isn't in the working range, remove it from the view hierarchy
|
||||||
|
[self removeNodeFromWorkingView:node];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)removeNodeFromWorkingView:(ASCellNode *)node
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertMainThread();
|
||||||
|
ASDisplayNodeAssert(node, @"invalid argument");
|
||||||
|
|
||||||
|
[node recursiveSetPreventOrCancelDisplay:YES];
|
||||||
|
[node.view removeFromSuperview];
|
||||||
|
[_workingIndexPaths removeObject:node.asyncdisplaykit_indexPath];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)moveNodeToWorkingView:(ASCellNode *)node
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertMainThread();
|
||||||
|
ASDisplayNodeAssert(node, @"invalid argument");
|
||||||
|
|
||||||
|
[self moveNode:node toView:[ASRangeController workingView]];
|
||||||
|
[_workingIndexPaths addObject:node.asyncdisplaykit_indexPath];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)moveNode:(ASCellNode *)node toView:(UIView *)view
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssertMainThread();
|
||||||
|
ASDisplayNodeAssert(node && view, @"invalid argument, did you mean -removeNodeFromWorkingView:?");
|
||||||
|
|
||||||
|
// use an explicit transaction to force CoreAnimation to display nodes in order
|
||||||
|
[CATransaction begin];
|
||||||
|
|
||||||
|
// if this node is in the view hierarchy, moving it will cause a redisplay, so we disable hierarchy notifications.
|
||||||
|
// if it *isn't* in the view hierarchy, we need it to receive those notifications to trigger its first display.
|
||||||
|
BOOL nodeIsInHierarchy = (node.view.superview != nil);
|
||||||
|
|
||||||
|
if (nodeIsInHierarchy)
|
||||||
|
ASDisplayNodeDisableHierarchyNotifications(node);
|
||||||
|
|
||||||
|
[view addSubview:node.view];
|
||||||
|
|
||||||
|
if (nodeIsInHierarchy)
|
||||||
|
ASDisplayNodeEnableHierarchyNotifications(node);
|
||||||
|
|
||||||
|
[CATransaction commit];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark API.
|
||||||
|
|
||||||
|
- (void)recalculateDataSourceCounts
|
||||||
|
{
|
||||||
|
// data source information (_sectionCounts, _sectionOffsets, _totalNodeCount) is not currently thread-safe
|
||||||
|
ASDisplayNodeAssertMainThread();
|
||||||
|
|
||||||
|
NSInteger sections = [_delegate rangeControllerSections:self];
|
||||||
|
|
||||||
|
NSMutableArray *sectionCounts = [NSMutableArray arrayWithCapacity:sections];
|
||||||
|
for (NSInteger section = 0; section < sections; section++) {
|
||||||
|
sectionCounts[section] = @([_delegate rangeController:self rowsInSection:section]);
|
||||||
|
}
|
||||||
|
|
||||||
|
NSMutableArray *sectionOffsets = [NSMutableArray arrayWithCapacity:sections];
|
||||||
|
NSInteger offset = 0;
|
||||||
|
for (NSInteger section = 0; section < sections; section++) {
|
||||||
|
sectionOffsets[section] = @(offset);
|
||||||
|
offset += [sectionCounts[section] integerValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
_sectionCounts = sectionCounts;
|
||||||
|
_sectionOffsets = sectionOffsets;
|
||||||
|
_totalNodeCount = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)rebuildData
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* teardown
|
||||||
|
*/
|
||||||
|
for (ASCellNode *node in _nodes.objectEnumerator) {
|
||||||
|
[node removeFromSupernode];
|
||||||
|
[node.view removeFromSuperview];
|
||||||
|
}
|
||||||
|
[_nodes removeAllObjects];
|
||||||
|
_nodes = nil;
|
||||||
|
|
||||||
|
for (UIView *view in [[ASRangeController workingView] subviews]) {
|
||||||
|
[view removeFromSuperview];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* setup
|
||||||
|
*/
|
||||||
|
[self recalculateDataSourceCounts];
|
||||||
|
_nodes = [NSMutableDictionary dictionaryWithCapacity:_totalNodeCount];
|
||||||
|
_visibleRange = _workingRange = NSMakeRange(NSNotFound, 0);
|
||||||
|
_sizedNodeCount = 0;
|
||||||
|
_nodeSizes = [NSMutableArray array];
|
||||||
|
_scrollDirection = ASScrollDirectionDown;
|
||||||
|
_workingIndexPaths = [NSMutableOrderedSet orderedSet];
|
||||||
|
|
||||||
|
// don't bother sizing if the data source is empty
|
||||||
|
if (_totalNodeCount > 0) {
|
||||||
|
[self sizeNextBlock];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)visibleNodeIndexPathsDidChange
|
||||||
|
{
|
||||||
|
if (_queuedRangeUpdate)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// coalesce these events -- handling them multiple times per runloop is noisy and expensive
|
||||||
|
_queuedRangeUpdate = YES;
|
||||||
|
[self performSelector:@selector(updateVisibleNodeIndexPaths)
|
||||||
|
withObject:nil
|
||||||
|
afterDelay:0
|
||||||
|
inModes:@[ NSRunLoopCommonModes ]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateVisibleNodeIndexPaths
|
||||||
|
{
|
||||||
|
NSArray *indexPaths = [_delegate rangeControllerVisibleNodeIndexPaths:self];
|
||||||
|
[self setVisibleRange:NSMakeRange([self indexForIndexPath:[indexPaths firstObject]],
|
||||||
|
indexPaths.count)];
|
||||||
|
|
||||||
|
_queuedRangeUpdate = NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSInteger)numberOfSizedSections
|
||||||
|
{
|
||||||
|
// short-circuit if we haven't started sizing
|
||||||
|
if (_sizedNodeCount == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
NSIndexPath *lastSizedIndex = [self indexPathForIndex:_sizedNodeCount - 1];
|
||||||
|
NSInteger sizedSectionCount = lastSizedIndex.section + 1;
|
||||||
|
|
||||||
|
ASDisplayNodeAssert(sizedSectionCount <= _sectionCounts.count, @"logic error");
|
||||||
|
return sizedSectionCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSInteger)numberOfSizedRowsInSection:(NSInteger)section
|
||||||
|
{
|
||||||
|
// short-circuit if we haven't started sizing
|
||||||
|
if (_sizedNodeCount == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (section > _sectionCounts.count) {
|
||||||
|
ASDisplayNodeAssert(NO, @"this isn't even a valid section");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSIndexPath *lastSizedIndex = [self indexPathForIndex:_sizedNodeCount - 1];
|
||||||
|
if (section > lastSizedIndex.section) {
|
||||||
|
ASDisplayNodeAssert(NO, @"this section hasn't been sized yet");
|
||||||
|
return 0;
|
||||||
|
} else if (section == lastSizedIndex.section) {
|
||||||
|
// we're still sizing this section, return the count we have
|
||||||
|
return lastSizedIndex.row + 1;
|
||||||
|
} else {
|
||||||
|
// we've already sized beyond this section, return the full count
|
||||||
|
return [_sectionCounts[section] integerValue];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)configureContentView:(UIView *)contentView forIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
ASCellNode *newNode = [self sizedNodeForIndexPath:indexPath];
|
||||||
|
ASDisplayNodeAssert(newNode, @"this node hasn't been sized yet!");
|
||||||
|
|
||||||
|
if (newNode.view.superview == contentView) {
|
||||||
|
// this content view is already correctly configured
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (UIView *view in contentView.subviews) {
|
||||||
|
ASDisplayNode *node = view.asyncdisplaykit_node;
|
||||||
|
if (node) {
|
||||||
|
// plunk this node back into the working range, if appropriate
|
||||||
|
ASDisplayNodeAssert([node isKindOfClass:[ASCellNode class]], @"invalid node");
|
||||||
|
[self discardNode:(ASCellNode *)node];
|
||||||
|
} else {
|
||||||
|
// if it's not a node, it's something random UITableView added to the hierarchy. kill it.
|
||||||
|
[view removeFromSuperview];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[self moveNode:newNode toView:contentView];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
// TODO add an assertion (here or in ASTableView) that the calculated size isn't bogus (eg must be < tableview width)
|
||||||
|
ASCellNode *node = [self sizedNodeForIndexPath:indexPath];
|
||||||
|
return node.calculatedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark Working range.
|
||||||
|
|
||||||
|
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters
|
||||||
|
{
|
||||||
|
_tuningParameters = tuningParameters;
|
||||||
|
|
||||||
|
if (ASRangeIsValid(_visibleRange)) {
|
||||||
|
[self recalculateWorkingRange];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static NSRange ASCalculateWorkingRange(ASRangeTuningParameters params, ASScrollDirection scrollDirection,
|
||||||
|
NSRange visibleRange, NSArray *nodeSizes, CGSize viewport)
|
||||||
|
{
|
||||||
|
ASDisplayNodeCAssert(NSMaxRange(visibleRange) <= nodeSizes.count, @"nodes can't be visible until they're sized");
|
||||||
|
|
||||||
|
// extend the visible range by enough nodes to fill at least the requested number of screenfuls
|
||||||
|
// NB. this logic assumes (UITableView-style) vertical scrolling and would need to be changed for ASCollectionView
|
||||||
|
CGFloat minUpperBufferSize, minLowerBufferSize;
|
||||||
|
switch (scrollDirection) {
|
||||||
|
case ASScrollDirectionUp:
|
||||||
|
minUpperBufferSize = viewport.height * params.leadingBufferScreenfuls;
|
||||||
|
minLowerBufferSize = viewport.height * params.trailingBufferScreenfuls;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ASScrollDirectionDown:
|
||||||
|
minUpperBufferSize = viewport.height * params.trailingBufferScreenfuls;
|
||||||
|
minLowerBufferSize = viewport.height * params.leadingBufferScreenfuls;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "top" buffer (above the screen, if we're scrolling vertically)
|
||||||
|
NSInteger upperBuffer = 0;
|
||||||
|
CGFloat upperBufferHeight = 0.0f;
|
||||||
|
for (NSInteger idx = visibleRange.location - 1; idx >= 0 && upperBufferHeight < minUpperBufferSize; idx--) {
|
||||||
|
upperBuffer++;
|
||||||
|
upperBufferHeight += [nodeSizes[idx] CGSizeValue].height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "bottom" buffer (below the screen, if we're scrolling vertically)
|
||||||
|
NSInteger lowerBuffer = 0;
|
||||||
|
CGFloat lowerBufferHeight = 0.0f;
|
||||||
|
for (NSInteger idx = NSMaxRange(visibleRange); idx < nodeSizes.count && lowerBufferHeight < minLowerBufferSize; idx++) {
|
||||||
|
lowerBuffer++;
|
||||||
|
lowerBufferHeight += [nodeSizes[idx] CGSizeValue].height;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NSMakeRange(visibleRange.location - upperBuffer,
|
||||||
|
visibleRange.length + upperBuffer + lowerBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setVisibleRange:(NSRange)visibleRange
|
||||||
|
{
|
||||||
|
if (NSEqualRanges(_visibleRange, visibleRange))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ASDisplayNodeAssert(ASRangeIsValid(visibleRange), @"invalid argument");
|
||||||
|
NSRange previouslyVisible = ASRangeIsValid(_visibleRange) ? _visibleRange : visibleRange;
|
||||||
|
_visibleRange = visibleRange;
|
||||||
|
|
||||||
|
// figure out where we're going, because that's where the bulk of the working range needs to be
|
||||||
|
NSInteger scrollDelta = _visibleRange.location - previouslyVisible.location;
|
||||||
|
if (scrollDelta < 0)
|
||||||
|
_scrollDirection = ASScrollDirectionUp;
|
||||||
|
if (scrollDelta > 0)
|
||||||
|
_scrollDirection = ASScrollDirectionDown;
|
||||||
|
|
||||||
|
[self recalculateWorkingRange];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)recalculateWorkingRange
|
||||||
|
{
|
||||||
|
NSRange workingRange = ASCalculateWorkingRange(_tuningParameters,
|
||||||
|
_scrollDirection,
|
||||||
|
_visibleRange,
|
||||||
|
_nodeSizes,
|
||||||
|
[_delegate rangeControllerViewportSize:self]);
|
||||||
|
[self setWorkingRange:workingRange];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setWorkingRange:(NSRange)newWorkingRange
|
||||||
|
{
|
||||||
|
if (NSEqualRanges(_workingRange, newWorkingRange))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// the working range is a superset of the visible range, but we only care about offscreen nodes
|
||||||
|
ASDisplayNodeAssert(NSEqualRanges(_visibleRange, NSIntersectionRange(_visibleRange, newWorkingRange)), @"logic error");
|
||||||
|
NSOrderedSet *visibleIndexPaths = [NSOrderedSet orderedSetWithArray:[self indexPathsForRange:_visibleRange]];
|
||||||
|
NSOrderedSet *oldWorkingIndexPaths = ASCopySetMinusSet(_workingIndexPaths, visibleIndexPaths);
|
||||||
|
NSOrderedSet *newWorkingIndexPaths = ASCopySetMinusSet([NSOrderedSet orderedSetWithArray:[self indexPathsForRange:newWorkingRange]], visibleIndexPaths);
|
||||||
|
|
||||||
|
// update bookkeeping for visible nodes; these will be removed from the working range later in -configureContentView::
|
||||||
|
[_workingIndexPaths minusOrderedSet:visibleIndexPaths];
|
||||||
|
|
||||||
|
// evict nodes that have left the working range (i.e., those that are in the old working range but not the new one)
|
||||||
|
NSOrderedSet *removedIndexPaths = ASCopySetMinusSet(oldWorkingIndexPaths, newWorkingIndexPaths);
|
||||||
|
for (NSIndexPath *indexPath in removedIndexPaths) {
|
||||||
|
ASCellNode *node = [self sizedNodeForIndexPath:indexPath];
|
||||||
|
ASDisplayNodeAssert(node, @"an unsized node should never have entered the working range");
|
||||||
|
[self removeNodeFromWorkingView:node];
|
||||||
|
}
|
||||||
|
|
||||||
|
// add nodes that have entered the working range (i.e., those that are in the new working range but not the old one)
|
||||||
|
NSOrderedSet *addedIndexPaths = ASCopySetMinusSet(newWorkingIndexPaths, oldWorkingIndexPaths);
|
||||||
|
for (NSIndexPath *indexPath in addedIndexPaths) {
|
||||||
|
// if a node in the working range is still sizing, the sizing logic will add it to the working range for us later
|
||||||
|
ASCellNode *node = [self sizedNodeForIndexPath:indexPath];
|
||||||
|
if (node) {
|
||||||
|
[self moveNodeToWorkingView:node];
|
||||||
|
} else {
|
||||||
|
ASDisplayNodeAssert(_sizedNodeCount != _totalNodeCount, @"logic error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_workingRange = newWorkingRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark Async sizing.
|
||||||
|
|
||||||
|
- (ASCellNode *)sizedNodeForIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
if ([self indexForIndexPath:indexPath] >= _sizedNodeCount) {
|
||||||
|
// this node hasn't been sized yet
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
// work around applebug: a UIMutableIndexPath with row r and section s is not considered equal to an NSIndexPath with
|
||||||
|
// row r and section s, so we cannot use the provided indexPath directly as a dictionary index.
|
||||||
|
ASCellNode *sizedNode = _nodes[[NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section]];
|
||||||
|
ASDisplayNodeAssert(sizedNode, @"this node should be sized but doesn't even exist");
|
||||||
|
ASDisplayNodeAssert([sizedNode.asyncdisplaykit_indexPath isEqual:indexPath], @"this node has the wrong index path");
|
||||||
|
[sizedNode recursiveSetPreventOrCancelDisplay:NO];
|
||||||
|
return sizedNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)sizeNextBlock
|
||||||
|
{
|
||||||
|
// concurrently size as many nodes as the CPU allows
|
||||||
|
static const NSInteger blockSize = [[NSProcessInfo processInfo] processorCount];
|
||||||
|
NSRange sizingRange = NSMakeRange(_sizedNodeCount, MIN(blockSize, _totalNodeCount - _sizedNodeCount));
|
||||||
|
|
||||||
|
// manage sizing on a throwaway background queue; we'll be blocking it
|
||||||
|
dispatch_async(dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT), ^{
|
||||||
|
dispatch_group_t group = dispatch_group_create();
|
||||||
|
|
||||||
|
NSArray *indexPaths = [self indexPathsForRange:sizingRange];
|
||||||
|
for (NSIndexPath *indexPath in indexPaths) {
|
||||||
|
ASCellNode *node = [_delegate rangeController:self nodeForIndexPath:indexPath];
|
||||||
|
node.asyncdisplaykit_indexPath = indexPath;
|
||||||
|
_nodes[indexPath] = node;
|
||||||
|
|
||||||
|
dispatch_group_async(group, [ASRangeController sizingQueue], ^{
|
||||||
|
[node measure:[_delegate rangeController:self constrainedSizeForNodeAtIndexPath:indexPath]];
|
||||||
|
node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for all sizing to finish, then bounce back to main
|
||||||
|
// TODO consider using a semaphore here -- we currently don't size nodes while updating the working range
|
||||||
|
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
// update sized node information
|
||||||
|
_sizedNodeCount = NSMaxRange(sizingRange);
|
||||||
|
for (NSIndexPath *indexPath in indexPaths) {
|
||||||
|
ASCellNode *node = _nodes[indexPath];
|
||||||
|
_nodeSizes[[self indexForIndexPath:indexPath]] = [NSValue valueWithCGSize:node.calculatedSize];
|
||||||
|
}
|
||||||
|
ASDisplayNodeAssert(_nodeSizes.count == _sizedNodeCount, @"logic error");
|
||||||
|
|
||||||
|
// update the working range
|
||||||
|
if (ASRangeIsValid(_visibleRange)) {
|
||||||
|
[self recalculateWorkingRange];
|
||||||
|
}
|
||||||
|
|
||||||
|
// delegateify
|
||||||
|
[_delegate rangeController:self didSizeNodesWithIndexPaths:indexPaths];
|
||||||
|
|
||||||
|
// kick off the next block
|
||||||
|
if (_sizedNodeCount < _totalNodeCount) {
|
||||||
|
[self performSelector:@selector(sizeNextBlock) withObject:NULL afterDelay:0];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
#pragma mark Editing.
|
||||||
|
|
||||||
|
static BOOL ASIndexPathsAreSequential(NSIndexPath *first, NSIndexPath *second)
|
||||||
|
{
|
||||||
|
BOOL row = (second.row == first.row + 1 && second.section == first.section);
|
||||||
|
BOOL section = (second.row == 0 && second.section == first.section + 1);
|
||||||
|
return row || section;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)appendNodesWithIndexPaths:(NSArray *)indexPaths
|
||||||
|
{
|
||||||
|
// sanity-check input
|
||||||
|
// TODO this is proof-of-concept-quality, expand validation when fleshing out update / editing support
|
||||||
|
BOOL indexPathsAreValid = ASIndexPathsAreSequential([self indexPathForIndex:_totalNodeCount - 1],
|
||||||
|
[indexPaths firstObject]);
|
||||||
|
if (!indexPaths || !indexPaths.count || !indexPathsAreValid) {
|
||||||
|
ASDisplayNodeAssert(NO, @"invalid argument");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update all the things
|
||||||
|
void (^updateBlock)() = ^{
|
||||||
|
BOOL isSizing = (_sizedNodeCount < _totalNodeCount);
|
||||||
|
NSInteger expectedTotalNodeCount = _totalNodeCount + indexPaths.count;
|
||||||
|
|
||||||
|
[self recalculateDataSourceCounts];
|
||||||
|
ASDisplayNodeAssert(_totalNodeCount == expectedTotalNodeCount, @"data source error");
|
||||||
|
|
||||||
|
if (!isSizing) {
|
||||||
|
// the last sizing pass completely finished, start a new one
|
||||||
|
[self sizeNextBlock];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// trampoline to main if necessary, we don't have locks on _sectionCounts / _sectionOffsets / _totalNodeCount
|
||||||
|
if (![NSThread isMainThread]) {
|
||||||
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||||
|
updateBlock();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
updateBlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@end
|
||||||
15
AsyncDisplayKit/Private/ASRangeControllerInternal.h
Normal file
15
AsyncDisplayKit/Private/ASRangeControllerInternal.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/* Copyright (c) 2014-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "ASCellNode.h"
|
||||||
|
|
||||||
|
@interface ASCellNode (ASRangeController)
|
||||||
|
|
||||||
|
@property (nonatomic, copy) NSIndexPath *asyncdisplaykit_indexPath;
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -64,9 +64,8 @@ static dispatch_block_t modifyMethodByAddingPrologueBlockAndReturnCleanupBlock(C
|
|||||||
|
|
||||||
NSMutableArray *_swizzleCleanupBlocks;
|
NSMutableArray *_swizzleCleanupBlocks;
|
||||||
|
|
||||||
NSCountedSet *_willAppearCounts;
|
NSCountedSet *_willEnterHierarchyCounts;
|
||||||
NSCountedSet *_willDisappearCounts;
|
NSCountedSet *_didExitHierarchyCounts;
|
||||||
NSCountedSet *_didDisappearCounts;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,20 +76,15 @@ static dispatch_block_t modifyMethodByAddingPrologueBlockAndReturnCleanupBlock(C
|
|||||||
_swizzleCleanupBlocks = [[NSMutableArray alloc] init];
|
_swizzleCleanupBlocks = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
// Using this instead of mocks. Count # of times method called
|
// Using this instead of mocks. Count # of times method called
|
||||||
_willAppearCounts = [[NSCountedSet alloc] init];
|
_willEnterHierarchyCounts = [[NSCountedSet alloc] init];
|
||||||
_willDisappearCounts = [[NSCountedSet alloc] init];
|
_didExitHierarchyCounts = [[NSCountedSet alloc] init];
|
||||||
_didDisappearCounts = [[NSCountedSet alloc] init];
|
|
||||||
|
|
||||||
dispatch_block_t cleanupBlock = modifyMethodByAddingPrologueBlockAndReturnCleanupBlock([ASDisplayNode class], @selector(willAppear), ^(id blockSelf){
|
dispatch_block_t cleanupBlock = modifyMethodByAddingPrologueBlockAndReturnCleanupBlock([ASDisplayNode class], @selector(willEnterHierarchy), ^(id blockSelf){
|
||||||
[_willAppearCounts addObject:blockSelf];
|
[_willEnterHierarchyCounts addObject:blockSelf];
|
||||||
});
|
});
|
||||||
[_swizzleCleanupBlocks addObject:cleanupBlock];
|
[_swizzleCleanupBlocks addObject:cleanupBlock];
|
||||||
cleanupBlock = modifyMethodByAddingPrologueBlockAndReturnCleanupBlock([ASDisplayNode class], @selector(didDisappear), ^(id blockSelf){
|
cleanupBlock = modifyMethodByAddingPrologueBlockAndReturnCleanupBlock([ASDisplayNode class], @selector(didExitHierarchy), ^(id blockSelf){
|
||||||
[_didDisappearCounts addObject:blockSelf];
|
[_didExitHierarchyCounts addObject:blockSelf];
|
||||||
});
|
|
||||||
[_swizzleCleanupBlocks addObject:cleanupBlock];
|
|
||||||
cleanupBlock = modifyMethodByAddingPrologueBlockAndReturnCleanupBlock([ASDisplayNode class], @selector(willDisappear), ^(id blockSelf){
|
|
||||||
[_willDisappearCounts addObject:blockSelf];
|
|
||||||
});
|
});
|
||||||
[_swizzleCleanupBlocks addObject:cleanupBlock];
|
[_swizzleCleanupBlocks addObject:cleanupBlock];
|
||||||
}
|
}
|
||||||
@@ -106,12 +100,10 @@ static dispatch_block_t modifyMethodByAddingPrologueBlockAndReturnCleanupBlock(C
|
|||||||
[_swizzleCleanupBlocks release];
|
[_swizzleCleanupBlocks release];
|
||||||
_swizzleCleanupBlocks = nil;
|
_swizzleCleanupBlocks = nil;
|
||||||
|
|
||||||
[_willAppearCounts release];
|
[_willEnterHierarchyCounts release];
|
||||||
_willAppearCounts = nil;
|
_willEnterHierarchyCounts = nil;
|
||||||
[_willDisappearCounts release];
|
[_didExitHierarchyCounts release];
|
||||||
_willDisappearCounts = nil;
|
_didExitHierarchyCounts = nil;
|
||||||
[_didDisappearCounts release];
|
|
||||||
_didDisappearCounts = nil;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testAppearanceMethodsCalledWithRootNodeInWindowLayer
|
- (void)testAppearanceMethodsCalledWithRootNodeInWindowLayer
|
||||||
@@ -140,14 +132,12 @@ static dispatch_block_t modifyMethodByAddingPrologueBlockAndReturnCleanupBlock(C
|
|||||||
[superview addSubview:n.view];
|
[superview addSubview:n.view];
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:n], 0u, @"willAppear erroneously called");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:n], 0u, @"willEnterHierarchy erroneously called");
|
||||||
XCTAssertEqual([_willDisappearCounts countForObject:n], 0u, @"willDisppear erroneously called");
|
XCTAssertEqual([_didExitHierarchyCounts countForObject:n], 0u, @"didExitHierarchy erroneously called");
|
||||||
XCTAssertEqual([_didDisappearCounts countForObject:n], 0u, @"didDisappear erroneously called");
|
|
||||||
|
|
||||||
[window addSubview:superview];
|
[window addSubview:superview];
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:n], 1u, @"willAppear not called when node's view added to hierarchy");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:n], 1u, @"willEnterHierarchy not called when node's view added to hierarchy");
|
||||||
XCTAssertEqual([_willDisappearCounts countForObject:n], 0u, @"willDisppear erroneously called");
|
XCTAssertEqual([_didExitHierarchyCounts countForObject:n], 0u, @"didExitHierarchy erroneously called");
|
||||||
XCTAssertEqual([_didDisappearCounts countForObject:n], 0u, @"didDisappear erroneously called");
|
|
||||||
|
|
||||||
XCTAssertTrue(n.inWindow, @"Node should be visible");
|
XCTAssertTrue(n.inWindow, @"Node should be visible");
|
||||||
|
|
||||||
@@ -159,9 +149,8 @@ static dispatch_block_t modifyMethodByAddingPrologueBlockAndReturnCleanupBlock(C
|
|||||||
|
|
||||||
XCTAssertFalse(n.inWindow, @"Node should be not visible");
|
XCTAssertFalse(n.inWindow, @"Node should be not visible");
|
||||||
|
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:n], 1u, @"willAppear not called when node's view added to hierarchy");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:n], 1u, @"willEnterHierarchy not called when node's view added to hierarchy");
|
||||||
XCTAssertEqual([_willDisappearCounts countForObject:n], 1u, @"willDisppear erroneously called");
|
XCTAssertEqual([_didExitHierarchyCounts countForObject:n], 1u, @"didExitHierarchy erroneously called");
|
||||||
XCTAssertEqual([_didDisappearCounts countForObject:n], 1u, @"didDisappear erroneously called");
|
|
||||||
|
|
||||||
[superview release];
|
[superview release];
|
||||||
[window release];
|
[window release];
|
||||||
@@ -198,11 +187,11 @@ static dispatch_block_t modifyMethodByAddingPrologueBlockAndReturnCleanupBlock(C
|
|||||||
[window addSubview:parent.view];
|
[window addSubview:parent.view];
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:parent], 1u, @"Should have -willAppear called once");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:parent], 1u, @"Should have -willEnterHierarchy called once");
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:a], 1u, @"Should have -willAppear called once");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:a], 1u, @"Should have -willEnterHierarchy called once");
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:b], 0u, @"Should not have appeared yet");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:b], 0u, @"Should not have appeared yet");
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:aa], 0u, @"Should not have appeared yet");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:aa], 0u, @"Should not have appeared yet");
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:ab], 0u, @"Should not have appeared yet");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:ab], 0u, @"Should not have appeared yet");
|
||||||
|
|
||||||
XCTAssertTrue(parent.inWindow, @"Should be visible");
|
XCTAssertTrue(parent.inWindow, @"Should be visible");
|
||||||
XCTAssertTrue(a.inWindow, @"Should be visible");
|
XCTAssertTrue(a.inWindow, @"Should be visible");
|
||||||
@@ -221,11 +210,11 @@ static dispatch_block_t modifyMethodByAddingPrologueBlockAndReturnCleanupBlock(C
|
|||||||
XCTAssertTrue(aa.inWindow, @"Nothing should be visible");
|
XCTAssertTrue(aa.inWindow, @"Nothing should be visible");
|
||||||
XCTAssertTrue(ab.inWindow, @"Nothing should be visible");
|
XCTAssertTrue(ab.inWindow, @"Nothing should be visible");
|
||||||
|
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:parent], 1u, @"Should have -willAppear called once");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:parent], 1u, @"Should have -willEnterHierarchy called once");
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:a], 1u, @"Should have -willAppear called once");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:a], 1u, @"Should have -willEnterHierarchy called once");
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:b], 1u, @"Should have -willAppear called once");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:b], 1u, @"Should have -willEnterHierarchy called once");
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:aa], 1u, @"Should have -willAppear called once");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:aa], 1u, @"Should have -willEnterHierarchy called once");
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:ab], 1u, @"Should have -willAppear called once");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:ab], 1u, @"Should have -willEnterHierarchy called once");
|
||||||
|
|
||||||
if (isLayerBacked) {
|
if (isLayerBacked) {
|
||||||
[parent.layer removeFromSuperlayer];
|
[parent.layer removeFromSuperlayer];
|
||||||
@@ -233,12 +222,6 @@ static dispatch_block_t modifyMethodByAddingPrologueBlockAndReturnCleanupBlock(C
|
|||||||
[parent.view removeFromSuperview];
|
[parent.view removeFromSuperview];
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssertEqual([_willDisappearCounts countForObject:parent], 1u, @"Should disappear properly");
|
|
||||||
XCTAssertEqual([_willDisappearCounts countForObject:a], 1u, @"Should disappear properly");
|
|
||||||
XCTAssertEqual([_willDisappearCounts countForObject:b], 1u, @"Should disappear properly");
|
|
||||||
XCTAssertEqual([_willDisappearCounts countForObject:aa], 1u, @"Should disappear properly");
|
|
||||||
XCTAssertEqual([_willDisappearCounts countForObject:ab], 1u, @"Should disappear properly");
|
|
||||||
|
|
||||||
XCTAssertFalse(parent.inWindow, @"Nothing should be visible");
|
XCTAssertFalse(parent.inWindow, @"Nothing should be visible");
|
||||||
XCTAssertFalse(a.inWindow, @"Nothing should be visible");
|
XCTAssertFalse(a.inWindow, @"Nothing should be visible");
|
||||||
XCTAssertFalse(b.inWindow, @"Nothing should be visible");
|
XCTAssertFalse(b.inWindow, @"Nothing should be visible");
|
||||||
@@ -338,8 +321,8 @@ static dispatch_block_t modifyMethodByAddingPrologueBlockAndReturnCleanupBlock(C
|
|||||||
XCTAssertFalse(childSubnode.inWindow, @"Should not yet be visible");
|
XCTAssertFalse(childSubnode.inWindow, @"Should not yet be visible");
|
||||||
XCTAssertFalse(childSubnode.inWindow, @"Should not yet be visible");
|
XCTAssertFalse(childSubnode.inWindow, @"Should not yet be visible");
|
||||||
|
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:child], 0u, @"Should not have -willAppear called");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:child], 0u, @"Should not have -willEnterHierarchy called");
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:childSubnode], 0u, @"Should not have -willAppear called");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:childSubnode], 0u, @"Should not have -willEnterHierarchy called");
|
||||||
|
|
||||||
if (isLayerBacked) {
|
if (isLayerBacked) {
|
||||||
[window.layer addSublayer:parentA.layer];
|
[window.layer addSublayer:parentA.layer];
|
||||||
@@ -354,8 +337,8 @@ static dispatch_block_t modifyMethodByAddingPrologueBlockAndReturnCleanupBlock(C
|
|||||||
XCTAssertTrue(child.inWindow, @"Should be visible after parent added to window");
|
XCTAssertTrue(child.inWindow, @"Should be visible after parent added to window");
|
||||||
XCTAssertTrue(childSubnode.inWindow, @"Should be visible after parent added to window");
|
XCTAssertTrue(childSubnode.inWindow, @"Should be visible after parent added to window");
|
||||||
|
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:child], 1u, @"Should have -willAppear called once");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:child], 1u, @"Should have -willEnterHierarchy called once");
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:childSubnode], 1u, @"Should have -willAppear called once");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:childSubnode], 1u, @"Should have -willEnterHierarchy called once");
|
||||||
|
|
||||||
// Move subnode from A to B
|
// Move subnode from A to B
|
||||||
if (useManualDisable) {
|
if (useManualDisable) {
|
||||||
@@ -372,9 +355,7 @@ static dispatch_block_t modifyMethodByAddingPrologueBlockAndReturnCleanupBlock(C
|
|||||||
ASDisplayNodeEnableHierarchyNotifications(child);
|
ASDisplayNodeEnableHierarchyNotifications(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:child], 1u, @"Should not have -willAppear called when moving child around in hierarchy");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:child], 1u, @"Should not have -willEnterHierarchy called when moving child around in hierarchy");
|
||||||
XCTAssertEqual([_willDisappearCounts countForObject:child], 0u, @"Should not have -willDisappear called when moving child around in hierarchy");
|
|
||||||
XCTAssertEqual([_willDisappearCounts countForObject:childSubnode], 0u, @"Subnode should not have -willDisappear called when moving parent (child) around in hierarchy");
|
|
||||||
|
|
||||||
// Move subnode back to A
|
// Move subnode back to A
|
||||||
if (useManualDisable) {
|
if (useManualDisable) {
|
||||||
@@ -392,15 +373,12 @@ static dispatch_block_t modifyMethodByAddingPrologueBlockAndReturnCleanupBlock(C
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:child], 1u, @"Should not have -willAppear called when moving child around in hierarchy");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:child], 1u, @"Should not have -willEnterHierarchy called when moving child around in hierarchy");
|
||||||
XCTAssertEqual([_willDisappearCounts countForObject:child], 0u, @"Should not have -willDisappear called when moving child around in hierarchy");
|
|
||||||
|
|
||||||
// Finally, remove subnode
|
// Finally, remove subnode
|
||||||
[child removeFromSupernode];
|
[child removeFromSupernode];
|
||||||
|
|
||||||
XCTAssertEqual([_willAppearCounts countForObject:child], 1u, @"Should appear and disappear just once");
|
XCTAssertEqual([_willEnterHierarchyCounts countForObject:child], 1u, @"Should appear and disappear just once");
|
||||||
XCTAssertEqual([_willDisappearCounts countForObject:child], 1u, @"Should appear and disappear just once");
|
|
||||||
XCTAssertEqual([_willDisappearCounts countForObject:childSubnode], 1u, @"Should appear and disappear just once");
|
|
||||||
|
|
||||||
// Make sure that we don't leave these unbalanced
|
// Make sure that we don't leave these unbalanced
|
||||||
XCTAssertFalse([child __visibilityNotificationsDisabled], @"Unbalanced visibility notifications calls");
|
XCTAssertFalse([child __visibilityNotificationsDisabled], @"Unbalanced visibility notifications calls");
|
||||||
@@ -442,9 +420,8 @@ static dispatch_block_t modifyMethodByAddingPrologueBlockAndReturnCleanupBlock(C
|
|||||||
{
|
{
|
||||||
DeclareNodeNamed(n);
|
DeclareNodeNamed(n);
|
||||||
|
|
||||||
XCTAssertThrows([n willAppear], @"Should not allow manually calling appearance methods.");
|
XCTAssertThrows([n willEnterHierarchy], @"Should not allow manually calling appearance methods.");
|
||||||
XCTAssertThrows([n willDisappear], @"Should not allow manually calling appearance methods.");
|
XCTAssertThrows([n didExitHierarchy], @"Should not allow manually calling appearance methods.");
|
||||||
XCTAssertThrows([n didDisappear], @"Should not allow manually calling appearance methods.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -50,12 +50,12 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
|||||||
|
|
||||||
#define XCTAssertNodesLoaded(nodes ...) \
|
#define XCTAssertNodesLoaded(nodes ...) \
|
||||||
for (ASDisplayNode *n in @[ nodes ]) {\
|
for (ASDisplayNode *n in @[ nodes ]) {\
|
||||||
XCTAssertTrue(n.isViewLoaded, @"%@ should be loaded", n.name);\
|
XCTAssertTrue(n.nodeLoaded, @"%@ should be loaded", n.name);\
|
||||||
}
|
}
|
||||||
|
|
||||||
#define XCTAssertNodesNotLoaded(nodes ...) \
|
#define XCTAssertNodesNotLoaded(nodes ...) \
|
||||||
for (ASDisplayNode *n in @[ nodes ]) {\
|
for (ASDisplayNode *n in @[ nodes ]) {\
|
||||||
XCTAssertFalse(n.isViewLoaded, @"%@ should not be loaded", n.name);\
|
XCTAssertFalse(n.nodeLoaded, @"%@ should not be loaded", n.name);\
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
|||||||
- (void)checkValuesMatchDefaults:(ASDisplayNode *)node isLayerBacked:(BOOL)isLayerBacked
|
- (void)checkValuesMatchDefaults:(ASDisplayNode *)node isLayerBacked:(BOOL)isLayerBacked
|
||||||
{
|
{
|
||||||
NSString *targetName = isLayerBacked ? @"layer" : @"view";
|
NSString *targetName = isLayerBacked ? @"layer" : @"view";
|
||||||
NSString *hasLoadedView = node.isViewLoaded ? @"with view" : [NSString stringWithFormat:@"after loading %@", targetName];
|
NSString *hasLoadedView = node.nodeLoaded ? @"with view" : [NSString stringWithFormat:@"after loading %@", targetName];
|
||||||
|
|
||||||
id rgbBlackCGColorIdPtr = (id)[UIColor colorWithRed:0 green:0 blue:0 alpha:1].CGColor;
|
id rgbBlackCGColorIdPtr = (id)[UIColor colorWithRed:0 green:0 blue:0 alpha:1].CGColor;
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
|||||||
[self checkValuesMatchDefaults:node isLayerBacked:isLayerBacked];
|
[self checkValuesMatchDefaults:node isLayerBacked:isLayerBacked];
|
||||||
|
|
||||||
[node layer]; // Force either view or layer loading
|
[node layer]; // Force either view or layer loading
|
||||||
XCTAssertTrue(node.isViewLoaded, @"Didn't load view");
|
XCTAssertTrue(node.nodeLoaded, @"Didn't load view");
|
||||||
|
|
||||||
// Assert that the values can be fetched from the node after the view is realized.
|
// Assert that the values can be fetched from the node after the view is realized.
|
||||||
[self checkValuesMatchDefaults:node isLayerBacked:isLayerBacked];
|
[self checkValuesMatchDefaults:node isLayerBacked:isLayerBacked];
|
||||||
@@ -220,7 +220,7 @@ for (ASDisplayNode *n in @[ nodes ]) {\
|
|||||||
- (void)checkValuesMatchSetValues:(ASDisplayNode *)node isLayerBacked:(BOOL)isLayerBacked
|
- (void)checkValuesMatchSetValues:(ASDisplayNode *)node isLayerBacked:(BOOL)isLayerBacked
|
||||||
{
|
{
|
||||||
NSString *targetName = isLayerBacked ? @"layer" : @"view";
|
NSString *targetName = isLayerBacked ? @"layer" : @"view";
|
||||||
NSString *hasLoadedView = node.isViewLoaded ? @"with view" : [NSString stringWithFormat:@"after loading %@", targetName];
|
NSString *hasLoadedView = node.nodeLoaded ? @"with view" : [NSString stringWithFormat:@"after loading %@", targetName];
|
||||||
|
|
||||||
XCTAssertEqual(isLayerBacked, node.isLayerBacked, @"isLayerBacked broken %@", hasLoadedView);
|
XCTAssertEqual(isLayerBacked, node.isLayerBacked, @"isLayerBacked broken %@", hasLoadedView);
|
||||||
XCTAssertEqualObjects((id)[self bogusImage].CGImage, (id)node.contents, @"contents broken %@", hasLoadedView);
|
XCTAssertEqualObjects((id)[self bogusImage].CGImage, (id)node.contents, @"contents broken %@", hasLoadedView);
|
||||||
@@ -853,7 +853,7 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
|||||||
|
|
||||||
DeclareNodeNamed(d);
|
DeclareNodeNamed(d);
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
XCTAssertFalse(d.isViewLoaded, @"Should not yet be loaded");
|
XCTAssertFalse(d.nodeLoaded, @"Should not yet be loaded");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shut the type mismatch up
|
// Shut the type mismatch up
|
||||||
@@ -1049,8 +1049,8 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
|||||||
XCTAssertTrue(replaceMe.supernode == parent, @"didn't remove old node");
|
XCTAssertTrue(replaceMe.supernode == parent, @"didn't remove old node");
|
||||||
XCTAssertTrue(first.supernode == nil, @"oops, added node too soon");
|
XCTAssertTrue(first.supernode == nil, @"oops, added node too soon");
|
||||||
XCTAssertTrue(second.supernode == nil, @"oops, added node too soon");
|
XCTAssertTrue(second.supernode == nil, @"oops, added node too soon");
|
||||||
XCTAssertFalse(first.isViewLoaded, @"first view loaded too soon");
|
XCTAssertFalse(first.nodeLoaded, @"first view loaded too soon");
|
||||||
XCTAssertFalse(second.isViewLoaded, @"second view loaded too soon");
|
XCTAssertFalse(second.nodeLoaded, @"second view loaded too soon");
|
||||||
|
|
||||||
// Allow first to complete, but verify that the nodes are nil, indicating cancellation (asserts are in blocks above)
|
// Allow first to complete, but verify that the nodes are nil, indicating cancellation (asserts are in blocks above)
|
||||||
dispatch_semaphore_signal(allowFirst);
|
dispatch_semaphore_signal(allowFirst);
|
||||||
@@ -1115,7 +1115,7 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
|||||||
DeclareNodeNamed(d);
|
DeclareNodeNamed(d);
|
||||||
d.layerBacked = isLayerBacked;
|
d.layerBacked = isLayerBacked;
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
XCTAssertFalse(d.isViewLoaded, @"Should not yet be loaded");
|
XCTAssertFalse(d.nodeLoaded, @"Should not yet be loaded");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shut the type mismatch up
|
// Shut the type mismatch up
|
||||||
@@ -1542,7 +1542,7 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
|||||||
ASDisplayNode *scrollNode = [[ASDisplayNode alloc] initWithViewClass:[UIScrollView class]];
|
ASDisplayNode *scrollNode = [[ASDisplayNode alloc] initWithViewClass:[UIScrollView class]];
|
||||||
|
|
||||||
XCTAssertFalse(scrollNode.isLayerBacked, @"Can't be layer backed");
|
XCTAssertFalse(scrollNode.isLayerBacked, @"Can't be layer backed");
|
||||||
XCTAssertFalse(scrollNode.isViewLoaded, @"Shouldn't have a view yet");
|
XCTAssertFalse(scrollNode.nodeLoaded, @"Shouldn't have a view yet");
|
||||||
|
|
||||||
scrollNode.frame = CGRectMake(12, 52, 100, 53);
|
scrollNode.frame = CGRectMake(12, 52, 100, 53);
|
||||||
scrollNode.alpha = 0.5;
|
scrollNode.alpha = 0.5;
|
||||||
@@ -1557,7 +1557,7 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
|
|||||||
ASDisplayNode *transformNode = [[ASDisplayNode alloc] initWithLayerClass:[CATransformLayer class]];
|
ASDisplayNode *transformNode = [[ASDisplayNode alloc] initWithLayerClass:[CATransformLayer class]];
|
||||||
|
|
||||||
XCTAssertTrue(transformNode.isLayerBacked, @"Created with layer class => should be layer-backed by default");
|
XCTAssertTrue(transformNode.isLayerBacked, @"Created with layer class => should be layer-backed by default");
|
||||||
XCTAssertFalse(transformNode.isViewLoaded, @"Shouldn't have a view yet");
|
XCTAssertFalse(transformNode.nodeLoaded, @"Shouldn't have a view yet");
|
||||||
|
|
||||||
transformNode.frame = CGRectMake(12, 52, 100, 53);
|
transformNode.frame = CGRectMake(12, 52, 100, 53);
|
||||||
transformNode.alpha = 0.5;
|
transformNode.alpha = 0.5;
|
||||||
|
|||||||
@@ -91,7 +91,7 @@
|
|||||||
{
|
{
|
||||||
for (NSInteger i = 10; i < 500; i += 50) {
|
for (NSInteger i = 10; i < 500; i += 50) {
|
||||||
CGSize constrainedSize = CGSizeMake(i, i);
|
CGSize constrainedSize = CGSizeMake(i, i);
|
||||||
CGSize calculatedSize = [_textNode sizeToFit:constrainedSize];
|
CGSize calculatedSize = [_textNode measure:constrainedSize];
|
||||||
XCTAssertTrue(calculatedSize.width <= constrainedSize.width, @"Calculated width (%f) should be less than or equal to constrained width (%f)", calculatedSize.width, constrainedSize.width);
|
XCTAssertTrue(calculatedSize.width <= constrainedSize.width, @"Calculated width (%f) should be less than or equal to constrained width (%f)", calculatedSize.width, constrainedSize.width);
|
||||||
XCTAssertTrue(calculatedSize.height <= constrainedSize.height, @"Calculated height (%f) should be less than or equal to constrained height (%f)", calculatedSize.height, constrainedSize.height);
|
XCTAssertTrue(calculatedSize.height <= constrainedSize.height, @"Calculated height (%f) should be less than or equal to constrained height (%f)", calculatedSize.height, constrainedSize.height);
|
||||||
}
|
}
|
||||||
@@ -101,8 +101,8 @@
|
|||||||
{
|
{
|
||||||
for (NSInteger i = 10; i < 500; i += 50) {
|
for (NSInteger i = 10; i < 500; i += 50) {
|
||||||
CGSize constrainedSize = CGSizeMake(i, i);
|
CGSize constrainedSize = CGSizeMake(i, i);
|
||||||
CGSize calculatedSize = [_textNode sizeToFit:constrainedSize];
|
CGSize calculatedSize = [_textNode measure:constrainedSize];
|
||||||
CGSize recalculatedSize = [_textNode sizeToFit:calculatedSize];
|
CGSize recalculatedSize = [_textNode measure:calculatedSize];
|
||||||
|
|
||||||
XCTAssertTrue(CGSizeEqualToSize(calculatedSize, recalculatedSize), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize));
|
XCTAssertTrue(CGSizeEqualToSize(calculatedSize, recalculatedSize), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize));
|
||||||
}
|
}
|
||||||
@@ -112,8 +112,8 @@
|
|||||||
{
|
{
|
||||||
for (CGFloat i = 10; i < 500; i *= 1.3) {
|
for (CGFloat i = 10; i < 500; i *= 1.3) {
|
||||||
CGSize constrainedSize = CGSizeMake(i, i);
|
CGSize constrainedSize = CGSizeMake(i, i);
|
||||||
CGSize calculatedSize = [_textNode sizeToFit:constrainedSize];
|
CGSize calculatedSize = [_textNode measure:constrainedSize];
|
||||||
CGSize recalculatedSize = [_textNode sizeToFit:calculatedSize];
|
CGSize recalculatedSize = [_textNode measure:calculatedSize];
|
||||||
|
|
||||||
XCTAssertTrue(CGSizeEqualToSize(calculatedSize, recalculatedSize), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize));
|
XCTAssertTrue(CGSizeEqualToSize(calculatedSize, recalculatedSize), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize));
|
||||||
}
|
}
|
||||||
@@ -141,7 +141,7 @@
|
|||||||
ASTextNodeTestDelegate *delegate = [ASTextNodeTestDelegate new];
|
ASTextNodeTestDelegate *delegate = [ASTextNodeTestDelegate new];
|
||||||
_textNode.delegate = delegate;
|
_textNode.delegate = delegate;
|
||||||
|
|
||||||
[_textNode sizeToFit:CGSizeMake(100, 100)];
|
[_textNode measure:CGSizeMake(100, 100)];
|
||||||
NSRange returnedLinkRange;
|
NSRange returnedLinkRange;
|
||||||
NSString *returnedAttributeName;
|
NSString *returnedAttributeName;
|
||||||
NSString *returnedLinkAttributeValue = [_textNode linkAttributeValueAtPoint:CGPointMake(3, 3) attributeName:&returnedAttributeName range:&returnedLinkRange];
|
NSString *returnedLinkAttributeValue = [_textNode linkAttributeValueAtPoint:CGPointMake(3, 3) attributeName:&returnedAttributeName range:&returnedLinkRange];
|
||||||
@@ -163,7 +163,7 @@
|
|||||||
ASTextNodeTestDelegate *delegate = [ASTextNodeTestDelegate new];
|
ASTextNodeTestDelegate *delegate = [ASTextNodeTestDelegate new];
|
||||||
_textNode.delegate = delegate;
|
_textNode.delegate = delegate;
|
||||||
|
|
||||||
CGSize calculatedSize = [_textNode sizeToFit:CGSizeMake(100, 100)];
|
CGSize calculatedSize = [_textNode measure:CGSizeMake(100, 100)];
|
||||||
NSRange returnedLinkRange = NSMakeRange(NSNotFound, 0);
|
NSRange returnedLinkRange = NSMakeRange(NSNotFound, 0);
|
||||||
NSRange expectedRange = NSMakeRange(NSNotFound, 0);
|
NSRange expectedRange = NSMakeRange(NSNotFound, 0);
|
||||||
NSString *returnedAttributeName;
|
NSString *returnedAttributeName;
|
||||||
|
|||||||
@@ -83,9 +83,7 @@ Besides _ASDisplayNode_, AsyncDisplayKit has UIKit equivalent classes:
|
|||||||
- _ASControlNode_: a UIButton equivalent
|
- _ASControlNode_: a UIButton equivalent
|
||||||
- _ASTextNode_: a UITextView equivalent, with features like tap highlights, custom truncation strings, gradients, shadows, and tappable links.
|
- _ASTextNode_: a UITextView equivalent, with features like tap highlights, custom truncation strings, gradients, shadows, and tappable links.
|
||||||
- _ASImageNode_: a UIImageView equivalent
|
- _ASImageNode_: a UIImageView equivalent
|
||||||
|
- _ASTableView_: a UITableView subclass that uses nodes instead of cells
|
||||||
Node-aware UITableView and UICollectionView implementations are currently planned, but not yet implemented.
|
|
||||||
|
|
||||||
|
|
||||||
## Subclassing
|
## Subclassing
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user