| /* |
| * 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 "WXLog.h" |
| #import "WXUtility.h" |
| #import "WXComponent_internal.h" |
| #import "WXComponentManager.h" |
| #import "WXSDKInstance_private.h" |
| |
| #import "WXCellSlotComponent.h" |
| #import "WXRecycleListLayout.h" |
| #import "WXRecycleListComponent.h" |
| #import "WXRecycleListDataManager.h" |
| #import "WXRecycleListTemplateManager.h" |
| #import "WXRecycleListUpdateManager.h" |
| #import "WXBridgeManager.h" |
| #import "WXSDKManager.h" |
| #import "WXComponent+DataBinding.h" |
| #import "WXComponent+Layout.h" |
| #import "WXModuleProtocol.h" |
| |
| @interface WXRecycleListComponentView:UICollectionView |
| @end |
| |
| @implementation WXRecycleListComponentView |
| - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch |
| { |
| if ([(id <WXScrollerProtocol>) self.wx_component respondsToSelector:@selector(requestGestureShouldStopPropagation:shouldReceiveTouch:)]) { |
| return [(id <WXScrollerProtocol>) self.wx_component requestGestureShouldStopPropagation:gestureRecognizer shouldReceiveTouch:touch]; |
| } |
| else{ |
| return YES; |
| } |
| } |
| |
| @end |
| |
| @interface WXRecycleListComponent () <WXRecycleListLayoutDelegate, WXRecycleListUpdateDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource> |
| |
| @end |
| |
| @implementation WXRecycleListComponent |
| { |
| NSString *_templateSwitchKey; |
| NSString *_aliasKey; |
| NSString *_indexKey; |
| __weak UICollectionView *_collectionView; |
| |
| NSMutableDictionary *_sizeCache; |
| NSMutableDictionary *_stickyCache; |
| |
| NSUInteger _previousLoadMoreCellNumber; |
| } |
| |
| WX_EXPORT_METHOD(@selector(appendData:)) |
| WX_EXPORT_METHOD(@selector(appendRange:)) |
| WX_EXPORT_METHOD(@selector(insertData:data:)) |
| WX_EXPORT_METHOD(@selector(updateData:data:)) |
| WX_EXPORT_METHOD(@selector(removeData:count:)) |
| WX_EXPORT_METHOD(@selector(moveData:toIndex:)) |
| WX_EXPORT_METHOD(@selector(insertRange:range:)) |
| WX_EXPORT_METHOD(@selector(setListData:)) |
| WX_EXPORT_METHOD(@selector(scrollTo:options:)) |
| WX_EXPORT_METHOD(@selector(scrollToElement:options:)) |
| WX_EXPORT_METHOD(@selector(queryElement:cssSelector:callback:)) |
| WX_EXPORT_METHOD(@selector(queryElementAll:cssSelector:callback:)) |
| WX_EXPORT_METHOD(@selector(closest:cssSelector:callback:)) |
| |
| - (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]) { |
| _dataManager = attributes[@"listData"]? [[WXRecycleListDataManager alloc] initWithData:attributes[@"listData"]] : [WXRecycleListDataManager new]; |
| _templateManager = [WXRecycleListTemplateManager new]; |
| _updateManager = [WXRecycleListUpdateManager new]; |
| _updateManager.delegate = self; |
| _templateSwitchKey = [WXConvert NSString:attributes[@"switch"]]; |
| _aliasKey = [WXConvert NSString:attributes[@"alias"]]; |
| _indexKey = [WXConvert NSString:attributes[@"index"]]; |
| _sizeCache = [NSMutableDictionary dictionary]; |
| _stickyCache = [NSMutableDictionary dictionary]; |
| } |
| |
| return self; |
| } |
| |
| #pragma mark - WXComponent Methods |
| |
| - (UIView *)loadView |
| { |
| WXRecycleListLayout *layout = [self recycleListLayout]; |
| return [[WXRecycleListComponentView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; |
| } |
| |
| - (void)viewDidLoad |
| { |
| [super viewDidLoad]; |
| |
| _collectionView = (UICollectionView *)self.view; |
| _collectionView.allowsSelection = NO; |
| _collectionView.allowsMultipleSelection = NO; |
| _collectionView.dataSource = self; |
| _collectionView.delegate = self; |
| |
| _templateManager.collectionView = _collectionView; |
| _updateManager.collectionView = _collectionView; |
| } |
| |
| - (void)viewWillUnload |
| { |
| [super viewWillUnload]; |
| |
| _collectionView.dataSource = nil; |
| _collectionView.delegate = nil; |
| } |
| |
| - (void)updateAttributes:(NSDictionary *)attributes |
| { |
| [super updateAttributes:attributes]; |
| |
| if (attributes[@"listData"]) { |
| NSArray *listData = attributes[@"listData"]; |
| [self _updateListData:listData withCompletion:nil animation:NO]; |
| } |
| if (attributes[@"switch"]) { |
| _templateSwitchKey = [WXConvert NSString:attributes[@"switch"]]; |
| } |
| if (attributes[@"alias"]) { |
| _aliasKey = [WXConvert NSString:attributes[@"alias"]]; |
| } |
| if (attributes[@"index"]) { |
| _indexKey = [WXConvert NSString:attributes[@"index"]]; |
| } |
| if (attributes[@"scrollDirection"]) { |
| WXScrollDirection newScrollDirection = attributes[@"scrollDirection"] ? [WXConvert WXScrollDirection:attributes[@"scrollDirection"]] : WXScrollDirectionVertical; |
| [self _updateScrollDirection:newScrollDirection]; |
| } |
| } |
| |
| - (CGPoint)absolutePositionForComponent:(WXComponent *)component |
| { |
| CGPoint position = CGPointZero; |
| UIView *view = component->_view; |
| while (view) { |
| if ([view isKindOfClass:[UICollectionViewCell class]]) { |
| NSIndexPath *indexPath = [_collectionView indexPathForCell:(UICollectionViewCell *)view]; |
| if (!indexPath) { |
| return CGPointMake(NAN, NAN); |
| } |
| UICollectionViewLayoutAttributes *attributes = [_collectionView layoutAttributesForItemAtIndexPath:indexPath]; |
| CGPoint cellOrigin = attributes.frame.origin; |
| position = CGPointMake(position.x + cellOrigin.x, |
| position.y + cellOrigin.y); |
| break; |
| } |
| position = CGPointMake(position.x + view.frame.origin.x, |
| position.y + view.frame.origin.y); |
| view = view.superview; |
| } |
| |
| return position; |
| } |
| |
| - (void)setContentSize:(CGSize)contentSize |
| { |
| // Do Nothing |
| } |
| |
| - (void)adjustSticky |
| { |
| // Do Nothing, sticky is adjusted by layout |
| } |
| |
| #pragma mark - Load More Event |
| |
| - (void)loadMore |
| { |
| [super loadMore]; |
| _previousLoadMoreCellNumber = [_collectionView numberOfItemsInSection:0]; |
| } |
| |
| - (BOOL)isNeedLoadMore |
| { |
| BOOL superNeedLoadMore = [super isNeedLoadMore]; |
| return superNeedLoadMore && _previousLoadMoreCellNumber != [_collectionView numberOfItemsInSection:0]; |
| } |
| |
| - (void)resetLoadmore |
| { |
| [super resetLoadmore]; |
| _previousLoadMoreCellNumber = 0; |
| } |
| |
| #pragma mark - Exported Component Methods |
| |
| - (void)appendData:(id)appendingData |
| { |
| if (!appendingData){ |
| return; |
| } |
| NSMutableArray * newListData = [[_dataManager data] mutableCopy]; |
| [newListData addObject:appendingData]; |
| } |
| |
| - (void)appendRange:(NSArray*)data |
| { |
| if (![data isKindOfClass:[NSArray class]]) { |
| WXLogError(@"wrong format of appending data:%@", data); |
| return; |
| } |
| |
| NSArray *oldData = [_dataManager data]; |
| [_updateManager updateWithAppendingData:data oldData:oldData completion:nil animation:NO]; |
| } |
| |
| - (void)setListData:(NSArray*)data |
| { |
| if ([data count]) { |
| [_dataManager updateData:data]; |
| } |
| } |
| - (void)insertData:(NSUInteger)index data:(id)data |
| { |
| // TODO: bring the update logic to UpdateManager |
| // TODO: update cell because index has changed |
| NSMutableArray *newListData = [[_dataManager data] mutableCopy]; |
| if (index <= newListData.count) { |
| [newListData insertObject:data atIndex:index]; |
| [_dataManager updateData:newListData]; |
| |
| NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; |
| |
| [UIView performWithoutAnimation:^{ |
| [self->_collectionView insertItemsAtIndexPaths:@[indexPath]]; |
| }]; |
| } |
| } |
| |
| - (void)updateComponentData:(NSString*)componentDataId componentData:(NSDictionary*)componentData callback:(NSString*)callbackId |
| { |
| NSMutableDictionary * virtualComponentData = [[_dataManager virtualComponentDataWithId:componentDataId] mutableCopy]; |
| NSIndexPath * indexPath = virtualComponentData[@"indexPath"]; |
| if (!indexPath) { |
| return; |
| } |
| virtualComponentData = virtualComponentData?:[NSMutableDictionary new]; |
| [virtualComponentData addEntriesFromDictionary:componentData]; |
| [_dataManager updateVirtualComponentData:componentDataId data:[virtualComponentData copy]]; |
| virtualComponentData[@"@phase"] = @"update"; |
| virtualComponentData[@"callbackId"] = callbackId; |
| [self _updateDataForCellSlotAtIndexPath:indexPath data:virtualComponentData]; |
| } |
| |
| - (void)_updateDataForCellSlotAtIndexPath:(NSIndexPath*)indexPath data:(NSDictionary*)data |
| { |
| if(!indexPath || !data) { |
| return; |
| } |
| WXPerformBlockOnMainThread(^{ |
| UICollectionViewCell * cellView = [self->_collectionView cellForItemAtIndexPath:indexPath]; |
| WXCellSlotComponent * cellSlotComponent = (WXCellSlotComponent*)cellView.wx_component; |
| if (cellSlotComponent) { |
| [self _updateBindingData:data forCell:cellSlotComponent atIndexPath:indexPath]; |
| } |
| // callback when update virtual component data success. |
| NSString * callbackId = data[@"callbackId"]; |
| if (callbackId) { |
| [[WXSDKManager bridgeMgr] callBack:self.weexInstance.instanceId funcId:callbackId params:@{@"result":@"success"}]; |
| } |
| }); |
| } |
| |
| - (void)updateData:(NSUInteger)index data:(id)data |
| { |
| NSMutableArray *newListData = [[_dataManager data] mutableCopy]; |
| if (!data && index > [newListData count]) { |
| return; |
| } |
| // TODO: bring the update logic to UpdateManager |
| newListData[index] = data; |
| [_dataManager updateData:newListData]; |
| NSMutableDictionary * newData = nil; |
| if (![data isKindOfClass:[NSDictionary class]]) { |
| newData = [NSMutableDictionary new]; |
| [newData setObject:@"data" forKey:data]; |
| data = newData; |
| } |
| newData = [data mutableCopy]; |
| newData[@"@phase"] = @"update"; |
| NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0]; |
| [self _updateDataForCellSlotAtIndexPath:indexPath data:[newData copy]]; |
| } |
| |
| - (void)insertRange:(NSInteger)index range:(NSArray*)data |
| { |
| if (![data count]) { |
| WXLogDebug(@"ignore invalid insertRange"); |
| return; |
| } |
| |
| NSMutableArray * newListData = [[_dataManager data] mutableCopy]; |
| NSRange range = NSMakeRange(index,[data count]); |
| NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:range]; |
| [newListData insertObjects:data atIndexes:indexSet]; |
| [_dataManager updateData:newListData]; |
| [_collectionView reloadData]; |
| } |
| |
| - (void)removeData:(NSInteger)index count:(NSInteger)count |
| { |
| // TODO: bring the update logic to UpdateManager |
| |
| NSMutableArray *newListData = [[_dataManager data] mutableCopy]; |
| if (index > [newListData count] || index + count - 1 > [newListData count]) { |
| |
| return; |
| } |
| NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(index, count)]; |
| [newListData removeObjectsAtIndexes:indexSet]; |
| __block NSMutableArray<NSIndexPath*>* indexPaths = [NSMutableArray new]; |
| [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { |
| NSIndexPath* indexPath = [NSIndexPath indexPathForRow:idx inSection:0]; |
| [indexPaths addObject:indexPath]; |
| }]; |
| |
| [_dataManager updateData:newListData]; |
| [_dataManager deleteVirtualComponentAtIndexPaths:indexPaths]; |
| [UIView performWithoutAnimation:^{ |
| [self->_collectionView deleteItemsAtIndexPaths:indexPaths]; |
| [self->_collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]]; |
| }]; |
| } |
| |
| - (void)moveData:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex |
| { |
| // TODO: bring the update logic to UpdateManager |
| NSMutableArray *newListData = [[_dataManager data] mutableCopy]; |
| id data = newListData[fromIndex]; |
| [newListData removeObjectAtIndex:fromIndex]; |
| [newListData insertObject:data atIndex:toIndex]; |
| [_dataManager updateData:newListData]; |
| |
| NSIndexPath *fromIndexPath = [NSIndexPath indexPathForItem:fromIndex inSection:0]; |
| NSIndexPath *toIndexPath = [NSIndexPath indexPathForItem:toIndex inSection:0]; |
| [UIView performWithoutAnimation:^{ |
| [self->_collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath]; |
| }]; |
| } |
| |
| - (void)scrollTo:(NSString *)virtualElementInfo options:(NSDictionary *)options |
| { |
| NSUInteger position = 0; |
| if ([virtualElementInfo isKindOfClass:[NSNumber class]]) { |
| position = [virtualElementInfo integerValue]; |
| } |
| else |
| { |
| if (virtualElementInfo.length == 0) { |
| return; |
| } |
| position = [self _positionForVirtualElementInfo:virtualElementInfo]; |
| } |
| NSIndexPath *toIndexPath = [NSIndexPath indexPathForItem:position inSection:0]; |
| BOOL animated = options[@"animated"] ? [WXConvert BOOL:options[@"animated"]] : YES; |
| [_collectionView scrollToItemAtIndexPath:toIndexPath atScrollPosition:UICollectionViewScrollPositionTop animated:animated]; |
| } |
| |
| - (void)scrollToElement:(NSString *)virtualElementInfo options:(NSDictionary *)options |
| { |
| [self scrollTo:virtualElementInfo options:options]; |
| } |
| |
| - (void)queryElement:(NSString *)virtualElementInfo cssSelector:(NSString *)cssSelector callback:(WXModuleCallback)callback |
| { |
| [self _queryElement:virtualElementInfo cssSelector:cssSelector callback:callback isAll:NO]; |
| } |
| |
| - (void)queryElementAll:(NSString *)virtualElementInfo cssSelector:(NSString *)cssSelector callback:(WXModuleCallback)callback |
| { |
| [self _queryElement:virtualElementInfo cssSelector:cssSelector callback:callback isAll:YES]; |
| } |
| |
| - (NSString *)_refForVirtualElementInfo:(NSString *)virtualElementInfo |
| { |
| if ([virtualElementInfo isKindOfClass:[NSString class]]){ |
| NSArray *stringArray = [virtualElementInfo componentsSeparatedByString:@"@"]; |
| if (stringArray.count == 2) { |
| return stringArray[0]; |
| } |
| } |
| return nil; |
| } |
| |
| - (NSUInteger )_positionForVirtualElementInfo:(NSString *)virtualElementInfo |
| { |
| NSArray *stringArray = [virtualElementInfo componentsSeparatedByString:@"@"]; |
| if (stringArray.count == 2) { |
| return [stringArray[1] integerValue]; |
| } |
| return 0; |
| } |
| |
| - (void)closest:(NSString *)virtualElementInfo cssSelector:(NSString *)cssSelector callback:(WXModuleCallback)callback |
| { |
| if(callback) |
| { |
| WXPerformBlockOnComponentThread(^{ |
| WXComponent *component = [self.weexInstance.componentManager componentForRef:[self _refForVirtualElementInfo:virtualElementInfo]]; |
| if (component) { |
| callback([self _closestComponentForCSSSelector:cssSelector component:component]); |
| } |
| }); |
| } |
| } |
| |
| - (NSDictionary *)_closestComponentForCSSSelector:(NSString *)cssSelector component:(WXComponent *)component |
| { |
| WXComponent *supercomponent = component.supercomponent; |
| if ([self _parseCssSelector:cssSelector component:supercomponent]) { |
| NSDictionary *info = @{@"attrs":supercomponent.attributes,@"type":supercomponent->_type,@"ref":supercomponent.ref}; |
| return info; |
| } |
| else |
| { |
| if ([supercomponent isKindOfClass:[WXRecycleListComponent class]] ) { |
| return nil; |
| } |
| return [self _closestComponentForCSSSelector:cssSelector component:supercomponent]; |
| } |
| } |
| |
| - (void)_queryElement:(NSString *)virtualElementInfo cssSelector:(NSString *)cssSelector callback:(WXModuleCallback)callback isAll:(BOOL)isAll |
| { |
| if(callback) |
| { |
| WXPerformBlockSyncOnComponentThread(^{ |
| WXComponent *component = [self.weexInstance.componentManager componentForRef:[self _refForVirtualElementInfo:virtualElementInfo]]; |
| if (component) { |
| NSMutableArray *infoArray = [NSMutableArray new]; |
| [self _matchComponentForCSSSelector:cssSelector component:component infoArray:infoArray]; |
| if (isAll) { |
| callback(infoArray); |
| } |
| else |
| { |
| if (infoArray.count != 0) { |
| callback(infoArray[0]); |
| } |
| } |
| } |
| }); |
| } |
| } |
| |
| - (void)_matchComponentForCSSSelector:(NSString *)cssSelector component:(WXComponent *)component infoArray:(NSMutableArray *)infoArray |
| { |
| for (WXComponent *subcomponent in component.subcomponents) { |
| if ([self _parseCssSelector:cssSelector component:subcomponent]) { |
| NSDictionary *info = @{@"attrs":subcomponent.attributes,@"type":subcomponent->_type,@"ref":subcomponent.ref}; |
| [infoArray addObject:info]; |
| } |
| if (subcomponent.subcomponents.count != 0) { |
| [self _matchComponentForCSSSelector:cssSelector component:subcomponent infoArray:infoArray]; |
| } |
| } |
| } |
| |
| - (BOOL)_parseCssSelector:(NSString *)cssSelector component:(WXComponent *)component |
| { |
| if (!cssSelector) { |
| return NO; |
| } |
| if ([cssSelector hasPrefix:@"["]&&[cssSelector hasSuffix:@"]"]) { |
| NSCharacterSet *unwantedChars = [NSCharacterSet characterSetWithCharactersInString:@"\"[]"]; |
| NSString *requiredString = [[cssSelector componentsSeparatedByCharactersInSet:unwantedChars] componentsJoinedByString:@""]; |
| NSArray *selectorArray = [requiredString componentsSeparatedByString:@"="]; |
| if (selectorArray.count == 2) { |
| NSString *attribute = selectorArray[0]; |
| NSString *value = selectorArray[1]; |
| NSDictionary *componentAttrs = component.attributes; |
| NSString *valueString = [NSString stringWithFormat:@"%@",componentAttrs[attribute]]; |
| if ([valueString isEqualToString:value]) { |
| return YES; |
| } |
| } |
| } |
| return NO; |
| } |
| |
| #pragma mark - WXComponent Internal Methods |
| |
| - (BOOL)_insertSubcomponent:(WXComponent *)subcomponent atIndex:(NSInteger)index |
| { |
| BOOL inserted = [super _insertSubcomponent:subcomponent atIndex:index]; |
| if ([subcomponent isKindOfClass:[WXCellSlotComponent class]]) { |
| WXCellSlotComponent *cell = (WXCellSlotComponent*)subcomponent; |
| [self.weexInstance.componentManager _addUITask:^{ |
| [_templateManager addTemplate:cell]; |
| }]; |
| //TODO: update collection view if adding template |
| } |
| return inserted; |
| } |
| |
| #pragma mark - Private |
| |
| - (void)_updateBindingData:(id)data forCell:(WXCellSlotComponent *)cellComponent atIndexPath:(NSIndexPath *)indexPath |
| { |
| id originalData = data; |
| if (![originalData isKindOfClass:[NSDictionary class]]) { |
| if (_aliasKey) { |
| NSMutableDictionary * dictionary = [NSMutableDictionary dictionary]; |
| [dictionary setObject:data forKey:_aliasKey]; |
| data = dictionary; |
| } else { |
| return; |
| } |
| } |
| |
| if (!data[@"indexPath"] || !data[@"recycleListComponentRef"]) { |
| NSMutableDictionary * dataNew = [data mutableCopy]; |
| dataNew[@"recycleListComponentRef"] = self.ref; |
| dataNew[@"indexPath"] = indexPath; |
| data = dataNew; |
| } |
| |
| if ([originalData isKindOfClass:[NSDictionary class]] && _aliasKey &&!data[@"phase"]) { |
| data = @{_aliasKey:data,@"aliasKey":_aliasKey}; |
| } |
| |
| if (_indexKey) { |
| NSMutableDictionary *dataNew = [data mutableCopy]; |
| dataNew[_indexKey] = @(indexPath.item); |
| data = dataNew; |
| } |
| |
| #ifdef DEBUG |
| NSDate *startTime = [NSDate date]; |
| #endif |
| |
| WXPerformBlockSyncOnComponentThread(^{ |
| [cellComponent updateCellData:[data copy]]; |
| }); |
| #ifdef DEBUG |
| double duration = -[startTime timeIntervalSinceNow] * 1000; |
| WXLogDebug(@"cell:%li update data time:%f", (long)indexPath.item, duration); |
| #endif |
| |
| NSValue *cachedSize = _sizeCache[indexPath]; |
| if (!cachedSize || !CGSizeEqualToSize([cachedSize CGSizeValue] , cellComponent.calculatedFrame.size)) { |
| _sizeCache[indexPath] = [NSValue valueWithCGSize:cellComponent.calculatedFrame.size]; |
| [_collectionView.collectionViewLayout invalidateLayout]; |
| } |
| NSNumber *cachedSticky = _stickyCache[indexPath]; |
| BOOL isSticky = cellComponent->_positionType == WXPositionTypeSticky; |
| if (!cachedSticky || [cachedSticky boolValue] != isSticky) { |
| _stickyCache[indexPath] = @(isSticky); |
| } |
| } |
| |
| - (void)_updateListData:(NSArray *)newData |
| withCompletion:(WXRecycleListUpdateCompletion)completion |
| animation:(BOOL)animation |
| { |
| if (![newData isKindOfClass:[NSArray class]]) { |
| WXLogError(@"wrong format of list data:%@", newData); |
| if (completion) { |
| completion(NO); |
| } |
| return; |
| } |
| |
| NSArray *oldData = [_dataManager data]; |
| [_updateManager updateWithNewData:newData oldData:oldData completion:completion animation:animation]; |
| } |
| |
| - (void)_updateScrollDirection:(WXScrollDirection)newScrollDirection |
| { |
| WXRecycleListLayout *layout = [self recycleListLayout]; |
| _collectionView.collectionViewLayout = layout; |
| } |
| |
| - (WXRecycleListLayout *)recycleListLayout |
| { |
| WXRecycleListLayout *layout = [WXRecycleListLayout new]; |
| layout.delegate = self; |
| // to show cells that original width / height is zero, otherwise cellForItemAtIndexPath will not be called |
| layout.minimumLineSpacing = 0.01; |
| layout.minimumInteritemSpacing = 0.01; |
| if (WXScrollDirectionHorizontal == self.scrollDirection) { |
| layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; |
| } |
| return layout; |
| } |
| |
| #pragma mark - UICollectionViewDataSource |
| |
| - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView |
| { |
| return 1; |
| } |
| |
| - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section |
| { |
| return [_dataManager numberOfItems]; |
| } |
| |
| - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath |
| { |
| // 1. get the data relating to the cell |
| id data = [_dataManager dataAtIndex:indexPath.row]; |
| |
| // 2. get the template type specified by data, and if template is not found, return an empty view of any template to avoid crash. |
| NSString * templateType = [self templateType:indexPath]; |
| _templateManager.collectionView = collectionView; |
| if (!templateType) { |
| WXLogError(@"Template %@ not registered for collection view.", templateType); |
| UICollectionViewCell *cellView = [_collectionView dequeueReusableCellWithReuseIdentifier:[_templateManager anyRegisteredTemplate] forIndexPath:indexPath]; |
| for (UIView *view in cellView.contentView.subviews) { |
| [view removeFromSuperview]; |
| } |
| cellView.wx_component = nil; |
| [cellView setAccessibilityIdentifier:nil]; |
| return cellView; |
| } |
| if (![_templateManager isTemplateRegistered:templateType]) { |
| templateType = @"default"; |
| } |
| |
| // 3. dequeue a cell component by template type |
| UICollectionViewCell *cellView = [_collectionView dequeueReusableCellWithReuseIdentifier:templateType forIndexPath:indexPath]; |
| WXCellSlotComponent *cellComponent = (WXCellSlotComponent *)cellView.wx_component; |
| if (!cellComponent) { |
| cellComponent = [_templateManager dequeueCellSlotWithType:templateType forIndexPath:indexPath]; |
| cellView.wx_component = cellComponent; |
| WXPerformBlockOnComponentThread(^{ |
| //TODO: How can we avoid this? |
| [super _insertSubcomponent:cellComponent atIndex:self.subcomponents.count]; |
| }); |
| } |
| |
| // 4. binding the data to the cell component |
| [self _updateBindingData:data forCell:cellComponent atIndexPath:indexPath]; |
| |
| // 5. Add cell component's view to content view. |
| UIView *contentView = cellComponent.view; |
| if (contentView.superview == cellView.contentView) { |
| return cellView; |
| } |
| |
| for (UIView *view in cellView.contentView.subviews) { |
| [view removeFromSuperview]; |
| } |
| [cellView.contentView addSubview:contentView]; |
| [cellView setAccessibilityIdentifier:contentView.accessibilityIdentifier]; |
| |
| WXLogDebug(@"Return cell view:%@, indexPath:%@", cellView, indexPath); |
| |
| [self handleAppear]; |
| |
| return cellView; |
| } |
| |
| - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath |
| { |
| return nil; |
| } |
| |
| #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 - UICollectionViewDelegateFlowLayout |
| |
| - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath |
| { |
| NSValue *size = _sizeCache[indexPath]; |
| if (size) { |
| return [size CGSizeValue]; |
| } else { |
| |
| WXCellSlotComponent *cell = [_templateManager templateWithType:[self templateType:indexPath]]; |
| CGSize size = cell.calculatedFrame.size; |
| _sizeCache[indexPath] = [NSValue valueWithCGSize:size]; |
| return CGSizeMake(_collectionView.frame.size.width, size.height); |
| } |
| } |
| |
| #pragma mark - WXRecycleListLayoutDelegate |
| |
| - (BOOL)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout isNeedStickyForIndexPath:(NSIndexPath *)indexPath |
| { |
| NSNumber *cachedSticky = _stickyCache[indexPath]; |
| if (cachedSticky) { |
| return [cachedSticky boolValue]; |
| } else { |
| return NO; |
| } |
| } |
| |
| #pragma mark - WXRecycleListUpdateDelegate |
| |
| - (void)updateManager:(WXRecycleListUpdateManager *)manager willUpdateData:(id)newData |
| { |
| [_dataManager updateData:newData]; |
| } |
| |
| - (void)updateManager:(WXRecycleListUpdateManager *)manager didUpdateData:(id)newData withSuccess:(BOOL)finished |
| { |
| |
| } |
| |
| - (NSString*)templateType:(NSIndexPath*)indexPath |
| { |
| NSDictionary *data = [_dataManager dataAtIndex:indexPath.row]; |
| // default is first template. |
| NSString *templateType = [_templateManager topTemplate].templateCaseType; |
| if (!data || ![data isKindOfClass:[NSDictionary class]]) { |
| return templateType; |
| } |
| |
| if (_templateSwitchKey && data[_templateSwitchKey]){ |
| templateType = data[_templateSwitchKey]; |
| } else if (data[WXDefaultRecycleTemplateType]){ |
| // read the default type. |
| templateType = data[WXDefaultRecycleTemplateType]; |
| } |
| return templateType; |
| } |
| |
| @end |