| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754 |
- //
- // AsyncDisplayKit+Debug.m
- // AsyncDisplayKit
- //
- // Created by Hannah Troisi on 3/7/16.
- //
- // 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/AsyncDisplayKit+Debug.h>
- #import <AsyncDisplayKit/ASAbstractLayoutController.h>
- #import <AsyncDisplayKit/ASLayout.h>
- #import <AsyncDisplayKit/ASWeakSet.h>
- #import <AsyncDisplayKit/UIImage+ASConvenience.h>
- #import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
- #import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
- #import <AsyncDisplayKit/ASDisplayNodeExtras.h>
- #import <AsyncDisplayKit/ASTextNode.h>
- #import <AsyncDisplayKit/ASRangeController.h>
- #pragma mark - ASImageNode (Debugging)
- static BOOL __shouldShowImageScalingOverlay = NO;
- @implementation ASImageNode (Debugging)
- + (void)setShouldShowImageScalingOverlay:(BOOL)show;
- {
- __shouldShowImageScalingOverlay = show;
- }
- + (BOOL)shouldShowImageScalingOverlay
- {
- return __shouldShowImageScalingOverlay;
- }
- @end
- #pragma mark - ASControlNode (DebuggingInternal)
- static BOOL __enableHitTestDebug = NO;
- @interface ASControlNode (DebuggingInternal)
- - (ASImageNode *)debugHighlightOverlay;
- @end
- @implementation ASControlNode (Debugging)
- + (void)setEnableHitTestDebug:(BOOL)enable
- {
- __enableHitTestDebug = enable;
- }
- + (BOOL)enableHitTestDebug
- {
- return __enableHitTestDebug;
- }
- // layout method required ONLY when hitTestDebug is enabled
- - (void)layout
- {
- [super layout];
-
- if ([ASControlNode enableHitTestDebug]) {
-
- // Construct hitTestDebug highlight overlay frame indicating tappable area of a node, which can be restricted by two things:
-
- // (1) Any parent's tapable area (its own bounds + hitTestSlop) may restrict the desired tappable area expansion using
- // hitTestSlop of a child as UIKit event delivery (hitTest:) will not search sub-hierarchies if one of our parents does
- // not return YES for pointInside:. To circumvent this restriction, a developer will need to set / adjust the hitTestSlop
- // on the limiting parent. This is indicated in the overlay by a dark GREEN edge. This is an ACTUAL restriction.
-
- // (2) Any parent's .clipToBounds. If a parent is clipping, we cannot show the overlay outside that area
- // (although it still respond to touch). To indicate that the overlay cannot accurately display the true tappable area,
- // the overlay will have an ORANGE edge. This is a VISUALIZATION restriction.
-
- CGRect intersectRect = UIEdgeInsetsInsetRect(self.bounds, [self hitTestSlop]);
- UIRectEdge clippedEdges = UIRectEdgeNone;
- UIRectEdge clipsToBoundsClippedEdges = UIRectEdgeNone;
- CALayer *layer = self.layer;
- CALayer *intersectLayer = layer;
- CALayer *intersectSuperlayer = layer.superlayer;
-
- // FIXED: Stop climbing hierarchy if UIScrollView is encountered (its offset bounds origin may make it seem like our events
- // will be clipped when scrolling will actually reveal them (because this process will not re-run due to scrolling))
- while (intersectSuperlayer && ![intersectSuperlayer.delegate respondsToSelector:@selector(contentOffset)]) {
-
- // Get parent's tappable area
- CGRect parentHitRect = intersectSuperlayer.bounds;
- BOOL parentClipsToBounds = NO;
-
- // If parent is a node, tappable area may be expanded by hitTestSlop
- ASDisplayNode *parentNode = ASLayerToDisplayNode(intersectSuperlayer);
- if (parentNode) {
- UIEdgeInsets parentSlop = [parentNode hitTestSlop];
-
- // If parent has hitTestSlop, expand tappable area (if parent doesn't clipToBounds)
- if (!UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, parentSlop)) {
- parentClipsToBounds = parentNode.clipsToBounds;
- if (!parentClipsToBounds) {
- parentHitRect = UIEdgeInsetsInsetRect(parentHitRect, [parentNode hitTestSlop]);
- }
- }
- }
-
- // Convert our current rect to parent coordinates
- CGRect intersectRectInParentCoordinates = [intersectSuperlayer convertRect:intersectRect fromLayer:intersectLayer];
-
- // Intersect rect with the parent's tappable area rect
- intersectRect = CGRectIntersection(parentHitRect, intersectRectInParentCoordinates);
- if (!CGSizeEqualToSize(parentHitRect.size, intersectRectInParentCoordinates.size)) {
- clippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates
- parentRect:parentHitRect rectEdge:clippedEdges];
- if (parentClipsToBounds) {
- clipsToBoundsClippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates
- parentRect:parentHitRect rectEdge:clipsToBoundsClippedEdges];
- }
- }
-
- // move up hierarchy
- intersectLayer = intersectSuperlayer;
- intersectSuperlayer = intersectLayer.superlayer;
- }
-
- // produce final overlay image (or fill background if edges aren't restricted)
- CGRect finalRect = [intersectLayer convertRect:intersectRect toLayer:layer];
- UIColor *fillColor = [[UIColor greenColor] colorWithAlphaComponent:0.4];
-
- ASImageNode *debugOverlay = [self debugHighlightOverlay];
-
- // determine if edges are clipped and if so, highlight the restricted edges
- if (clippedEdges == UIRectEdgeNone) {
- debugOverlay.backgroundColor = fillColor;
- } else {
- const CGFloat borderWidth = 2.0;
- UIColor *borderColor = [[UIColor orangeColor] colorWithAlphaComponent:0.8];
- UIColor *clipsBorderColor = [UIColor colorWithRed:30/255.0 green:90/255.0 blue:50/255.0 alpha:0.7];
- CGRect imgRect = CGRectMake(0, 0, 2.0 * borderWidth + 1.0, 2.0 * borderWidth + 1.0);
-
- UIGraphicsBeginImageContext(imgRect.size);
-
- [fillColor setFill];
- UIRectFill(imgRect);
-
- [self drawEdgeIfClippedWithEdges:clippedEdges color:clipsBorderColor borderWidth:borderWidth imgRect:imgRect];
- [self drawEdgeIfClippedWithEdges:clipsToBoundsClippedEdges color:borderColor borderWidth:borderWidth imgRect:imgRect];
-
- UIImage *debugHighlightImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
-
- UIEdgeInsets edgeInsets = UIEdgeInsetsMake(borderWidth, borderWidth, borderWidth, borderWidth);
- debugOverlay.image = [debugHighlightImage resizableImageWithCapInsets:edgeInsets resizingMode:UIImageResizingModeStretch];
- debugOverlay.backgroundColor = nil;
- }
-
- debugOverlay.frame = finalRect;
- }
- }
- - (UIRectEdge)setEdgesOfIntersectionForChildRect:(CGRect)childRect parentRect:(CGRect)parentRect rectEdge:(UIRectEdge)rectEdge
- {
- // determine which edges of childRect are outside parentRect (and thus will be clipped)
- if (childRect.origin.y < parentRect.origin.y) {
- rectEdge |= UIRectEdgeTop;
- }
- if (childRect.origin.x < parentRect.origin.x) {
- rectEdge |= UIRectEdgeLeft;
- }
- if (CGRectGetMaxY(childRect) > CGRectGetMaxY(parentRect)) {
- rectEdge |= UIRectEdgeBottom;
- }
- if (CGRectGetMaxX(childRect) > CGRectGetMaxX(parentRect)) {
- rectEdge |= UIRectEdgeRight;
- }
-
- return rectEdge;
- }
- - (void)drawEdgeIfClippedWithEdges:(UIRectEdge)rectEdge color:(UIColor *)color borderWidth:(CGFloat)borderWidth imgRect:(CGRect)imgRect
- {
- [color setFill];
-
- // highlight individual edges of overlay if edge is restricted by parentRect
- // so that the developer is aware that increasing hitTestSlop will not result in an expanded tappable area
- if (rectEdge & UIRectEdgeTop) {
- UIRectFill(CGRectMake(0.0, 0.0, imgRect.size.width, borderWidth));
- }
- if (rectEdge & UIRectEdgeLeft) {
- UIRectFill(CGRectMake(0.0, 0.0, borderWidth, imgRect.size.height));
- }
- if (rectEdge & UIRectEdgeBottom) {
- UIRectFill(CGRectMake(0.0, imgRect.size.height - borderWidth, imgRect.size.width, borderWidth));
- }
- if (rectEdge & UIRectEdgeRight) {
- UIRectFill(CGRectMake(imgRect.size.width - borderWidth, 0.0, borderWidth, imgRect.size.height));
- }
- }
- @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
- preloadTuningParameters:(ASRangeTuningParameters)preloadTuningParameters
- 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
- preloadRatio:(CGFloat)preloadRatio
- leadingpreloadRatio:(CGFloat)leadingpreloadRatio
- 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
- preloadTuningParameters:(ASRangeTuningParameters)preloadTuningParameters
- interfaceState:(ASInterfaceState)interfaceState
- {
- [[_ASRangeDebugOverlayView sharedInstance] updateRangeController:controller
- withScrollableDirections:scrollableDirections
- scrollDirection:direction
- rangeMode:mode
- displayTuningParameters:displayTuningParameters
- preloadTuningParameters:preloadTuningParameters
- 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
- preloadTuningParameters:(ASRangeTuningParameters)preloadTuningParameters
- 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 preloadRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, preloadTuningParameters, scrollableDirections, scrollDirection);
-
- // figure out which is biggest and assume that is full bounds
- BOOL displayRangeLargerThanPreload = NO;
- CGFloat visibleRatio = 0;
- CGFloat displayRatio = 0;
- CGFloat preloadRatio = 0;
- CGFloat leadingDisplayTuningRatio = 0;
- CGFloat leadingPreloadTuningRatio = 0;
- if (!((displayTuningParameters.leadingBufferScreenfuls + displayTuningParameters.trailingBufferScreenfuls) == 0)) {
- leadingDisplayTuningRatio = displayTuningParameters.leadingBufferScreenfuls / (displayTuningParameters.leadingBufferScreenfuls + displayTuningParameters.trailingBufferScreenfuls);
- }
- if (!((preloadTuningParameters.leadingBufferScreenfuls + preloadTuningParameters.trailingBufferScreenfuls) == 0)) {
- leadingPreloadTuningRatio = preloadTuningParameters.leadingBufferScreenfuls / (preloadTuningParameters.leadingBufferScreenfuls + preloadTuningParameters.trailingBufferScreenfuls);
- }
-
- if (ASScrollDirectionContainsVerticalDirection(scrollDirection)) {
-
- if (displayRect.size.height >= preloadRect.size.height) {
- displayRangeLargerThanPreload = YES;
- } else {
- displayRangeLargerThanPreload = NO;
- }
-
- if (displayRangeLargerThanPreload) {
- visibleRatio = visibleRect.size.height / displayRect.size.height;
- displayRatio = 1.0;
- preloadRatio = preloadRect.size.height / displayRect.size.height;
- } else {
- visibleRatio = visibleRect.size.height / preloadRect.size.height;
- displayRatio = displayRect.size.height / preloadRect.size.height;
- preloadRatio = 1.0;
- }
- } else {
-
- if (displayRect.size.width >= preloadRect.size.width) {
- displayRangeLargerThanPreload = YES;
- } else {
- displayRangeLargerThanPreload = NO;
- }
-
- if (displayRangeLargerThanPreload) {
- visibleRatio = visibleRect.size.width / displayRect.size.width;
- displayRatio = 1.0;
- preloadRatio = preloadRect.size.width / displayRect.size.width;
- } else {
- visibleRatio = visibleRect.size.width / preloadRect.size.width;
- displayRatio = displayRect.size.width / preloadRect.size.width;
- preloadRatio = 1.0;
- }
- }
- [viewToUpdate updateWithVisibleRatio:visibleRatio
- displayRatio:displayRatio
- leadingDisplayRatio:leadingDisplayTuningRatio
- preloadRatio:preloadRatio
- leadingpreloadRatio:leadingPreloadTuningRatio
- 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 *_preloadRect;
- CGFloat _visibleRatio;
- CGFloat _displayRatio;
- CGFloat _preloadRatio;
- CGFloat _leadingDisplayRatio;
- CGFloat _leadingpreloadRatio;
- 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];
- _preloadRect = [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 layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))].size;
- 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 preloadDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _preloadRatio;
- CGFloat visiblePoint = 0;
- CGFloat displayPoint = 0;
- CGFloat preloadPoint = 0;
-
- BOOL displayLargerThanPreload = (_displayRatio == 1.0) ? YES : NO;
-
- if (ASScrollDirectionContainsLeft(_scrollDirection) || ASScrollDirectionContainsUp(_scrollDirection)) {
-
- if (displayLargerThanPreload) {
- visiblePoint = (displayDimension - visibleDimension) * _leadingDisplayRatio;
- preloadPoint = visiblePoint - (preloadDimension - visibleDimension) * _leadingpreloadRatio;
- } else {
- visiblePoint = (preloadDimension - visibleDimension) * _leadingpreloadRatio;
- displayPoint = visiblePoint - (displayDimension - visibleDimension) * _leadingDisplayRatio;
- }
- } else if (ASScrollDirectionContainsRight(_scrollDirection) || ASScrollDirectionContainsDown(_scrollDirection)) {
-
- if (displayLargerThanPreload) {
- visiblePoint = (displayDimension - visibleDimension) * (1 - _leadingDisplayRatio);
- preloadPoint = visiblePoint - (preloadDimension - visibleDimension) * (1 - _leadingpreloadRatio);
- } else {
- visiblePoint = (preloadDimension - visibleDimension) * (1 - _leadingpreloadRatio);
- 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);
- _preloadRect.frame = CGRectMake(HORIZONTAL_INSET + preloadPoint, rect.origin.y, preloadDimension, subCellHeight);
- } completion:^(BOOL finished) {}];
-
- if (!animate) {
- _visibleRect.alpha = _displayRect.alpha = _preloadRect.alpha = 0;
- [UIView animateWithDuration:0.3 animations:^{
- _visibleRect.alpha = _displayRect.alpha = _preloadRect.alpha = 1;
- }];
- }
-
- _firstLayoutOfRects = NO;
- }
- - (void)updateWithVisibleRatio:(CGFloat)visibleRatio
- displayRatio:(CGFloat)displayRatio
- leadingDisplayRatio:(CGFloat)leadingDisplayRatio
- preloadRatio:(CGFloat)preloadRatio
- leadingpreloadRatio:(CGFloat)leadingpreloadRatio
- direction:(ASScrollDirection)scrollDirection
- {
- _visibleRatio = visibleRatio;
- _displayRatio = displayRatio;
- _leadingDisplayRatio = leadingDisplayRatio;
- _preloadRatio = preloadRatio;
- _leadingpreloadRatio = leadingpreloadRatio;
- _scrollDirection = scrollDirection;
-
- [self setNeedsLayout];
- }
- - (void)setBarSubviewOrder
- {
- if (_preloadRatio == 1.0) {
- [self sendSubviewToBack:_preloadRect.view];
- } else {
- [self sendSubviewToBack:_displayRect.view];
- }
-
- [self bringSubviewToFront:_visibleRect.view];
- }
- - (void)setBarDebugLabelsWithSize:(CGFloat)size
- {
- if (!_debugString) {
- _debugString = [[_rangeController dataSource] nameForRangeControllerDataSource];
- }
- if (_debugString) {
- _debugText.attributedText = [_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
|