Fixed broken listview header alignment
This commit is contained in:
parent
8c95de11e1
commit
790cee6e26
|
@ -39,6 +39,7 @@
|
||||||
+ (NSString *)NSString:(id)json;
|
+ (NSString *)NSString:(id)json;
|
||||||
+ (NSNumber *)NSNumber:(id)json;
|
+ (NSNumber *)NSNumber:(id)json;
|
||||||
+ (NSData *)NSData:(id)json;
|
+ (NSData *)NSData:(id)json;
|
||||||
|
+ (NSIndexSet *)NSIndexSet:(id)json;
|
||||||
|
|
||||||
+ (NSURL *)NSURL:(id)json;
|
+ (NSURL *)NSURL:(id)json;
|
||||||
+ (NSURLRequest *)NSURLRequest:(id)json;
|
+ (NSURLRequest *)NSURLRequest:(id)json;
|
||||||
|
|
|
@ -64,6 +64,20 @@ RCT_CONVERTER(NSString *, NSString, description)
|
||||||
return [[self NSString:json] dataUsingEncoding:NSUTF8StringEncoding];
|
return [[self NSString:json] dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (NSIndexSet *)NSIndexSet:(id)json
|
||||||
|
{
|
||||||
|
json = [self NSNumberArray:json];
|
||||||
|
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
|
||||||
|
for (NSNumber *number in json) {
|
||||||
|
NSInteger index = number.integerValue;
|
||||||
|
if (RCT_DEBUG && index < 0) {
|
||||||
|
RCTLogError(@"Invalid index value %zd. Indices must be positive.", index);
|
||||||
|
}
|
||||||
|
[indexSet addIndex:index];
|
||||||
|
}
|
||||||
|
return indexSet;
|
||||||
|
}
|
||||||
|
|
||||||
+ (NSURL *)NSURL:(id)json
|
+ (NSURL *)NSURL:(id)json
|
||||||
{
|
{
|
||||||
NSString *path = [self NSString:json];
|
NSString *path = [self NSString:json];
|
||||||
|
|
|
@ -119,8 +119,6 @@ RCT_EXPORT_MODULE()
|
||||||
|
|
||||||
- (void)updateSettings
|
- (void)updateSettings
|
||||||
{
|
{
|
||||||
_settings = [NSMutableDictionary dictionaryWithDictionary:[_defaults objectForKey:RCTDevMenuSettingsKey]];
|
|
||||||
|
|
||||||
__weak RCTDevMenu *weakSelf = self;
|
__weak RCTDevMenu *weakSelf = self;
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
RCTDevMenu *strongSelf = weakSelf;
|
RCTDevMenu *strongSelf = weakSelf;
|
||||||
|
@ -128,6 +126,8 @@ RCT_EXPORT_MODULE()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strongSelf->_settings = [NSMutableDictionary dictionaryWithDictionary:[strongSelf->_defaults objectForKey:RCTDevMenuSettingsKey]];
|
||||||
|
|
||||||
strongSelf.shakeToShow = [strongSelf->_settings[@"shakeToShow"] ?: @YES boolValue];
|
strongSelf.shakeToShow = [strongSelf->_settings[@"shakeToShow"] ?: @YES boolValue];
|
||||||
strongSelf.profilingEnabled = [strongSelf->_settings[@"profilingEnabled"] ?: @NO boolValue];
|
strongSelf.profilingEnabled = [strongSelf->_settings[@"profilingEnabled"] ?: @NO boolValue];
|
||||||
strongSelf.liveReloadEnabled = [strongSelf->_settings[@"liveReloadEnabled"] ?: @NO boolValue];
|
strongSelf.liveReloadEnabled = [strongSelf->_settings[@"liveReloadEnabled"] ?: @NO boolValue];
|
||||||
|
|
|
@ -45,6 +45,6 @@
|
||||||
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
|
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
|
||||||
@property (nonatomic, assign) NSTimeInterval scrollEventThrottle;
|
@property (nonatomic, assign) NSTimeInterval scrollEventThrottle;
|
||||||
@property (nonatomic, assign) BOOL centerContent;
|
@property (nonatomic, assign) BOOL centerContent;
|
||||||
@property (nonatomic, copy) NSArray *stickyHeaderIndices;
|
@property (nonatomic, copy) NSIndexSet *stickyHeaderIndices;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -28,8 +28,8 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
|
||||||
*/
|
*/
|
||||||
@interface RCTCustomScrollView : UIScrollView<UIGestureRecognizerDelegate>
|
@interface RCTCustomScrollView : UIScrollView<UIGestureRecognizerDelegate>
|
||||||
|
|
||||||
@property (nonatomic, copy, readwrite) NSArray *stickyHeaderIndices;
|
@property (nonatomic, copy) NSIndexSet *stickyHeaderIndices;
|
||||||
@property (nonatomic, readwrite, assign) BOOL centerContent;
|
@property (nonatomic, assign) BOOL centerContent;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@ -155,97 +155,72 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
|
||||||
[super setContentOffset:contentOffset];
|
[super setContentOffset:contentOffset];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setBounds:(CGRect)bounds
|
|
||||||
{
|
|
||||||
[super setBounds:bounds];
|
|
||||||
[self dockClosestSectionHeader];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dockClosestSectionHeader
|
- (void)dockClosestSectionHeader
|
||||||
{
|
{
|
||||||
UIView *contentView = [self contentView];
|
UIView *contentView = [self contentView];
|
||||||
if (_stickyHeaderIndices.count == 0 || !contentView) {
|
CGFloat scrollTop = self.bounds.origin.y + self.contentInset.top;
|
||||||
|
|
||||||
|
// Find the section headers that need to be docked
|
||||||
|
__block UIView *previousHeader = nil;
|
||||||
|
__block UIView *currentHeader = nil;
|
||||||
|
__block UIView *nextHeader = nil;
|
||||||
|
NSInteger subviewCount = contentView.reactSubviews.count;
|
||||||
|
[_stickyHeaderIndices enumerateIndexesWithOptions:0 usingBlock:^(NSUInteger idx, BOOL *stop) {
|
||||||
|
|
||||||
|
if (idx >= subviewCount) {
|
||||||
|
RCTLogError(@"Sticky header index %zd was outside the range {0, %zd}", idx, subviewCount);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIView *header = contentView.reactSubviews[idx];
|
||||||
|
|
||||||
|
// If nextHeader not yet found, search for docked headers
|
||||||
|
if (!nextHeader) {
|
||||||
|
CGFloat height = header.bounds.size.height;
|
||||||
|
CGFloat top = header.center.y - height * header.layer.anchorPoint.y;
|
||||||
|
if (top > scrollTop) {
|
||||||
|
nextHeader = header;
|
||||||
|
} else {
|
||||||
|
previousHeader = currentHeader;
|
||||||
|
currentHeader = header;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset transforms for header views
|
||||||
|
header.transform = CGAffineTransformIdentity;
|
||||||
|
header.layer.zPosition = ZINDEX_DEFAULT;
|
||||||
|
|
||||||
|
}];
|
||||||
|
|
||||||
|
// If no docked header, bail out
|
||||||
|
if (!currentHeader) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// find the section header that needs to be docked
|
// Adjust current header to hug the top of the screen
|
||||||
NSInteger firstIndexInView = [[_stickyHeaderIndices firstObject] integerValue] + 1;
|
CGFloat currentFrameHeight = currentHeader.bounds.size.height;
|
||||||
CGRect scrollBounds = self.bounds;
|
CGFloat currentFrameTop = currentHeader.center.y - currentFrameHeight * currentHeader.layer.anchorPoint.y;
|
||||||
scrollBounds.origin.x += self.contentInset.left;
|
CGFloat yOffset = scrollTop - currentFrameTop;
|
||||||
scrollBounds.origin.y += self.contentInset.top;
|
if (nextHeader) {
|
||||||
|
// The next header nudges the current header out of the way when it reaches
|
||||||
NSInteger i = 0;
|
// the top of the screen
|
||||||
for (UIView *subview in contentView.reactSubviews) {
|
CGFloat nextFrameHeight = nextHeader.bounds.size.height;
|
||||||
CGRect rowFrame = [RCTCustomScrollView _calculateUntransformedFrame:subview];
|
CGFloat nextFrameTop = nextHeader.center.y - nextFrameHeight * nextHeader.layer.anchorPoint.y;
|
||||||
if (CGRectIntersectsRect(scrollBounds, rowFrame)) {
|
CGFloat overlap = currentFrameHeight - (nextFrameTop - scrollTop);
|
||||||
firstIndexInView = i;
|
yOffset -= MAX(0, overlap);
|
||||||
break;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
NSInteger stickyHeaderii = 0;
|
currentHeader.transform = CGAffineTransformMakeTranslation(0, yOffset);
|
||||||
for (NSNumber *stickyHeaderI in _stickyHeaderIndices) {
|
currentHeader.layer.zPosition = ZINDEX_STICKY_HEADER;
|
||||||
if ([stickyHeaderI integerValue] > firstIndexInView) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
stickyHeaderii++;
|
|
||||||
}
|
|
||||||
stickyHeaderii = MAX(0, stickyHeaderii - 1);
|
|
||||||
|
|
||||||
// Set up transforms for the various section headers
|
|
||||||
NSInteger currentlyDockedIndex = [_stickyHeaderIndices[stickyHeaderii] integerValue];
|
|
||||||
NSInteger previouslyDockedIndex = stickyHeaderii > 0 ? [_stickyHeaderIndices[stickyHeaderii-1] integerValue] : -1;
|
|
||||||
NSInteger nextDockedIndex = (stickyHeaderii < _stickyHeaderIndices.count - 1) ?
|
|
||||||
[_stickyHeaderIndices[stickyHeaderii + 1] integerValue] : -1;
|
|
||||||
|
|
||||||
UIView *currentHeader = contentView.reactSubviews[currentlyDockedIndex];
|
|
||||||
UIView *previousHeader = previouslyDockedIndex >= 0 ? contentView.reactSubviews[previouslyDockedIndex] : nil;
|
|
||||||
CGRect curFrame = [RCTCustomScrollView _calculateUntransformedFrame:currentHeader];
|
|
||||||
|
|
||||||
if (previousHeader) {
|
if (previousHeader) {
|
||||||
// the previous header is offset to sit right above the currentlyDockedHeader's initial position
|
// The previous header sits right above the currentHeader's initial position
|
||||||
// (so it scrolls away nicely once the currentHeader locks into position)
|
// so it scrolls away nicely once the currentHeader has locked into place
|
||||||
CGRect previousFrame = [RCTCustomScrollView _calculateUntransformedFrame:previousHeader];
|
CGFloat previousFrameHeight = previousHeader.bounds.size.height;
|
||||||
CGFloat yOffset = curFrame.origin.y - previousFrame.origin.y - previousFrame.size.height;
|
CGFloat targetCenter = currentFrameTop - previousFrameHeight * (1.0 - previousHeader.layer.anchorPoint.y);
|
||||||
|
yOffset = targetCenter - previousHeader.center.y;
|
||||||
previousHeader.transform = CGAffineTransformMakeTranslation(0, yOffset);
|
previousHeader.transform = CGAffineTransformMakeTranslation(0, yOffset);
|
||||||
|
previousHeader.layer.zPosition = ZINDEX_STICKY_HEADER;
|
||||||
}
|
}
|
||||||
|
|
||||||
UIView *nextHeader = nextDockedIndex >= 0 ? contentView.reactSubviews[nextDockedIndex] : nil;
|
|
||||||
CGRect nextFrame = [RCTCustomScrollView _calculateUntransformedFrame:nextHeader];
|
|
||||||
|
|
||||||
if (curFrame.origin.y < scrollBounds.origin.y) {
|
|
||||||
// scrolled off (or being scrolled off) the top of the screen
|
|
||||||
CGFloat yOffset = 0;
|
|
||||||
if (nextHeader && nextFrame.origin.y < scrollBounds.origin.y + curFrame.size.height) {
|
|
||||||
// next frame is bumping me off if scrolling down (or i'm bumping the next one off if scrolling up)
|
|
||||||
yOffset = nextFrame.origin.y - curFrame.origin.y - curFrame.size.height;
|
|
||||||
} else {
|
|
||||||
// standard sticky header position
|
|
||||||
yOffset = scrollBounds.origin.y - curFrame.origin.y;
|
|
||||||
}
|
|
||||||
currentHeader.transform = CGAffineTransformMakeTranslation(0, yOffset);
|
|
||||||
currentHeader.layer.zPosition = ZINDEX_STICKY_HEADER;
|
|
||||||
} else {
|
|
||||||
// i'm the current header but in the viewport, so just scroll in normal position
|
|
||||||
currentHeader.transform = CGAffineTransformIdentity;
|
|
||||||
currentHeader.layer.zPosition = ZINDEX_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// in our setup, 'next header' will always just scroll with the page
|
|
||||||
if (nextHeader) {
|
|
||||||
nextHeader.transform = CGAffineTransformIdentity;
|
|
||||||
nextHeader.layer.zPosition = ZINDEX_DEFAULT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (CGRect)_calculateUntransformedFrame:(UIView *)view
|
|
||||||
{
|
|
||||||
CGRect frame = CGRectNull;
|
|
||||||
if (view) {
|
|
||||||
frame.size = view.bounds.size;
|
|
||||||
frame.origin = CGPointMake(view.layer.position.x - view.bounds.size.width * view.layer.anchorPoint.x, view.layer.position.y - view.bounds.size.height * view.layer.anchorPoint.y);
|
|
||||||
}
|
|
||||||
return frame;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -312,7 +287,7 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
|
||||||
_scrollView.centerContent = centerContent;
|
_scrollView.centerContent = centerContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setStickyHeaderIndices:(NSArray *)headerIndices
|
- (void)setStickyHeaderIndices:(NSIndexSet *)headerIndices
|
||||||
{
|
{
|
||||||
RCTAssert(_scrollView.contentSize.width <= self.frame.size.width,
|
RCTAssert(_scrollView.contentSize.width <= self.frame.size.width,
|
||||||
@"sticky headers are not supported with horizontal scrolled views");
|
@"sticky headers are not supported with horizontal scrolled views");
|
||||||
|
@ -340,14 +315,8 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
|
||||||
|
|
||||||
- (void)setContentInset:(UIEdgeInsets)contentInset
|
- (void)setContentInset:(UIEdgeInsets)contentInset
|
||||||
{
|
{
|
||||||
CGPoint contentOffset = _scrollView.contentOffset;
|
|
||||||
|
|
||||||
_contentInset = contentInset;
|
_contentInset = contentInset;
|
||||||
[RCTView autoAdjustInsetsForView:self
|
[self setNeedsLayout];
|
||||||
withScrollView:_scrollView
|
|
||||||
updateOffset:NO];
|
|
||||||
|
|
||||||
_scrollView.contentOffset = contentOffset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)scrollToOffset:(CGPoint)offset
|
- (void)scrollToOffset:(CGPoint)offset
|
||||||
|
@ -390,6 +359,7 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove)
|
||||||
|
|
||||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||||
{
|
{
|
||||||
|
[_scrollView dockClosestSectionHeader];
|
||||||
[self updateClippedSubviews];
|
[self updateClippedSubviews];
|
||||||
|
|
||||||
NSTimeInterval now = CACurrentMediaTime();
|
NSTimeInterval now = CACurrentMediaTime();
|
||||||
|
|
|
@ -41,7 +41,7 @@ RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL)
|
||||||
RCT_EXPORT_VIEW_PROPERTY(scrollsToTop, BOOL)
|
RCT_EXPORT_VIEW_PROPERTY(scrollsToTop, BOOL)
|
||||||
RCT_EXPORT_VIEW_PROPERTY(showsHorizontalScrollIndicator, BOOL)
|
RCT_EXPORT_VIEW_PROPERTY(showsHorizontalScrollIndicator, BOOL)
|
||||||
RCT_EXPORT_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL)
|
RCT_EXPORT_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL)
|
||||||
RCT_EXPORT_VIEW_PROPERTY(stickyHeaderIndices, NSNumberArray)
|
RCT_EXPORT_VIEW_PROPERTY(stickyHeaderIndices, NSIndexSet)
|
||||||
RCT_EXPORT_VIEW_PROPERTY(scrollEventThrottle, NSTimeInterval)
|
RCT_EXPORT_VIEW_PROPERTY(scrollEventThrottle, NSTimeInterval)
|
||||||
RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat)
|
RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat)
|
||||||
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
|
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
|
||||||
|
|
Loading…
Reference in New Issue