mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-24 07:05:35 +00:00
[ASRangeController] Debug overlay to show the size and direction of display + fetchData ranges during scrolling. (#2008)
* Rebase ASRangeController diff with master. * fix Table / CollectionNode debug label names - now returns class names rather than generic ASTableNode, ASPagerNode - ASPagerNode will be labeled as ASPagerNodeProxy (not sure how to get around this) * refactor layout code: use ASDk's own resizeableRoundedCorner... methods * Fixes and cleanup for manual layout version of range controller debug overlay. I am working on a layout spec-based version, but it has some issues, so landing this to get the near-term value is probably the best next step. * Remove .orig and .rej files. * One last .orig file to remove. * Final project file cleanup and tweaks to implementation for ASTableNode. * fix build issues * fix arrow directions
This commit is contained in:
committed by
Adlai Holler
parent
7494b114aa
commit
2afd063b10
@@ -17,6 +17,7 @@
|
||||
#import "ASEnvironmentInternal.h"
|
||||
#import "ASInternalHelpers.h"
|
||||
#import "ASCellNode+Internal.h"
|
||||
#import "AsyncDisplayKit+Debug.h"
|
||||
|
||||
#pragma mark - _ASCollectionPendingState
|
||||
|
||||
@@ -171,6 +172,12 @@
|
||||
[self.view clearFetchedData];
|
||||
}
|
||||
|
||||
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState
|
||||
{
|
||||
[super interfaceStateDidChange:newState fromState:oldState];
|
||||
[ASRangeController layoutDebugOverlayIfNeeded];
|
||||
}
|
||||
|
||||
#if ASRangeControllerLoggingEnabled
|
||||
- (void)visibleStateDidChange:(BOOL)isVisible
|
||||
{
|
||||
|
||||
@@ -1080,6 +1080,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
|
||||
return [_dataController nodeAtIndexPath:indexPath];
|
||||
}
|
||||
|
||||
- (NSString *)nameForRangeControllerDataSource
|
||||
{
|
||||
return self.asyncDataSource ? NSStringFromClass([self.asyncDataSource class]) : NSStringFromClass([self class]);
|
||||
}
|
||||
|
||||
#pragma mark - ASRangeControllerDelegate
|
||||
|
||||
- (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
#import "ASInternalHelpers.h"
|
||||
#import "ASCellNode+Internal.h"
|
||||
#import "AsyncDisplayKit+Debug.h"
|
||||
|
||||
#pragma mark - _ASTablePendingState
|
||||
|
||||
@@ -125,6 +126,12 @@
|
||||
[self.view clearFetchedData];
|
||||
}
|
||||
|
||||
- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState
|
||||
{
|
||||
[super interfaceStateDidChange:newState fromState:oldState];
|
||||
[ASRangeController layoutDebugOverlayIfNeeded];
|
||||
}
|
||||
|
||||
#if ASRangeControllerLoggingEnabled
|
||||
- (void)visibleStateDidChange:(BOOL)isVisible
|
||||
{
|
||||
|
||||
@@ -937,6 +937,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
||||
return ASInterfaceStateForDisplayNode(self.tableNode, self.window);
|
||||
}
|
||||
|
||||
- (NSString *)nameForRangeControllerDataSource
|
||||
{
|
||||
return self.asyncDataSource ? NSStringFromClass([self.asyncDataSource class]) : NSStringFromClass([self class]);
|
||||
}
|
||||
|
||||
#pragma mark - ASRangeControllerDelegate
|
||||
|
||||
- (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#import "ASControlNode.h"
|
||||
#import "ASImageNode.h"
|
||||
#import "ASRangeController.h"
|
||||
|
||||
@interface ASImageNode (Debugging)
|
||||
|
||||
@@ -29,16 +30,40 @@
|
||||
@interface ASControlNode (Debugging)
|
||||
|
||||
/**
|
||||
Class method to enable a visualization overlay of the tappable area on the ASControlNode. For app debugging purposes only.
|
||||
NOTE: GESTURE RECOGNIZERS, (including tap gesture recognizers on a control node) WILL NOT BE VISUALIZED!!!
|
||||
Overlay = translucent GREEN color,
|
||||
edges that are clipped by the tappable area of any parent (their bounds + hitTestSlop) in the hierarchy = DARK GREEN BORDERED EDGE,
|
||||
edges that are clipped by clipToBounds = YES of any parent in the hierarchy = ORANGE BORDERED EDGE (may still receive touches beyond
|
||||
overlay rect, but can't be visualized).
|
||||
@param enable Specify YES to make this debug feature enabled when messaging the ASControlNode class.
|
||||
* Class method to enable a visualization overlay of the tappable area on the ASControlNode. For app debugging purposes only.
|
||||
* NOTE: GESTURE RECOGNIZERS, (including tap gesture recognizers on a control node) WILL NOT BE VISUALIZED!!!
|
||||
* Overlay = translucent GREEN color,
|
||||
* edges that are clipped by the tappable area of any parent (their bounds + hitTestSlop) in the hierarchy = DARK GREEN BORDERED EDGE,
|
||||
* edges that are clipped by clipToBounds = YES of any parent in the hierarchy = ORANGE BORDERED EDGE (may still receive touches beyond
|
||||
* overlay rect, but can't be visualized).
|
||||
* @param enable Specify YES to make this debug feature enabled when messaging the ASControlNode class.
|
||||
*/
|
||||
+ (void)setEnableHitTestDebug:(BOOL)enable;
|
||||
+ (BOOL)enableHitTestDebug;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASRangeController (Debugging)
|
||||
|
||||
/**
|
||||
* Class method to enable a visualization overlay of the all ASRangeController's tuning parameters. For dev purposes only.
|
||||
* To use, message ASRangeController in the AppDelegate --> [ASRangeController setShouldShowRangeDebugOverlay:YES];
|
||||
* @param enable Specify YES to make this debug feature enabled when messaging the ASRangeController class.
|
||||
*/
|
||||
+ (void)setShouldShowRangeDebugOverlay:(BOOL)show;
|
||||
+ (BOOL)shouldShowRangeDebugOverlay;
|
||||
|
||||
+ (void)layoutDebugOverlayIfNeeded;
|
||||
|
||||
- (void)addRangeControllerToRangeDebugOverlay;
|
||||
|
||||
- (void)updateRangeController:(ASRangeController *)controller
|
||||
withScrollableDirections:(ASScrollDirection)scrollableDirections
|
||||
scrollDirection:(ASScrollDirection)direction
|
||||
rangeMode:(ASLayoutRangeMode)mode
|
||||
displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters
|
||||
fetchDataTuningParameters:(ASRangeTuningParameters)fetchDataTuningParameters
|
||||
interfaceState:(ASInterfaceState)interfaceState;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -13,6 +13,16 @@
|
||||
#import "AsyncDisplayKit+Debug.h"
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
#import "ASDisplayNodeExtras.h"
|
||||
#import "ASWeakSet.h"
|
||||
#import "UIImage+ASConvenience.h"
|
||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||
#import <AsyncDisplayKit/CGRect+ASConvenience.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/ASTextNode.h>
|
||||
#import <AsyncDisplayKit/ASRangeController.h>
|
||||
|
||||
|
||||
#pragma mark - ASImageNode (Debugging)
|
||||
|
||||
static BOOL __shouldShowImageScalingOverlay = NO;
|
||||
|
||||
@@ -30,6 +40,8 @@ static BOOL __shouldShowImageScalingOverlay = NO;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - ASControlNode (DebuggingInternal)
|
||||
|
||||
static BOOL __enableHitTestDebug = NO;
|
||||
|
||||
@interface ASControlNode (DebuggingInternal)
|
||||
@@ -191,3 +203,551 @@ static BOOL __enableHitTestDebug = NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - ASRangeController (Debugging)
|
||||
|
||||
@interface _ASRangeDebugOverlayView : UIView
|
||||
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
- (void)addRangeController:(ASRangeController *)rangeController;
|
||||
|
||||
- (void)updateRangeController:(ASRangeController *)controller
|
||||
withScrollableDirections:(ASScrollDirection)scrollableDirections
|
||||
scrollDirection:(ASScrollDirection)direction
|
||||
rangeMode:(ASLayoutRangeMode)mode
|
||||
displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters
|
||||
fetchDataTuningParameters:(ASRangeTuningParameters)fetchDataTuningParameters
|
||||
interfaceState:(ASInterfaceState)interfaceState;
|
||||
|
||||
@end
|
||||
|
||||
@interface _ASRangeDebugBarView : UIView
|
||||
|
||||
@property (nonatomic, weak) ASRangeController *rangeController;
|
||||
@property (nonatomic, assign) BOOL destroyOnLayout;
|
||||
@property (nonatomic, strong) NSString *debugString;
|
||||
|
||||
- (instancetype)initWithRangeController:(ASRangeController *)rangeController;
|
||||
|
||||
- (void)updateWithVisibleRatio:(CGFloat)visibleRatio
|
||||
displayRatio:(CGFloat)displayRatio
|
||||
leadingDisplayRatio:(CGFloat)leadingDisplayRatio
|
||||
fetchDataRatio:(CGFloat)fetchDataRatio
|
||||
leadingFetchDataRatio:(CGFloat)leadingFetchDataRatio
|
||||
direction:(ASScrollDirection)direction;
|
||||
|
||||
@end
|
||||
|
||||
static BOOL __shouldShowRangeDebugOverlay = NO;
|
||||
|
||||
@implementation ASRangeController (Debugging)
|
||||
|
||||
+ (void)setShouldShowRangeDebugOverlay:(BOOL)show
|
||||
{
|
||||
__shouldShowRangeDebugOverlay = show;
|
||||
}
|
||||
|
||||
+ (BOOL)shouldShowRangeDebugOverlay
|
||||
{
|
||||
return __shouldShowRangeDebugOverlay;
|
||||
}
|
||||
|
||||
+ (void)layoutDebugOverlayIfNeeded
|
||||
{
|
||||
[[_ASRangeDebugOverlayView sharedInstance] setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)addRangeControllerToRangeDebugOverlay
|
||||
{
|
||||
[[_ASRangeDebugOverlayView sharedInstance] addRangeController:self];
|
||||
}
|
||||
|
||||
- (void)updateRangeController:(ASRangeController *)controller
|
||||
withScrollableDirections:(ASScrollDirection)scrollableDirections
|
||||
scrollDirection:(ASScrollDirection)direction
|
||||
rangeMode:(ASLayoutRangeMode)mode
|
||||
displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters
|
||||
fetchDataTuningParameters:(ASRangeTuningParameters)fetchDataTuningParameters
|
||||
interfaceState:(ASInterfaceState)interfaceState
|
||||
{
|
||||
[[_ASRangeDebugOverlayView sharedInstance] updateRangeController:controller
|
||||
withScrollableDirections:scrollableDirections
|
||||
scrollDirection:direction
|
||||
rangeMode:mode
|
||||
displayTuningParameters:displayTuningParameters
|
||||
fetchDataTuningParameters:fetchDataTuningParameters
|
||||
interfaceState:interfaceState];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark _ASRangeDebugOverlayView
|
||||
|
||||
@interface _ASRangeDebugOverlayView () <UIGestureRecognizerDelegate>
|
||||
@end
|
||||
|
||||
@implementation _ASRangeDebugOverlayView
|
||||
{
|
||||
NSMutableArray *_rangeControllerViews;
|
||||
NSInteger _newControllerCount;
|
||||
NSInteger _removeControllerCount;
|
||||
BOOL _animating;
|
||||
}
|
||||
|
||||
+ (UIWindow *)keyWindow
|
||||
{
|
||||
// hack to work around app extensions not having UIApplication...not sure of a better way to do this?
|
||||
return [[NSClassFromString(@"UIApplication") sharedApplication] keyWindow];
|
||||
}
|
||||
|
||||
+ (instancetype)sharedInstance
|
||||
{
|
||||
static _ASRangeDebugOverlayView *__rangeDebugOverlay = nil;
|
||||
|
||||
if (!__rangeDebugOverlay && [ASRangeController shouldShowRangeDebugOverlay]) {
|
||||
__rangeDebugOverlay = [[self alloc] initWithFrame:CGRectZero];
|
||||
[[self keyWindow] addSubview:__rangeDebugOverlay];
|
||||
}
|
||||
|
||||
return __rangeDebugOverlay;
|
||||
}
|
||||
|
||||
#define OVERLAY_INSET 10
|
||||
#define OVERLAY_SCALE 3
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
|
||||
if (self) {
|
||||
_rangeControllerViews = [[NSMutableArray alloc] init];
|
||||
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4];
|
||||
self.layer.zPosition = 1000;
|
||||
self.clipsToBounds = YES;
|
||||
|
||||
CGSize windowSize = [[[self class] keyWindow] bounds].size;
|
||||
self.frame = CGRectMake(windowSize.width - (windowSize.width / OVERLAY_SCALE) - OVERLAY_INSET, windowSize.height - OVERLAY_INSET,
|
||||
windowSize.width / OVERLAY_SCALE, 0.0);
|
||||
|
||||
UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(rangeDebugOverlayWasPanned:)];
|
||||
[self addGestureRecognizer:panGR];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#define BAR_THICKNESS 24
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
|
||||
[self layoutToFitAllBarsExcept:0];
|
||||
} completion:^(BOOL finished) {
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)layoutToFitAllBarsExcept:(NSInteger)barsToClip
|
||||
{
|
||||
CGSize boundsSize = self.bounds.size;
|
||||
CGFloat totalHeight = 0.0;
|
||||
|
||||
CGRect barRect = CGRectMake(0, boundsSize.height - BAR_THICKNESS, self.bounds.size.width, BAR_THICKNESS);
|
||||
NSMutableArray *displayedBars = [NSMutableArray array];
|
||||
|
||||
for (_ASRangeDebugBarView *barView in [_rangeControllerViews copy]) {
|
||||
barView.frame = barRect;
|
||||
|
||||
ASInterfaceState interfaceState = [barView.rangeController.dataSource interfaceStateForRangeController:barView.rangeController];
|
||||
|
||||
if (!(interfaceState & (ASInterfaceStateVisible))) {
|
||||
if (barView.destroyOnLayout && barView.alpha == 0.0) {
|
||||
[_rangeControllerViews removeObjectIdenticalTo:barView];
|
||||
[barView removeFromSuperview];
|
||||
} else {
|
||||
barView.alpha = 0.0;
|
||||
}
|
||||
} else {
|
||||
assert(!barView.destroyOnLayout); // In this case we should not have a visible interfaceState
|
||||
barView.alpha = 1.0;
|
||||
totalHeight += BAR_THICKNESS;
|
||||
barRect.origin.y -= BAR_THICKNESS;
|
||||
[displayedBars addObject:barView];
|
||||
}
|
||||
}
|
||||
|
||||
if (totalHeight > 0) {
|
||||
totalHeight -= (BAR_THICKNESS * barsToClip);
|
||||
}
|
||||
|
||||
if (barsToClip == 0) {
|
||||
CGRect overlayFrame = self.frame;
|
||||
CGFloat heightChange = (overlayFrame.size.height - totalHeight);
|
||||
|
||||
overlayFrame.origin.y += heightChange;
|
||||
overlayFrame.size.height = totalHeight;
|
||||
self.frame = overlayFrame;
|
||||
|
||||
for (_ASRangeDebugBarView *barView in displayedBars) {
|
||||
[self offsetYOrigin:-heightChange forView:barView];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setOrigin:(CGPoint)origin forView:(UIView *)view
|
||||
{
|
||||
CGRect newFrame = view.frame;
|
||||
newFrame.origin = origin;
|
||||
view.frame = newFrame;
|
||||
}
|
||||
|
||||
- (void)offsetYOrigin:(CGFloat)offset forView:(UIView *)view
|
||||
{
|
||||
CGRect newFrame = view.frame;
|
||||
newFrame.origin = CGPointMake(newFrame.origin.x, newFrame.origin.y + offset);
|
||||
view.frame = newFrame;
|
||||
}
|
||||
|
||||
- (void)addRangeController:(ASRangeController *)rangeController
|
||||
{
|
||||
for (_ASRangeDebugBarView *rangeView in _rangeControllerViews) {
|
||||
if (rangeView.rangeController == rangeController) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ASRangeDebugBarView *rangeView = [[_ASRangeDebugBarView alloc] initWithRangeController:rangeController];
|
||||
[_rangeControllerViews addObject:rangeView];
|
||||
[self addSubview:rangeView];
|
||||
|
||||
if (!_animating) {
|
||||
[self layoutToFitAllBarsExcept:1];
|
||||
}
|
||||
|
||||
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
|
||||
_animating = YES;
|
||||
[self layoutToFitAllBarsExcept:0];
|
||||
} completion:^(BOOL finished) {
|
||||
_animating = NO;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateRangeController:(ASRangeController *)controller
|
||||
withScrollableDirections:(ASScrollDirection)scrollableDirections
|
||||
scrollDirection:(ASScrollDirection)scrollDirection
|
||||
rangeMode:(ASLayoutRangeMode)rangeMode
|
||||
displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters
|
||||
fetchDataTuningParameters:(ASRangeTuningParameters)fetchDataTuningParameters
|
||||
interfaceState:(ASInterfaceState)interfaceState;
|
||||
{
|
||||
_ASRangeDebugBarView *viewToUpdate = [self barViewForRangeController:controller];
|
||||
|
||||
CGRect boundsRect = self.bounds;
|
||||
CGRect visibleRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, ASRangeTuningParametersZero, scrollableDirections, scrollDirection);
|
||||
CGRect displayRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, displayTuningParameters, scrollableDirections, scrollDirection);
|
||||
CGRect fetchDataRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, fetchDataTuningParameters, scrollableDirections, scrollDirection);
|
||||
|
||||
// figure out which is biggest and assume that is full bounds
|
||||
BOOL displayRangeLargerThanFetch = NO;
|
||||
CGFloat visibleRatio = 0;
|
||||
CGFloat displayRatio = 0;
|
||||
CGFloat fetchDataRatio = 0;
|
||||
CGFloat leadingDisplayTuningRatio = 0;
|
||||
CGFloat leadingFetchDataTuningRatio = 0;
|
||||
|
||||
if (!((displayTuningParameters.leadingBufferScreenfuls + displayTuningParameters.trailingBufferScreenfuls) == 0)) {
|
||||
leadingDisplayTuningRatio = displayTuningParameters.leadingBufferScreenfuls / (displayTuningParameters.leadingBufferScreenfuls + displayTuningParameters.trailingBufferScreenfuls);
|
||||
}
|
||||
if (!((fetchDataTuningParameters.leadingBufferScreenfuls + fetchDataTuningParameters.trailingBufferScreenfuls) == 0)) {
|
||||
leadingFetchDataTuningRatio = fetchDataTuningParameters.leadingBufferScreenfuls / (fetchDataTuningParameters.leadingBufferScreenfuls + fetchDataTuningParameters.trailingBufferScreenfuls);
|
||||
}
|
||||
|
||||
if (ASScrollDirectionContainsVerticalDirection(scrollDirection)) {
|
||||
|
||||
if (displayRect.size.height >= fetchDataRect.size.height) {
|
||||
displayRangeLargerThanFetch = YES;
|
||||
} else {
|
||||
displayRangeLargerThanFetch = NO;
|
||||
}
|
||||
|
||||
if (displayRangeLargerThanFetch) {
|
||||
visibleRatio = visibleRect.size.height / displayRect.size.height;
|
||||
displayRatio = 1.0;
|
||||
fetchDataRatio = fetchDataRect.size.height / displayRect.size.height;
|
||||
} else {
|
||||
visibleRatio = visibleRect.size.height / fetchDataRect.size.height;
|
||||
displayRatio = displayRect.size.height / fetchDataRect.size.height;
|
||||
fetchDataRatio = 1.0;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (displayRect.size.width >= fetchDataRect.size.width) {
|
||||
displayRangeLargerThanFetch = YES;
|
||||
} else {
|
||||
displayRangeLargerThanFetch = NO;
|
||||
}
|
||||
|
||||
if (displayRangeLargerThanFetch) {
|
||||
visibleRatio = visibleRect.size.width / displayRect.size.width;
|
||||
displayRatio = 1.0;
|
||||
fetchDataRatio = fetchDataRect.size.width / displayRect.size.width;
|
||||
} else {
|
||||
visibleRatio = visibleRect.size.width / fetchDataRect.size.width;
|
||||
displayRatio = displayRect.size.width / fetchDataRect.size.width;
|
||||
fetchDataRatio = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
[viewToUpdate updateWithVisibleRatio:visibleRatio
|
||||
displayRatio:displayRatio
|
||||
leadingDisplayRatio:leadingDisplayTuningRatio
|
||||
fetchDataRatio:fetchDataRatio
|
||||
leadingFetchDataRatio:leadingFetchDataTuningRatio
|
||||
direction:scrollDirection];
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (_ASRangeDebugBarView *)barViewForRangeController:(ASRangeController *)controller
|
||||
{
|
||||
_ASRangeDebugBarView *rangeControllerBarView = nil;
|
||||
|
||||
for (_ASRangeDebugBarView *rangeView in [[_rangeControllerViews reverseObjectEnumerator] allObjects]) {
|
||||
// remove barView if its rangeController has been deleted
|
||||
if (!rangeView.rangeController) {
|
||||
rangeView.destroyOnLayout = YES;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
ASInterfaceState interfaceState = [rangeView.rangeController.dataSource interfaceStateForRangeController:rangeView.rangeController];
|
||||
if (!(interfaceState & (ASInterfaceStateVisible | ASInterfaceStateDisplay))) {
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
if ([rangeView.rangeController isEqual:controller]) {
|
||||
rangeControllerBarView = rangeView;
|
||||
}
|
||||
}
|
||||
|
||||
return rangeControllerBarView;
|
||||
}
|
||||
|
||||
#define MIN_VISIBLE_INSET 40
|
||||
- (void)rangeDebugOverlayWasPanned:(UIPanGestureRecognizer *)recognizer
|
||||
{
|
||||
CGPoint translation = [recognizer translationInView:recognizer.view];
|
||||
CGFloat newCenterX = recognizer.view.center.x + translation.x;
|
||||
CGFloat newCenterY = recognizer.view.center.y + translation.y;
|
||||
CGSize boundsSize = recognizer.view.bounds.size;
|
||||
CGSize superBoundsSize = recognizer.view.superview.bounds.size;
|
||||
CGFloat minAllowableX = -boundsSize.width / 2.0 + MIN_VISIBLE_INSET;
|
||||
CGFloat maxAllowableX = superBoundsSize.width + boundsSize.width / 2.0 - MIN_VISIBLE_INSET;
|
||||
|
||||
if (newCenterX > maxAllowableX) {
|
||||
newCenterX = maxAllowableX;
|
||||
} else if (newCenterX < minAllowableX) {
|
||||
newCenterX = minAllowableX;
|
||||
}
|
||||
|
||||
CGFloat minAllowableY = -boundsSize.height / 2.0 + MIN_VISIBLE_INSET;
|
||||
CGFloat maxAllowableY = superBoundsSize.height + boundsSize.height / 2.0 - MIN_VISIBLE_INSET;
|
||||
|
||||
if (newCenterY > maxAllowableY) {
|
||||
newCenterY = maxAllowableY;
|
||||
} else if (newCenterY < minAllowableY) {
|
||||
newCenterY = minAllowableY;
|
||||
}
|
||||
|
||||
recognizer.view.center = CGPointMake(newCenterX, newCenterY);
|
||||
[recognizer setTranslation:CGPointMake(0, 0) inView:recognizer.view];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark _ASRangeDebugBarView
|
||||
|
||||
@implementation _ASRangeDebugBarView
|
||||
{
|
||||
ASTextNode *_debugText;
|
||||
ASTextNode *_leftDebugText;
|
||||
ASTextNode *_rightDebugText;
|
||||
ASImageNode *_visibleRect;
|
||||
ASImageNode *_displayRect;
|
||||
ASImageNode *_fetchDataRect;
|
||||
CGFloat _visibleRatio;
|
||||
CGFloat _displayRatio;
|
||||
CGFloat _fetchDataRatio;
|
||||
CGFloat _leadingDisplayRatio;
|
||||
CGFloat _leadingFetchDataRatio;
|
||||
ASScrollDirection _scrollDirection;
|
||||
BOOL _firstLayoutOfRects;
|
||||
}
|
||||
|
||||
- (instancetype)initWithRangeController:(ASRangeController *)rangeController
|
||||
{
|
||||
self = [super initWithFrame:CGRectZero];
|
||||
if (self) {
|
||||
_firstLayoutOfRects = YES;
|
||||
_rangeController = rangeController;
|
||||
_debugText = [self createDebugTextNode];
|
||||
_leftDebugText = [self createDebugTextNode];
|
||||
_rightDebugText = [self createDebugTextNode];
|
||||
_fetchDataRect = [self createRangeNodeWithColor:[UIColor orangeColor]];
|
||||
_displayRect = [self createRangeNodeWithColor:[UIColor yellowColor]];
|
||||
_visibleRect = [self createRangeNodeWithColor:[UIColor greenColor]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#define HORIZONTAL_INSET 10
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
CGSize boundsSize = self.bounds.size;
|
||||
CGFloat subCellHeight = 9.0;
|
||||
[self setBarDebugLabelsWithSize:subCellHeight];
|
||||
[self setBarSubviewOrder];
|
||||
|
||||
CGRect rect = CGRectIntegral(CGRectMake(0, 0, boundsSize.width, floorf(boundsSize.height / 2.0)));
|
||||
rect.size = [_debugText measure:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
|
||||
rect.origin.x = (boundsSize.width - rect.size.width) / 2.0;
|
||||
_debugText.frame = rect;
|
||||
rect.origin.y += rect.size.height;
|
||||
|
||||
rect.origin.x = 0;
|
||||
rect.size = CGSizeMake(HORIZONTAL_INSET, boundsSize.height / 2.0);
|
||||
_leftDebugText.frame = rect;
|
||||
|
||||
rect.origin.x = boundsSize.width - HORIZONTAL_INSET;
|
||||
_rightDebugText.frame = rect;
|
||||
|
||||
CGFloat visibleDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _visibleRatio;
|
||||
CGFloat displayDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _displayRatio;
|
||||
CGFloat fetchDataDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _fetchDataRatio;
|
||||
CGFloat visiblePoint = 0;
|
||||
CGFloat displayPoint = 0;
|
||||
CGFloat fetchDataPoint = 0;
|
||||
|
||||
BOOL displayLargerThanFetchData = (_displayRatio == 1.0) ? YES : NO;
|
||||
|
||||
if (ASScrollDirectionContainsLeft(_scrollDirection) || ASScrollDirectionContainsUp(_scrollDirection)) {
|
||||
|
||||
if (displayLargerThanFetchData) {
|
||||
visiblePoint = (displayDimension - visibleDimension) * _leadingDisplayRatio;
|
||||
fetchDataPoint = visiblePoint - (fetchDataDimension - visibleDimension) * _leadingFetchDataRatio;
|
||||
} else {
|
||||
visiblePoint = (fetchDataDimension - visibleDimension) * _leadingFetchDataRatio;
|
||||
displayPoint = visiblePoint - (displayDimension - visibleDimension) * _leadingDisplayRatio;
|
||||
}
|
||||
} else if (ASScrollDirectionContainsRight(_scrollDirection) || ASScrollDirectionContainsDown(_scrollDirection)) {
|
||||
|
||||
if (displayLargerThanFetchData) {
|
||||
visiblePoint = (displayDimension - visibleDimension) * (1 - _leadingDisplayRatio);
|
||||
fetchDataPoint = visiblePoint - (fetchDataDimension - visibleDimension) * (1 - _leadingFetchDataRatio);
|
||||
} else {
|
||||
visiblePoint = (fetchDataDimension - visibleDimension) * (1 - _leadingFetchDataRatio);
|
||||
displayPoint = visiblePoint - (displayDimension - visibleDimension) * (1 - _leadingDisplayRatio);
|
||||
}
|
||||
}
|
||||
|
||||
BOOL animate = !_firstLayoutOfRects;
|
||||
[UIView animateWithDuration:animate ? 0.3 : 0.0 delay:0.0 options:UIViewAnimationOptionLayoutSubviews animations:^{
|
||||
_visibleRect.frame = CGRectMake(HORIZONTAL_INSET + visiblePoint, rect.origin.y, visibleDimension, subCellHeight);
|
||||
_displayRect.frame = CGRectMake(HORIZONTAL_INSET + displayPoint, rect.origin.y, displayDimension, subCellHeight);
|
||||
_fetchDataRect.frame = CGRectMake(HORIZONTAL_INSET + fetchDataPoint, rect.origin.y, fetchDataDimension, subCellHeight);
|
||||
} completion:^(BOOL finished) {}];
|
||||
|
||||
if (!animate) {
|
||||
_visibleRect.alpha = _displayRect.alpha = _fetchDataRect.alpha = 0;
|
||||
[UIView animateWithDuration:0.3 animations:^{
|
||||
_visibleRect.alpha = _displayRect.alpha = _fetchDataRect.alpha = 1;
|
||||
}];
|
||||
}
|
||||
|
||||
_firstLayoutOfRects = NO;
|
||||
}
|
||||
|
||||
- (void)updateWithVisibleRatio:(CGFloat)visibleRatio
|
||||
displayRatio:(CGFloat)displayRatio
|
||||
leadingDisplayRatio:(CGFloat)leadingDisplayRatio
|
||||
fetchDataRatio:(CGFloat)fetchDataRatio
|
||||
leadingFetchDataRatio:(CGFloat)leadingFetchDataRatio
|
||||
direction:(ASScrollDirection)scrollDirection
|
||||
{
|
||||
_visibleRatio = visibleRatio;
|
||||
_displayRatio = displayRatio;
|
||||
_leadingDisplayRatio = leadingDisplayRatio;
|
||||
_fetchDataRatio = fetchDataRatio;
|
||||
_leadingFetchDataRatio = leadingFetchDataRatio;
|
||||
_scrollDirection = scrollDirection;
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setBarSubviewOrder
|
||||
{
|
||||
if (_fetchDataRatio == 1.0) {
|
||||
[self sendSubviewToBack:_fetchDataRect.view];
|
||||
} else {
|
||||
[self sendSubviewToBack:_displayRect.view];
|
||||
}
|
||||
|
||||
[self bringSubviewToFront:_visibleRect.view];
|
||||
}
|
||||
|
||||
- (void)setBarDebugLabelsWithSize:(CGFloat)size
|
||||
{
|
||||
if (!_debugString) {
|
||||
_debugString = [[_rangeController dataSource] nameForRangeControllerDataSource];
|
||||
}
|
||||
if (_debugString) {
|
||||
_debugText.attributedString = [_ASRangeDebugBarView whiteAttributedStringFromString:_debugString withSize:size];
|
||||
}
|
||||
|
||||
if (ASScrollDirectionContainsVerticalDirection(_scrollDirection)) {
|
||||
_leftDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"▲" withSize:size];
|
||||
_rightDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"▼" withSize:size];
|
||||
} else if (ASScrollDirectionContainsHorizontalDirection(_scrollDirection)) {
|
||||
_leftDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"◀︎" withSize:size];
|
||||
_rightDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"▶︎" withSize:size];
|
||||
}
|
||||
|
||||
_leftDebugText.hidden = (_scrollDirection != ASScrollDirectionLeft && _scrollDirection != ASScrollDirectionUp);
|
||||
_rightDebugText.hidden = (_scrollDirection != ASScrollDirectionRight && _scrollDirection != ASScrollDirectionDown);
|
||||
}
|
||||
|
||||
- (ASTextNode *)createDebugTextNode
|
||||
{
|
||||
ASTextNode *label = [[ASTextNode alloc] init];
|
||||
[self addSubnode:label];
|
||||
return label;
|
||||
}
|
||||
|
||||
#define RANGE_BAR_CORNER_RADIUS 3
|
||||
#define RANGE_BAR_BORDER_WIDTH 1
|
||||
- (ASImageNode *)createRangeNodeWithColor:(UIColor *)color
|
||||
{
|
||||
ASImageNode *rangeBarImageNode = [[ASImageNode alloc] init];
|
||||
rangeBarImageNode.image = [UIImage as_resizableRoundedImageWithCornerRadius:RANGE_BAR_CORNER_RADIUS
|
||||
cornerColor:[UIColor clearColor]
|
||||
fillColor:[color colorWithAlphaComponent:0.5]
|
||||
borderColor:[[UIColor blackColor] colorWithAlphaComponent:0.9]
|
||||
borderWidth:RANGE_BAR_BORDER_WIDTH
|
||||
roundedCorners:UIRectCornerAllCorners
|
||||
scale:[[UIScreen mainScreen] scale]];
|
||||
[self addSubnode:rangeBarImageNode];
|
||||
|
||||
return rangeBarImageNode;
|
||||
}
|
||||
|
||||
+ (NSAttributedString *)whiteAttributedStringFromString:(NSString *)string withSize:(CGFloat)size
|
||||
{
|
||||
NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor whiteColor],
|
||||
NSFontAttributeName : [UIFont systemFontOfSize:size]};
|
||||
return [[NSAttributedString alloc] initWithString:string attributes:attributes];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -134,6 +134,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (NSArray<NSArray <ASCellNode *> *> *)completedNodes;
|
||||
|
||||
- (NSString *)nameForRangeControllerDataSource;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,13 +11,16 @@
|
||||
#import "ASRangeController.h"
|
||||
|
||||
#import "ASAssert.h"
|
||||
#import "ASWeakSet.h"
|
||||
#import "ASCellNode.h"
|
||||
#import "ASDisplayNodeExtras.h"
|
||||
#import "ASDisplayNodeInternal.h"
|
||||
#import "ASMultidimensionalArrayUtils.h"
|
||||
#import "ASInternalHelpers.h"
|
||||
#import "ASMultiDimensionalArrayUtils.h"
|
||||
#import "ASWeakSet.h"
|
||||
|
||||
#import "ASDisplayNode+FrameworkPrivate.h"
|
||||
#import "ASCellNode.h"
|
||||
#import "AsyncDisplayKit+Debug.h"
|
||||
|
||||
#define AS_RANGECONTROLLER_LOG_UPDATE_FREQ 0
|
||||
|
||||
@@ -64,6 +67,10 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
|
||||
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
||||
#endif
|
||||
|
||||
if ([ASRangeController shouldShowRangeDebugOverlay]) {
|
||||
[self addRangeControllerToRangeDebugOverlay];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -335,6 +342,25 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This code is for debugging only, but would be great to clean up with a delegate method implementation.
|
||||
if ([ASRangeController shouldShowRangeDebugOverlay]) {
|
||||
ASScrollDirection scrollableDirections = ASScrollDirectionUp | ASScrollDirectionDown;
|
||||
if ([_dataSource isKindOfClass:NSClassFromString(@"ASCollectionView")]) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
||||
scrollableDirections = (ASScrollDirection)[_dataSource performSelector:@selector(scrollableDirections)];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
[self updateRangeController:self
|
||||
withScrollableDirections:scrollableDirections
|
||||
scrollDirection:scrollDirection
|
||||
rangeMode:rangeMode
|
||||
displayTuningParameters:parametersDisplay
|
||||
fetchDataTuningParameters:parametersFetchData
|
||||
interfaceState:selfInterfaceState];
|
||||
}
|
||||
|
||||
_rangeIsValid = YES;
|
||||
|
||||
#if ASRangeControllerLoggingEnabled
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
_pagerNode = [[ASPagerNode alloc] init];
|
||||
_pagerNode.dataSource = self;
|
||||
[ASRangeController setShouldShowRangeDebugOverlay:YES];
|
||||
|
||||
// Could implement ASCollectionDelegate if we wanted extra callbacks, like from UIScrollView.
|
||||
//_pagerNode.delegate = self;
|
||||
|
||||
Reference in New Issue
Block a user