mirror of
https://github.com/status-im/react-native.git
synced 2025-01-27 01:40:08 +00:00
Fixed broken listview header alignment
This commit is contained in:
parent
8c95de11e1
commit
790cee6e26
@ -39,6 +39,7 @@
|
||||
+ (NSString *)NSString:(id)json;
|
||||
+ (NSNumber *)NSNumber:(id)json;
|
||||
+ (NSData *)NSData:(id)json;
|
||||
+ (NSIndexSet *)NSIndexSet:(id)json;
|
||||
|
||||
+ (NSURL *)NSURL:(id)json;
|
||||
+ (NSURLRequest *)NSURLRequest:(id)json;
|
||||
|
@ -64,6 +64,20 @@ RCT_CONVERTER(NSString *, NSString, description)
|
||||
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
|
||||
{
|
||||
NSString *path = [self NSString:json];
|
||||
|
@ -119,8 +119,6 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
- (void)updateSettings
|
||||
{
|
||||
_settings = [NSMutableDictionary dictionaryWithDictionary:[_defaults objectForKey:RCTDevMenuSettingsKey]];
|
||||
|
||||
__weak RCTDevMenu *weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
RCTDevMenu *strongSelf = weakSelf;
|
||||
@ -128,6 +126,8 @@ RCT_EXPORT_MODULE()
|
||||
return;
|
||||
}
|
||||
|
||||
strongSelf->_settings = [NSMutableDictionary dictionaryWithDictionary:[strongSelf->_defaults objectForKey:RCTDevMenuSettingsKey]];
|
||||
|
||||
strongSelf.shakeToShow = [strongSelf->_settings[@"shakeToShow"] ?: @YES boolValue];
|
||||
strongSelf.profilingEnabled = [strongSelf->_settings[@"profilingEnabled"] ?: @NO boolValue];
|
||||
strongSelf.liveReloadEnabled = [strongSelf->_settings[@"liveReloadEnabled"] ?: @NO boolValue];
|
||||
|
@ -45,6 +45,6 @@
|
||||
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
|
||||
@property (nonatomic, assign) NSTimeInterval scrollEventThrottle;
|
||||
@property (nonatomic, assign) BOOL centerContent;
|
||||
@property (nonatomic, copy) NSArray *stickyHeaderIndices;
|
||||
@property (nonatomic, copy) NSIndexSet *stickyHeaderIndices;
|
||||
|
||||
@end
|
||||
|
@ -28,8 +28,8 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
|
||||
*/
|
||||
@interface RCTCustomScrollView : UIScrollView<UIGestureRecognizerDelegate>
|
||||
|
||||
@property (nonatomic, copy, readwrite) NSArray *stickyHeaderIndices;
|
||||
@property (nonatomic, readwrite, assign) BOOL centerContent;
|
||||
@property (nonatomic, copy) NSIndexSet *stickyHeaderIndices;
|
||||
@property (nonatomic, assign) BOOL centerContent;
|
||||
|
||||
@end
|
||||
|
||||
@ -155,97 +155,72 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
|
||||
[super setContentOffset:contentOffset];
|
||||
}
|
||||
|
||||
- (void)setBounds:(CGRect)bounds
|
||||
{
|
||||
[super setBounds:bounds];
|
||||
[self dockClosestSectionHeader];
|
||||
}
|
||||
|
||||
- (void)dockClosestSectionHeader
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// find the section header that needs to be docked
|
||||
NSInteger firstIndexInView = [[_stickyHeaderIndices firstObject] integerValue] + 1;
|
||||
CGRect scrollBounds = self.bounds;
|
||||
scrollBounds.origin.x += self.contentInset.left;
|
||||
scrollBounds.origin.y += self.contentInset.top;
|
||||
|
||||
NSInteger i = 0;
|
||||
for (UIView *subview in contentView.reactSubviews) {
|
||||
CGRect rowFrame = [RCTCustomScrollView _calculateUntransformedFrame:subview];
|
||||
if (CGRectIntersectsRect(scrollBounds, rowFrame)) {
|
||||
firstIndexInView = i;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
// Adjust current header to hug the top of the screen
|
||||
CGFloat currentFrameHeight = currentHeader.bounds.size.height;
|
||||
CGFloat currentFrameTop = currentHeader.center.y - currentFrameHeight * currentHeader.layer.anchorPoint.y;
|
||||
CGFloat yOffset = scrollTop - currentFrameTop;
|
||||
if (nextHeader) {
|
||||
// The next header nudges the current header out of the way when it reaches
|
||||
// the top of the screen
|
||||
CGFloat nextFrameHeight = nextHeader.bounds.size.height;
|
||||
CGFloat nextFrameTop = nextHeader.center.y - nextFrameHeight * nextHeader.layer.anchorPoint.y;
|
||||
CGFloat overlap = currentFrameHeight - (nextFrameTop - scrollTop);
|
||||
yOffset -= MAX(0, overlap);
|
||||
}
|
||||
NSInteger stickyHeaderii = 0;
|
||||
for (NSNumber *stickyHeaderI in _stickyHeaderIndices) {
|
||||
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];
|
||||
currentHeader.transform = CGAffineTransformMakeTranslation(0, yOffset);
|
||||
currentHeader.layer.zPosition = ZINDEX_STICKY_HEADER;
|
||||
|
||||
if (previousHeader) {
|
||||
// the previous header is offset to sit right above the currentlyDockedHeader's initial position
|
||||
// (so it scrolls away nicely once the currentHeader locks into position)
|
||||
CGRect previousFrame = [RCTCustomScrollView _calculateUntransformedFrame:previousHeader];
|
||||
CGFloat yOffset = curFrame.origin.y - previousFrame.origin.y - previousFrame.size.height;
|
||||
// The previous header sits right above the currentHeader's initial position
|
||||
// so it scrolls away nicely once the currentHeader has locked into place
|
||||
CGFloat previousFrameHeight = previousHeader.bounds.size.height;
|
||||
CGFloat targetCenter = currentFrameTop - previousFrameHeight * (1.0 - previousHeader.layer.anchorPoint.y);
|
||||
yOffset = targetCenter - previousHeader.center.y;
|
||||
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
|
||||
@ -312,7 +287,7 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
|
||||
_scrollView.centerContent = centerContent;
|
||||
}
|
||||
|
||||
- (void)setStickyHeaderIndices:(NSArray *)headerIndices
|
||||
- (void)setStickyHeaderIndices:(NSIndexSet *)headerIndices
|
||||
{
|
||||
RCTAssert(_scrollView.contentSize.width <= self.frame.size.width,
|
||||
@"sticky headers are not supported with horizontal scrolled views");
|
||||
@ -340,14 +315,8 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
|
||||
|
||||
- (void)setContentInset:(UIEdgeInsets)contentInset
|
||||
{
|
||||
CGPoint contentOffset = _scrollView.contentOffset;
|
||||
|
||||
_contentInset = contentInset;
|
||||
[RCTView autoAdjustInsetsForView:self
|
||||
withScrollView:_scrollView
|
||||
updateOffset:NO];
|
||||
|
||||
_scrollView.contentOffset = contentOffset;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)scrollToOffset:(CGPoint)offset
|
||||
@ -390,6 +359,7 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove)
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
{
|
||||
[_scrollView dockClosestSectionHeader];
|
||||
[self updateClippedSubviews];
|
||||
|
||||
NSTimeInterval now = CACurrentMediaTime();
|
||||
|
@ -41,7 +41,7 @@ RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(scrollsToTop, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(showsHorizontalScrollIndicator, 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(zoomScale, CGFloat)
|
||||
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
|
||||
|
Loading…
x
Reference in New Issue
Block a user