blob: 22be5751237ed050b844b30ab3f1834262f4b545 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#import "WXRecyclerComponent.h"
#import "WXComponent_internal.h"
#import "WXSDKInstance_private.h"
#import "WXRecyclerDataController.h"
#import "WXRecyclerUpdateController.h"
#import "WXMultiColumnLayout.h"
#import "WXHeaderComponent.h"
#import "WXFooterComponent.h"
#import "WXCellComponent.h"
#import "WXAssert.h"
#import "WXConvert.h"
#import "WXUtility.h"
#import "WXMonitor.h"
#import "NSObject+WXSwizzle.h"
#import "WXComponent+Events.h"
#import "WXRecyclerDragController.h"
#import "WXComponent+Layout.h"
static NSString * const kCollectionCellReuseIdentifier = @"WXRecyclerCell";
static NSString * const kCollectionHeaderReuseIdentifier = @"WXRecyclerHeader";
static float const kRecyclerNormalColumnGap = 32;
typedef enum : NSUInteger {
WXRecyclerLayoutTypeMultiColumn,
WXRecyclerLayoutTypeFlex,
WXRecyclerLayoutTypeGrid,
} WXRecyclerLayoutType;
@interface WXCollectionView : UICollectionView
@end
@implementation WXCollectionView
- (void)dealloc
{
self.delegate = nil;
self.dataSource = nil;
if ([self.collectionViewLayout isKindOfClass:[WXMultiColumnLayout class]]) {
WXMultiColumnLayout* wxLayout = (WXMultiColumnLayout *)self.collectionViewLayout;
wxLayout.weak_collectionView = nil;
}
}
- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index
{
[super insertSubview:view atIndex:index];
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self.wx_component layoutDidFinish];
}
- (void)setContentOffset:(CGPoint)contentOffset
{
// FIXME: side effect caused by hooking _adjustContentOffsetIfNecessary.
// When UICollectionView is pulled down and finger releases,contentOffset will be set from -xxxx to about -0.5(greater than -0.5), then contentOffset will be reset to zero by calling _adjustContentOffsetIfNecessary.
// So hooking _adjustContentOffsetIfNecessary will always cause remaining 1px space between list's top and navigator.
// Demo: http://dotwe.org/895630945793a9a044e49abe39cbb77f
// Have to reset contentOffset to zero manually here.
if (fabs(contentOffset.y) < 0.5) {
contentOffset.y = 0;
}
if (isnan(contentOffset.x)) {
contentOffset.x = 0;
}
if(isnan(contentOffset.y)) {
contentOffset.y = 0;
}
[super setContentOffset:contentOffset];
}
@end
@interface WXCollectionViewCell : UICollectionViewCell
@end
@implementation WXCollectionViewCell
- (void)prepareForReuse
{
[super prepareForReuse];
WXCellComponent *cellComponent = (WXCellComponent *)self.wx_component;
if (cellComponent.isRecycle && [cellComponent isViewLoaded] && [self.contentView.subviews containsObject:cellComponent.view]) {
[cellComponent _unloadViewWithReusing:YES];
}
}
@end
@interface WXRecyclerComponent () <UICollectionViewDataSource, UICollectionViewDelegate, WXMultiColumnLayoutDelegate, WXRecyclerUpdateControllerDelegate, WXCellRenderDelegate, WXHeaderRenderDelegate, WXRecyclerDragControllerDelegate>
@property (nonatomic, strong, readonly) WXRecyclerDataController *dataController;
@property (nonatomic, strong, readonly) WXRecyclerUpdateController *updateController;
@property (nonatomic, weak, readonly) UICollectionView *collectionView;
@property (nonatomic, strong) WXRecyclerDragController *dragController;
@end
@implementation WXRecyclerComponent
{
WXRecyclerLayoutType _layoutType;
UICollectionViewLayout *_collectionViewlayout;
UIEdgeInsets _padding;
NSUInteger _previousLoadMoreCellNumber;
}
- (instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance
{
if (self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]) {
[self _fillPadding];
if ([type isEqualToString:@"waterfall"] || (attributes[@"layout"] && [attributes[@"layout"] isEqualToString:@"multi-column"])) {
// TODO: abstraction
_layoutType = WXRecyclerLayoutTypeMultiColumn;
CGFloat scaleFactor = weexInstance.pixelScaleFactor;
_collectionViewlayout = [WXMultiColumnLayout new];
WXMultiColumnLayout *layout = (WXMultiColumnLayout *)_collectionViewlayout;
layout.columnWidth = [WXConvert WXLength:attributes[@"columnWidth"] isFloat:YES scaleFactor:scaleFactor] ? : [WXLength lengthWithFloat:0.0 type:WXLengthTypeAuto];
layout.columnCount = [WXConvert WXLength:attributes[@"columnCount"] isFloat:NO scaleFactor:1.0] ? : [WXLength lengthWithInt:1 type:WXLengthTypeFixed];
if (attributes[@"leftGap"]) {
layout.leftGap = [WXConvert WXPixelType:attributes[@"leftGap"] scaleFactor:scaleFactor];
}
if (attributes[@"rightGap"]) {
layout.rightGap = [WXConvert WXPixelType:attributes[@"rightGap"] scaleFactor:scaleFactor];
}
layout.columnGap = [self _floatValueForColumnGap:([WXConvert WXLength:attributes[@"columnGap"] isFloat:YES scaleFactor:scaleFactor] ? : [WXLength lengthWithFloat:0.0 type:WXLengthTypeNormal])];
layout.delegate = self;
} else {
_collectionViewlayout = [UICollectionViewLayout new];
}
_dataController = [WXRecyclerDataController new];
_updateController = [WXRecyclerUpdateController new];
_updateController.delegate = self;
[self fixFlicker];
if ([attributes[@"draggable"] boolValue]) {
// lazy load
_dragController = [WXRecyclerDragController new];
_dragController.delegate = self;
if([attributes[@"dragTriggerType"] isEqual: @"pan"]){
_dragController.dragTriggerType = WXRecyclerDragTriggerPan;
}
_dragController.isDragable = YES;
}
}
return self;
}
- (void)dealloc
{
_collectionView.delegate = nil;
_collectionView.dataSource = nil;
if ([_collectionViewlayout isKindOfClass:[WXMultiColumnLayout class]]) {
WXMultiColumnLayout* wxLayout = (WXMultiColumnLayout *)_collectionViewlayout;
wxLayout.weak_collectionView = nil;
}
}
#pragma mark - Public Subclass Methods
- (UIView *)loadView
{
return [[WXCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:_collectionViewlayout];
}
- (void)viewDidLoad
{
[super viewDidLoad];
_collectionView = (UICollectionView *)self.view;
_collectionView.allowsSelection = NO;
_collectionView.allowsMultipleSelection = NO;
_collectionView.dataSource = self;
_collectionView.delegate = self;
if ([_collectionViewlayout isKindOfClass:[WXMultiColumnLayout class]]) {
WXMultiColumnLayout* wxLayout = (WXMultiColumnLayout *)_collectionViewlayout;
wxLayout.weak_collectionView = _collectionView;
}
[_collectionView registerClass:[WXCollectionViewCell class] forCellWithReuseIdentifier:kCollectionCellReuseIdentifier];
[_collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:kCollectionSupplementaryViewKindHeader withReuseIdentifier:kCollectionHeaderReuseIdentifier];
_dragController.dragingCell = [[WXCollectionViewCell alloc] initWithFrame:CGRectMake(0, 0, 100, 100/2.0f)];
_dragController.collectionView = _collectionView;
[self performUpdatesWithCompletion:^(BOOL finished) {
}];
}
- (void)viewWillUnload
{
[super viewWillUnload];
_collectionView.dataSource = nil;
_collectionView.delegate = nil;
if ([_collectionViewlayout isKindOfClass:[WXMultiColumnLayout class]]) {
WXMultiColumnLayout* wxLayout = (WXMultiColumnLayout *)_collectionViewlayout;
wxLayout.weak_collectionView = nil;
}
}
- (void)updateAttributes:(NSDictionary *)attributes
{
[super updateAttributes:attributes];
if (_layoutType == WXRecyclerLayoutTypeMultiColumn) {
CGFloat scaleFactor = self.weexInstance.pixelScaleFactor;
WXMultiColumnLayout *layout = (WXMultiColumnLayout *)_collectionViewlayout;
BOOL needUpdateLayout = NO;
if ([attributes[@"draggable"] boolValue]) {
if (!_dragController) { // lazy load
_dragController = [WXRecyclerDragController new];
_dragController.delegate = self;
}
if([attributes[@"dragTriggerType"] isEqual: @"pan"]){
_dragController.dragTriggerType = WXRecyclerDragTriggerPan;
}
_dragController.isDragable = YES;
} else {
_dragController.isDragable = NO;
}
if (attributes[@"columnWidth"]) {
WXLength* columnWidth = [WXConvert WXLength:attributes[@"columnWidth"] isFloat:YES scaleFactor:scaleFactor];
if (![columnWidth isEqualToLength:layout.columnWidth]) {
layout.columnWidth = columnWidth;
needUpdateLayout = YES;
}
}
if (attributes[@"columnCount"]) {
WXLength* columCount = [WXConvert WXLength:attributes[@"columnCount"] isFloat:NO scaleFactor:1.0];
if (![columCount isEqualToLength:layout.columnCount]) {
layout.columnCount = columCount;
needUpdateLayout = YES;
}
}
if (attributes[@"columnGap"]) {
float columnGap = [self _floatValueForColumnGap:([WXConvert WXLength:attributes[@"columnGap"] isFloat:YES scaleFactor:scaleFactor])];
if (columnGap != layout.columnGap) {
layout.columnGap = columnGap;
needUpdateLayout = YES;
}
}
if (attributes[@"leftGap"]) {
layout.leftGap = [WXConvert WXPixelType:attributes[@"leftGap"] scaleFactor:scaleFactor];
}
if (attributes[@"rightGap"]) {
layout.rightGap = [WXConvert WXPixelType:attributes[@"rightGap"] scaleFactor:scaleFactor];
}
if (needUpdateLayout) {
for (WXComponent *component in self.subcomponents) {
[component setNeedsLayout];
}
[self.collectionView reloadData];
[self.collectionView.collectionViewLayout invalidateLayout];
}
}
}
- (void)setContentSize:(CGSize)contentSize
{
// Do Nothing
}
- (void)adjustSticky
{
// Do Nothing, sticky is adjusted by layout
}
#pragma mark - Private Subclass Methods
- (void)_updateStylesOnComponentThread:(NSDictionary *)styles resetStyles:(NSMutableArray *)resetStyles isUpdateStyles:(BOOL)isUpdateStyles
{
[super _updateStylesOnComponentThread:styles resetStyles:resetStyles isUpdateStyles:isUpdateStyles];
[self _fillPadding];
}
- (void)_handleFirstScreenTime
{
// Do Nothing, firstScreenTime is set by cellDidRendered:
}
- (void)scrollToComponent:(WXComponent *)component withOffset:(CGFloat)offset animated:(BOOL)animated
{
CGPoint contentOffset = _collectionView.contentOffset;
CGFloat contentOffsetY = 0;
CGRect rect;
while (component) {
if ([component isKindOfClass:[WXCellComponent class]]) {
NSIndexPath *toIndexPath = [self.dataController indexPathForCell:(WXCellComponent *)component];
UICollectionViewLayoutAttributes *attributes = [_collectionView layoutAttributesForItemAtIndexPath:toIndexPath];
rect = attributes.frame;
break;
}
if ([component isKindOfClass:[WXHeaderComponent class]]) {
NSUInteger toIndex = [self.dataController indexForHeader:(WXHeaderComponent *)component];
UICollectionViewLayoutAttributes *attributes = [_collectionView layoutAttributesForSupplementaryElementOfKind:kCollectionSupplementaryViewKindHeader atIndexPath:[NSIndexPath indexPathWithIndex:toIndex]];
rect = attributes.frame;
break;
}
contentOffsetY += component.calculatedFrame.origin.y;
component = component.supercomponent;
}
contentOffsetY += rect.origin.y;
contentOffsetY += offset * self.weexInstance.pixelScaleFactor;
if (_collectionView.contentSize.height >= _collectionView.frame.size.height && contentOffsetY > _collectionView.contentSize.height - _collectionView.frame.size.height) {
contentOffset.y = _collectionView.contentSize.height - _collectionView.frame.size.height;
} else {
contentOffset.y = contentOffsetY;
}
[_collectionView setContentOffset:contentOffset animated:animated];
}
- (void)performUpdatesWithCompletion:(void (^)(BOOL finished))completion
{
WXAssertMainThread();
//TODO: support completion
if (![self isViewLoaded]) {
completion(NO);
}
NSArray *oldData = [self.dataController.sections copy];
NSArray *newData = [self _sectionArrayFromComponents:self.subcomponents];
[_updateController performUpdatesWithNewData:newData oldData:oldData view:_collectionView];
}
- (BOOL)_insertSubcomponent:(WXComponent *)subcomponent atIndex:(NSInteger)index
{
if ([subcomponent isKindOfClass:[WXCellComponent class]]) {
((WXCellComponent *)subcomponent).delegate = self;
} else if ([subcomponent isKindOfClass:[WXHeaderComponent class]]) {
((WXHeaderComponent *)subcomponent).delegate = self;
}
BOOL inserted = [super _insertSubcomponent:subcomponent atIndex:index];
if (![subcomponent isKindOfClass:[WXHeaderComponent class]]
&& ![subcomponent isKindOfClass:[WXCellComponent class]]) {
return inserted;
}
WXPerformBlockOnMainThread(^{
[self performUpdatesWithCompletion:^(BOOL finished) {
// void
}];
});
return inserted;
}
- (void)insertSubview:(WXComponent *)subcomponent atIndex:(NSInteger)index
{
//Here will not insert cell/header/footer's view again
if (![subcomponent isKindOfClass:[WXCellComponent class]]
&& ![subcomponent isKindOfClass:[WXHeaderComponent class]]
&& ![subcomponent isKindOfClass:[WXFooterComponent class]]) {
[super insertSubview:subcomponent atIndex:index];
}
}
#pragma mark - WXRecyclerUpdateControllerDelegate
- (void)updateController:(WXRecyclerUpdateController *)controller willPerformUpdateWithNewData:(NSArray<WXSectionDataController *> *)newData
{
if (newData) {
[self.dataController updateData:newData];
}
}
- (void)updateController:(WXRecyclerUpdateController *)controller didPerformUpdateWithFinished:(BOOL)finished
{
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
WXLogDebug(@"section number:%li", (long)[self.dataController numberOfSections]);
return [self.dataController numberOfSections];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
NSInteger numberOfItems = [self.dataController numberOfItemsInSection:section];
WXLogDebug(@"Number of items is %ld in section:%ld", (long)numberOfItems, (long)section);
return numberOfItems;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
WXLogDebug(@"Getting cell at indexPath:%@", indexPath);
WXCollectionViewCell *cellView = [_collectionView dequeueReusableCellWithReuseIdentifier:kCollectionCellReuseIdentifier forIndexPath:indexPath];
UIView *contentView = [self.dataController cellForItemAtIndexPath:indexPath];
cellView.wx_component = contentView.wx_component;
[self.dragController goThroughAnchor:cellView.wx_component indexPath:indexPath];
if (contentView.superview == cellView.contentView) {
return cellView;
}
for (UIView *view in cellView.contentView.subviews) {
[view removeFromSuperview];
}
[cellView.contentView addSubview:contentView];
[cellView setAccessibilityIdentifier:contentView.accessibilityIdentifier];
return cellView;
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
UICollectionReusableView *reusableView = nil;
if ([kind isEqualToString:kCollectionSupplementaryViewKindHeader]) {
reusableView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:kCollectionHeaderReuseIdentifier forIndexPath:indexPath];
UIView *contentView = [self.dataController viewForHeaderAtIndexPath:indexPath];
if (contentView.superview != reusableView) {
for (UIView *view in reusableView.subviews) {
[view removeFromSuperview];
}
[reusableView addSubview:contentView];
}
}
return reusableView;
}
#pragma mark - UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
WXLogDebug(@"will display cell:%@, at index path:%@", cell, indexPath);
}
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
WXLogDebug(@"Did end displaying cell:%@, at index path:%@", cell, indexPath);
}
#pragma mark - WXMultiColumnLayoutDelegate
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView insetForLayout:(UICollectionViewLayout *)collectionViewLayout
{
return _padding;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView contentWidthForLayout:(UICollectionViewLayout *)collectionViewLayout
{
return [self safeContainerStyleWidth];
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout heightForItemAtIndexPath:(NSIndexPath *)indexPath
{
CGSize itemSize = [self.dataController sizeForItemAtIndexPath:indexPath];
return itemSize.height;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout heightForHeaderInSection:(NSInteger)section
{
CGSize headerSize = [self.dataController sizeForHeaderAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
return headerSize.height;
}
- (BOOL)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout hasHeaderInSection:(NSInteger)section
{
return [self.dataController hasHeaderInSection:section];
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout isNeedStickyForHeaderInSection:(NSInteger)section
{
return [self.dataController isStickyForHeaderAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
}
#pragma mark - WXHeaderRenderDelegate
- (float)headerWidthForLayout:(WXHeaderComponent *)header
{
if (_layoutType == WXRecyclerLayoutTypeMultiColumn) {
return ((WXMultiColumnLayout *)_collectionViewlayout).computedHeaderWidth;
}
return 0.0;
}
- (void)headerDidLayout:(WXHeaderComponent *)header
{
WXPerformBlockOnMainThread(^{
[self.collectionView.collectionViewLayout invalidateLayout];
});
}
- (void)headerDidRemove:(WXHeaderComponent *)header
{
WXPerformBlockOnMainThread(^{
[self performUpdatesWithCompletion:^(BOOL finished) {
}];
});
}
#pragma mark - WXCellRenderDelegate
- (float)containerWidthForLayout:(WXCellComponent *)cell
{
if (_layoutType == WXRecyclerLayoutTypeMultiColumn) {
return ((WXMultiColumnLayout *)_collectionViewlayout).computedColumnWidth;
}
return 0.0;
}
- (void)cellDidLayout:(WXCellComponent *)cell
{
BOOL previousLayoutComplete = cell.isLayoutComplete;
cell.isLayoutComplete = YES;
WXPerformBlockOnMainThread(^{
if (previousLayoutComplete) {
[self.updateController reloadItemsAtIndexPath:[self.dataController indexPathForCell:cell]];
} else {
[self performUpdatesWithCompletion:^(BOOL finished) {
}];
}
});
}
- (void)cellDidRendered:(WXCellComponent *)cell
{
if (WX_MONITOR_INSTANCE_PERF_IS_RECORDED(WXPTFirstScreenRender, self.weexInstance) && !self.weexInstance.onRenderProgress) {
return;
}
NSIndexPath *indexPath = [self.dataController indexPathForCell:cell];
UICollectionViewLayoutAttributes *attributes = [self.collectionView layoutAttributesForItemAtIndexPath:indexPath];
CGRect cellRect = attributes.frame;
if (cellRect.origin.y + cellRect.size.height >= _collectionView.frame.size.height) {
WX_MONITOR_INSTANCE_PERF_END(WXPTFirstScreenRender, self.weexInstance);
}
if (self.weexInstance.onRenderProgress) {
CGRect renderRect = [_collectionView convertRect:cellRect toView:self.weexInstance.rootView];
self.weexInstance.onRenderProgress(renderRect);
}
}
- (void)cellDidRemove:(WXCellComponent *)cell
{
if (cell.isLayoutComplete) {
WXPerformBlockOnMainThread(^{
[self performUpdatesWithCompletion:^(BOOL finished) {
}];
});
}
}
- (void)cell:(WXCellComponent *)cell didMoveToIndex:(NSUInteger)index
{
if (cell.isLayoutComplete) {
WXPerformBlockOnMainThread(^{
[self performUpdatesWithCompletion:^(BOOL finished) {
}];
});
}
}
#pragma mark - Load More Event
- (void)setLoadmoreretry:(NSUInteger)loadmoreretry
{
if (loadmoreretry != self.loadmoreretry) {
_previousLoadMoreCellNumber = 0;
}
[super setLoadmoreretry:loadmoreretry];
}
- (void)loadMore
{
[super loadMore];
_previousLoadMoreCellNumber = [self totalNumberOfCells];
}
- (BOOL)isNeedLoadMore
{
BOOL superNeedLoadMore = [super isNeedLoadMore];
return superNeedLoadMore && _previousLoadMoreCellNumber != [self totalNumberOfCells];
}
- (NSUInteger)totalNumberOfCells
{
NSUInteger cellNumber = 0;
NSUInteger sectionCount = [_collectionView numberOfSections];
for (int section = 0; section < sectionCount; section ++) {
cellNumber += [_collectionView numberOfItemsInSection:section];
}
return cellNumber;
}
- (void)resetLoadmore{
[super resetLoadmore];
_previousLoadMoreCellNumber = 0;
}
#pragma mark - Private
- (float)_floatValueForColumnGap:(WXLength *)gap
{
if (gap.isNormal) {
return kRecyclerNormalColumnGap * self.weexInstance.pixelScaleFactor;
} else {
return gap.floatValue;
}
}
- (void)_fillPadding
{
if (self.flexCssNode == nullptr) {
return;
}
UIEdgeInsets padding = {
WXFloorPixelValue(self.flexCssNode->getPaddingTop() + self.flexCssNode->getBorderWidthTop()),
WXFloorPixelValue(self.flexCssNode->getPaddingLeft() + self.flexCssNode->getBorderWidthLeft()),
WXFloorPixelValue(self.flexCssNode->getPaddingBottom() + self.flexCssNode->getBorderWidthBottom()),
WXFloorPixelValue(self.flexCssNode->getPaddingRight() + self.flexCssNode->getBorderWidthRight())
};
if (!UIEdgeInsetsEqualToEdgeInsets(padding, _padding)) {
_padding = padding;
[self setNeedsLayout];
for (WXComponent *component in self.subcomponents) {
[component setNeedsLayout];
}
if (_collectionView) {
WXPerformBlockOnMainThread(^{
[_collectionView.collectionViewLayout invalidateLayout];
});
}
}
}
- (NSArray<WXSectionDataController *> *)_sectionArrayFromComponents:(NSArray<WXComponent *> *)components
{
NSMutableArray<WXSectionDataController *> *sectionArray = [NSMutableArray array];
NSMutableArray<WXCellComponent *> *cellArray = [NSMutableArray array];
WXSectionDataController *currentSection;
for (int i = 0; i < components.count; i++) {
if (!currentSection) {
currentSection = [WXSectionDataController new];
}
WXComponent* component = components[i];
if ([component isKindOfClass:[WXHeaderComponent class]]) {
if (i != 0 && (currentSection.headerComponent || cellArray.count > 0)) {
currentSection.cellComponents = [cellArray copy];
[sectionArray addObject:currentSection];
currentSection = [WXSectionDataController new];
[cellArray removeAllObjects];
}
currentSection.headerComponent = (WXHeaderComponent *)component;
} else if ([component isKindOfClass:[WXCellComponent class]]
&& ((WXCellComponent *)component).isLayoutComplete) {
[cellArray addObject:(WXCellComponent *)component];
} else if ([component isKindOfClass:[WXFooterComponent class]]) {
currentSection.footerComponent = component;
} else {
continue;
}
}
if (cellArray.count > 0 || currentSection.headerComponent) {
currentSection.cellComponents = [cellArray copy];
[sectionArray addObject:currentSection];
}
return sectionArray;
}
- (void)fixFlicker
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// FIXME:(ง •̀_•́)ง┻━┻ Stupid scoll view, always reset content offset to zero by calling _adjustContentOffsetIfNecessary after insert cells.
// So if you pull down list while list is rendering, the list will be flickering.
// Demo:
// Have to hook _adjustContentOffsetIfNecessary here.
// Any other more elegant way?
NSString *a = @"ntOffsetIfNe";
NSString *b = @"adjustConte";
NSString *originSelector = [NSString stringWithFormat:@"_%@%@cessary", b, a];
[[self class] weex_swizzle:[WXCollectionView class] Method:NSSelectorFromString(originSelector) withMethod:@selector(fixedFlickerSelector)];
});
}
#define mark dragControllerDelegate
- (void)updateDataSource{
NSMutableArray *oldComponents = [[NSMutableArray alloc] initWithArray:self.dataController.sections[self.dragController.startIndexPath.section].cellComponents];
if(oldComponents.count > 1){
WXCellComponent *startComponent = self.dataController.sections[self.dragController.startIndexPath.section].cellComponents[self.dragController.startIndexPath.item];
[oldComponents removeObject:startComponent];
[oldComponents insertObject:startComponent atIndex:self.dragController.targetIndexPath.item];
self.dataController.sections[self.dragController.startIndexPath.section].cellComponents = oldComponents;
}
}
- (void)dragFireEvent:(NSString *)eventName params:(NSDictionary *)params{
[self fireEvent:eventName params:params];
}
- (void)fixedFlickerSelector
{
// DO NOT delete this method.
}
@end