| |
| /* |
| * 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"]) { |
| layout.columnWidth = [WXConvert WXLength:attributes[@"columnWidth"] isFloat:YES scaleFactor:scaleFactor]; |
| needUpdateLayout = YES; |
| } |
| |
| if (attributes[@"columnCount"]) { |
| layout.columnCount = [WXConvert WXLength:attributes[@"columnCount"] isFloat:NO scaleFactor:1.0]; |
| |
| needUpdateLayout = YES; |
| } |
| if (attributes[@"columnGap"]) { |
| layout.columnGap = [self _floatValueForColumnGap:([WXConvert WXLength:attributes[@"columnGap"] isFloat:YES scaleFactor:scaleFactor])]; |
| 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 |