http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Component/Recycler/WXRecyclerComponent.mm ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/Recycler/WXRecyclerComponent.mm b/ios/sdk/WeexSDK/Sources/Component/Recycler/WXRecyclerComponent.mm new file mode 100644 index 0000000..8406cfe --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/Recycler/WXRecyclerComponent.mm @@ -0,0 +1,768 @@ + +/* + * 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" +#import "WXScrollerComponent+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)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; +} + +#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; + + [_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; +} + +- (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]; +} + +- (void)_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; + } + + [super _insertSubcomponent:subcomponent atIndex:index]; + + if (![subcomponent isKindOfClass:[WXHeaderComponent class]] + && ![subcomponent isKindOfClass:[WXCellComponent class]]) { + return; + } + + WXPerformBlockOnMainThread(^{ + [self performUpdatesWithCompletion:^(BOOL finished) { + + }]; + }); +} + +- (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 +{ +//#ifndef USE_FLEX + if(![WXComponent isUseFlex]) + { + return self.scrollerCSSNode->style.dimensions[CSS_WIDTH]; + } +//#else + else + { + return self.flexScrollerCSSNode->getStyleWidth(); + } +//#endif +} + +- (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 +{ + UIEdgeInsets padding; +//#ifndef USE_FLEX + if(![WXComponent isUseFlex]) + { + padding = { + WXFloorPixelValue(self.cssNode->style.padding[CSS_TOP] + self.cssNode->style.border[CSS_TOP]), + WXFloorPixelValue(self.cssNode->style.padding[CSS_LEFT] + self.cssNode->style.border[CSS_LEFT]), + WXFloorPixelValue(self.cssNode->style.padding[CSS_BOTTOM] + self.cssNode->style.border[CSS_BOTTOM]), + WXFloorPixelValue(self.cssNode->style.padding[CSS_RIGHT] + self.cssNode->style.border[CSS_RIGHT]) + }; + } +//#else + else + { + 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()) + }; + } +//#endif + + + 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
http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.m b/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.m deleted file mode 100644 index b8feb31..0000000 --- a/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.m +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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 "WXSDKInstance.h" -#import "WXLog.h" -#import "WXCellComponent.h" -#import "WXListComponent.h" -#import "WXComponent_internal.h" -#import "WXDiffUtil.h" - -@interface WXCellComponent () - -@end - -@implementation WXCellComponent -{ - NSIndexPath *_indexPathBeforeMove; - BOOL _isUseContainerWidth; -} - -- (instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance -{ - self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]; - - if (self) { - _async = attributes[@"async"] ? [WXConvert BOOL:attributes[@"async"]] : YES; - _isRecycle = attributes[@"recycle"] ? [WXConvert BOOL:attributes[@"recycle"]] : YES; - _insertAnimation = [WXConvert UITableViewRowAnimation:attributes[@"insertAnimation"]]; - _deleteAnimation = [WXConvert UITableViewRowAnimation:attributes[@"deleteAnimation"]]; - _keepScrollPosition = attributes[@"keepScrollPosition"] ? [WXConvert BOOL:attributes[@"keepScrollPosition"]] : NO; - _lazyCreateView = YES; - _isNeedJoinLayoutSystem = NO; - if (attributes[@"zIndex"]) { - _zIndex = [WXConvert NSString:attributes[@"zIndex"]]; - } - } - - return self; -} - -- (void)dealloc -{ - -} - -- (BOOL)weex_isEqualTo:(id<WXDiffable>)object -{ - return self == object; -} - -- (void)_frameDidCalculated:(BOOL)isChanged -{ - [super _frameDidCalculated:isChanged]; - - if (isChanged) { - [self.delegate cellDidLayout:self]; - } -} - -- (void)didFinishDrawingLayer:(BOOL)success -{ - [self.delegate cellDidRendered:self]; -} - -- (void)updateAttributes:(NSDictionary *)attributes -{ - if (attributes[@"async"]) { - _async = [WXConvert BOOL:attributes[@"async"]]; - } - - if (attributes[@"recycle"]) { - _isRecycle = [WXConvert BOOL:attributes[@"recycle"]]; - } - - if (attributes[@"insertAnimation"]) { - _insertAnimation = [WXConvert UITableViewRowAnimation:attributes[@"insertAnimation"]]; - } - - if (attributes[@"deleteAnimation"]) { - _deleteAnimation = [WXConvert UITableViewRowAnimation:attributes[@"deleteAnimation"]]; - } - - if (attributes[@"keepScrollPosition"]) { - _keepScrollPosition = [WXConvert BOOL:attributes[@"keepScrollPosition"]]; - } -} - -- (void)_moveToSupercomponent:(WXComponent *)newSupercomponent atIndex:(NSUInteger)index -{ - if (self.delegate == (id<WXCellRenderDelegate>)newSupercomponent) { - [self.delegate cell:self didMoveToIndex:index]; - [super _removeFromSupercomponent]; - [newSupercomponent _insertSubcomponent:self atIndex:index]; - } else { - [super _moveToSupercomponent:newSupercomponent atIndex:index]; - } -} - -- (void)_removeFromSupercomponent -{ - [super _removeFromSupercomponent]; - - [self.delegate cellDidRemove:self]; -} - -- (void)removeFromSuperview -{ - // do nothing -} - -- (void)_calculateFrameWithSuperAbsolutePosition:(CGPoint)superAbsolutePosition gatherDirtyComponents:(NSMutableSet<WXComponent *> *)dirtyComponents -{ - if (self.delegate && (isUndefined(self.cssNode->style.dimensions[CSS_WIDTH]) || _isUseContainerWidth)) { - self.cssNode->style.dimensions[CSS_WIDTH] = [self.delegate containerWidthForLayout:self]; - //TODO: set _isUseContainerWidth to NO if updateStyles have width - _isUseContainerWidth = YES; - } - - if ([self needsLayout]) { - layoutNode(self.cssNode, CSS_UNDEFINED, CSS_UNDEFINED, CSS_DIRECTION_INHERIT); - if ([WXLog logLevel] >= WXLogLevelDebug) { - print_css_node(self.cssNode, CSS_PRINT_LAYOUT | CSS_PRINT_STYLE | CSS_PRINT_CHILDREN); - } - } - - [super _calculateFrameWithSuperAbsolutePosition:superAbsolutePosition gatherDirtyComponents:dirtyComponents]; -} -@end - http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.mm ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.mm b/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.mm new file mode 100644 index 0000000..62a542c --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.mm @@ -0,0 +1,174 @@ +/* + * 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 "WXSDKInstance.h" +#import "WXLog.h" +#import "WXCellComponent.h" +#import "WXListComponent.h" +#import "WXComponent_internal.h" +#import "WXDiffUtil.h" +#import "WXComponent+Layout.h" + +@interface WXCellComponent () + +@end + +@implementation WXCellComponent +{ + NSIndexPath *_indexPathBeforeMove; + BOOL _isUseContainerWidth; +} + +- (instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance +{ +#ifdef DEBUG + WXLogDebug(@"flexLayout -> init Cell: ref:%@, styles:%@",ref,styles); +#endif + + self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]; + + if (self) { + _async = attributes[@"async"] ? [WXConvert BOOL:attributes[@"async"]] : YES; + _isRecycle = attributes[@"recycle"] ? [WXConvert BOOL:attributes[@"recycle"]] : YES; + _insertAnimation = [WXConvert UITableViewRowAnimation:attributes[@"insertAnimation"]]; + _deleteAnimation = [WXConvert UITableViewRowAnimation:attributes[@"deleteAnimation"]]; + _keepScrollPosition = attributes[@"keepScrollPosition"] ? [WXConvert BOOL:attributes[@"keepScrollPosition"]] : NO; + _lazyCreateView = YES; + _isNeedJoinLayoutSystem = NO; + if (attributes[@"zIndex"]) { + _zIndex = [WXConvert NSString:attributes[@"zIndex"]]; + } + } + + return self; +} + +- (void)dealloc +{ + +} + +- (BOOL)weex_isEqualTo:(id<WXDiffable>)object +{ + return self == object; +} + +- (void)_frameDidCalculated:(BOOL)isChanged +{ + [super _frameDidCalculated:isChanged]; + + if (isChanged) { + [self.delegate cellDidLayout:self]; + } +} + +- (void)didFinishDrawingLayer:(BOOL)success +{ + [self.delegate cellDidRendered:self]; +} + +- (void)updateAttributes:(NSDictionary *)attributes +{ + if (attributes[@"async"]) { + _async = [WXConvert BOOL:attributes[@"async"]]; + } + + if (attributes[@"recycle"]) { + _isRecycle = [WXConvert BOOL:attributes[@"recycle"]]; + } + + if (attributes[@"insertAnimation"]) { + _insertAnimation = [WXConvert UITableViewRowAnimation:attributes[@"insertAnimation"]]; + } + + if (attributes[@"deleteAnimation"]) { + _deleteAnimation = [WXConvert UITableViewRowAnimation:attributes[@"deleteAnimation"]]; + } + + if (attributes[@"keepScrollPosition"]) { + _keepScrollPosition = [WXConvert BOOL:attributes[@"keepScrollPosition"]]; + } +} + +- (void)_moveToSupercomponent:(WXComponent *)newSupercomponent atIndex:(NSUInteger)index +{ + if (self.delegate == (id<WXCellRenderDelegate>)newSupercomponent) { + [self.delegate cell:self didMoveToIndex:index]; + [super _removeFromSupercomponent]; + [newSupercomponent _insertSubcomponent:self atIndex:index]; + } else { + [super _moveToSupercomponent:newSupercomponent atIndex:index]; + } +} + +- (void)_removeFromSupercomponent +{ + [super _removeFromSupercomponent]; + + [self.delegate cellDidRemove:self]; +} + +- (void)removeFromSuperview +{ + // do nothing +} + +- (void)_calculateFrameWithSuperAbsolutePosition:(CGPoint)superAbsolutePosition gatherDirtyComponents:(NSMutableSet<WXComponent *> *)dirtyComponents +{ + +//#ifndef USE_FLEX + if (![WXComponent isUseFlex]) { + if (self.delegate && (isUndefined(self.cssNode->style.dimensions[CSS_WIDTH]) || _isUseContainerWidth)) { + self.cssNode->style.dimensions[CSS_WIDTH] = [self.delegate containerWidthForLayout:self]; + //TODO: set _isUseContainerWidth to NO if updateStyles have width + _isUseContainerWidth = YES; + } + + if ([self needsLayout]) { + layoutNode(self.cssNode, CSS_UNDEFINED, CSS_UNDEFINED, CSS_DIRECTION_INHERIT); + if ([WXLog logLevel] >= WXLogLevelDebug) { + print_css_node(self.cssNode, (css_print_options_t)(CSS_PRINT_LAYOUT | CSS_PRINT_STYLE | CSS_PRINT_CHILDREN)); + } + } + } + +//#else + else + { + if (self.delegate && (flexIsUndefined(self.flexCssNode->getStyleWidth()) || _isUseContainerWidth)) { + self.flexCssNode->setStyleWidth([self.delegate containerWidthForLayout:self],NO); + _isUseContainerWidth = YES; + } + + if ([self needsLayout]) { + std::pair<float, float> renderPageSize; + renderPageSize.first = self.weexInstance.frame.size.width; + renderPageSize.second = self.weexInstance.frame.size.height; + self.flexCssNode->calculateLayout(renderPageSize); + if ([WXLog logLevel] >= WXLogLevelDebug) { + + } + } + } + +//#endif + [super _calculateFrameWithSuperAbsolutePosition:superAbsolutePosition gatherDirtyComponents:dirtyComponents]; +} +@end + http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h b/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h index bf54a91..5f63065 100644 --- a/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h +++ b/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h @@ -36,15 +36,6 @@ typedef id (^WXDataBindingBlock)(NSDictionary *data, BOOL *needUpdate); @package NSString *_type; NSMutableArray *_subcomponents; - /** - * Layout - */ - css_node_t *_cssNode; - BOOL _isLayoutDirty; - CGRect _calculatedFrame; - CGPoint _absolutePosition; - WXPositionType _positionType; - //Transition WXTransition *_transition; @@ -212,6 +203,8 @@ typedef id (^WXDataBindingBlock)(NSDictionary *data, BOOL *needUpdate); - (void)_initCSSNodeWithStyles:(NSDictionary *)styles; +- (void)_initFlexCssNodeWithStyles:(NSDictionary *)styles; + - (void)_updateCSSNodeStyles:(NSDictionary *)styles; - (void)_resetCSSNodeStyles:(NSArray *)styles; http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Component/WXCycleSliderComponent.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXCycleSliderComponent.m b/ios/sdk/WeexSDK/Sources/Component/WXCycleSliderComponent.m deleted file mode 100644 index 929dd1a..0000000 --- a/ios/sdk/WeexSDK/Sources/Component/WXCycleSliderComponent.m +++ /dev/null @@ -1,681 +0,0 @@ -/* - * 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 "WXCycleSliderComponent.h" -#import "WXIndicatorComponent.h" -#import "WXComponent_internal.h" -#import "NSTimer+Weex.h" -#import "WXSDKManager.h" -#import "WXUtility.h" - -typedef NS_ENUM(NSInteger, Direction) { - DirectionNone = 1 << 0, - DirectionLeft = 1 << 1, - DirectionRight = 1 << 2 -}; - -@class WXRecycleSliderView; -@class WXIndicatorView; - -@protocol WXRecycleSliderViewDelegate <UIScrollViewDelegate> - -- (void)recycleSliderView:(WXRecycleSliderView *)recycleSliderView didScroll:(UIScrollView *)scrollView; -- (void)recycleSliderView:(WXRecycleSliderView *)recycleSliderView didScrollToItemAtIndex:(NSInteger)index; -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView; -- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate; - -@end - -@interface WXRecycleSliderView : UIView <UIScrollViewDelegate> - -@property (nonatomic, strong) WXIndicatorView *indicator; -@property (nonatomic, weak) id<WXRecycleSliderViewDelegate> delegate; - -@property (nonatomic, strong) UIScrollView *scrollView; -@property (nonatomic, strong) NSMutableArray *itemViews; -@property (nonatomic, assign) Direction direction; -@property (nonatomic, assign) NSInteger currentIndex; -@property (nonatomic, assign) NSInteger nextIndex; -@property (nonatomic, assign) CGRect currentItemFrame; -@property (nonatomic, assign) CGRect nextItemFrame; -@property (nonatomic, assign) BOOL infinite; - -- (void)insertItemView:(UIView *)view atIndex:(NSInteger)index; -- (void)removeItemView:(UIView *)view; - -@end - -@implementation WXRecycleSliderView - -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - _currentIndex = 0; - _itemViews = [[NSMutableArray alloc] init]; - _scrollView = [[UIScrollView alloc] init]; - _scrollView.backgroundColor = [UIColor clearColor]; - _scrollView.delegate = self; - _scrollView.showsHorizontalScrollIndicator = NO; - _scrollView.showsVerticalScrollIndicator = NO; - _scrollView.scrollsToTop = NO; - [self addSubview:_scrollView]; - } - return self; -} - -- (void)dealloc -{ - if (_scrollView) { - _scrollView.delegate = nil; - } -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - [self resetAllViewsFrame]; -} - -- (void)accessibilityDecrement -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - [self.wx_component performSelector:NSSelectorFromString(@"resumeAutoPlay:") withObject:@(false)]; -#pragma clang diagnostic pop - - [self nextPage]; -} - -- (void)accessibilityIncrement -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - [self.wx_component performSelector:NSSelectorFromString(@"resumeAutoPlay:") withObject:@(false)]; -#pragma clang diagnostic pop - - [self lastPage]; -} - -- (void)accessibilityElementDidLoseFocus -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - [self.wx_component performSelector:NSSelectorFromString(@"resumeAutoPlay:") withObject:@(true)]; -#pragma clang diagnostic pop -} - -#pragma mark Private Methods -- (CGFloat)height { - return self.frame.size.height; -} - -- (CGFloat)width { - return self.frame.size.width; -} - -- (UIView *)getItemAtIndex:(NSInteger)index -{ - if (self.itemViews.count > index) { - return [self.itemViews objectAtIndex:index]; - }else{ - return nil; - } -} - -- (void)setCurrentIndex:(NSInteger)currentIndex -{ - if (currentIndex >= _itemViews.count || currentIndex < 0) { - currentIndex = 0; - } - NSInteger oldIndex = _currentIndex; - _currentIndex = currentIndex; - if (_infinite) { - if (_direction == DirectionRight) { - self.nextItemFrame = CGRectMake(0, 0, self.width, self.height); - self.nextIndex = self.currentIndex - 1; - if (self.nextIndex < 0) - { - self.nextIndex = _itemViews.count - 1; - } - }else if (_direction == DirectionLeft) { - self.nextItemFrame = CGRectMake(self.width * 2, 0, self.width, self.height); - self.nextIndex = _itemViews.count?(self.currentIndex + 1) % _itemViews.count:0; - }else { - self.nextIndex = _itemViews.count?(self.currentIndex + 1) % _itemViews.count:0; - } - [self resetAllViewsFrame]; - } else { - [_scrollView setContentOffset:CGPointMake(_currentIndex * self.width, 0) animated:YES]; - } - [self resetIndicatorPoint]; - if (self.delegate && [self.delegate respondsToSelector:@selector(recycleSliderView:didScrollToItemAtIndex:)]) { - if (oldIndex != _currentIndex) { - [self.delegate recycleSliderView:self didScrollToItemAtIndex:_currentIndex]; - } - } -} - -- (void)resetIndicatorPoint -{ - [self.indicator setPointCount:self.itemViews.count]; - [self.indicator setCurrentPoint:_currentIndex]; -} - -#pragma mark Scroll & Frames -- (void)setDirection:(Direction)direction { - if (_direction == direction) return; - _direction = direction; - if (_direction == DirectionNone) return; - if (_direction == DirectionRight) { - self.nextItemFrame = CGRectMake(0, 0, self.width, self.height); - self.nextIndex = self.currentIndex - 1; - if (self.nextIndex < 0) - { - self.nextIndex = _itemViews.count - 1; - } - UIView *view = [self getItemAtIndex:_nextIndex]; - if (view) { - view.frame = _nextItemFrame; - } - }else if (_direction == DirectionLeft){ - self.nextItemFrame = CGRectMake(self.width * 2, 0, self.width, self.height); - self.nextIndex = _itemViews.count?(self.currentIndex + 1) % _itemViews.count:0; - UIView *view = [self getItemAtIndex:_nextIndex]; - if (view) { - view.frame = _nextItemFrame; - } - } -} - -- (void)resetAllViewsFrame -{ - if (_infinite && _itemViews.count > 1) { - self.scrollView.frame = CGRectMake(0, 0, self.width, self.height); - self.scrollView.contentOffset = CGPointMake(self.width, 0); - if (self.itemViews.count > 1) { - self.scrollView.contentSize = CGSizeMake(self.width * 3, 0); - } else { - self.scrollView.contentSize = CGSizeZero; - } - _currentItemFrame = CGRectMake(self.width, 0, self.width, self.height); - for (int i = 0; i < self.itemViews.count; i++) { - UIView *view = [self.itemViews objectAtIndex:i]; - if (i != self.currentIndex) { - view.frame = CGRectMake(self.frame.size.width * 3, 0, self.width, self.height);; - } - } - [self getItemAtIndex:_currentIndex].frame = _currentItemFrame; - if (_itemViews.count == 2) { - _nextItemFrame = CGRectMake(self.width * 2, 0, self.width, self.height); - [self getItemAtIndex:_nextIndex].frame = _nextItemFrame; - } - } else { - self.scrollView.frame = self.bounds; - self.scrollView.contentSize = CGSizeMake(self.width * _itemViews.count, self.height); - self.scrollView.contentOffset = CGPointMake(_currentIndex * self.width, 0); - for (int i = 0; i < _itemViews.count; i ++) { - UIView *view = [_itemViews objectAtIndex:i]; - view.frame = CGRectMake(i * self.width, 0, self.width, self.height); - } - [self.scrollView setContentOffset:CGPointMake(_currentIndex * self.width, 0) animated:NO]; - } - [self resetIndicatorPoint]; -} - -- (void)nextPage { - if (_itemViews.count > 1) { - if (_infinite) { - [self.scrollView setContentOffset:CGPointMake(self.width * 2, 0) animated:YES]; - } else { - // the currentindex will be set at the end of animation - NSInteger nextIndex = self.currentIndex + 1; - if(nextIndex < _itemViews.count) { - [self.scrollView setContentOffset:CGPointMake(nextIndex * self.width, 0) animated:YES]; - } - } - } -} - -- (void)lastPage -{ - - NSInteger lastIndex = [self currentIndex]-1; - if (_itemViews.count > 1) { - if (_infinite) { - if (lastIndex < 0) { - lastIndex = [_itemViews count]-1; - } - } - [self setCurrentIndex:lastIndex]; - } -} - -- (void)resetScrollView -{ - if (WXFloatEqual(self.scrollView.contentOffset.x / self.width , 1.0)) - { - return; - } - [self setCurrentIndex:self.nextIndex]; - self.scrollView.contentOffset = CGPointMake(self.width, 0); -} - -#pragma mark Public Methods - -- (void)setIndicator:(WXIndicatorView *)indicator -{ - _indicator = indicator; - [_indicator setPointCount:self.itemViews.count]; - [_indicator setCurrentPoint:_currentIndex]; -} - -- (void)insertItemView:(UIView *)view atIndex:(NSInteger)index -{ - if (![self.itemViews containsObject:view]) { - view.tag = self.itemViews.count; - if (index < 0) { - [self.itemViews addObject:view]; - } else { - [self.itemViews insertObject:view atIndex:index]; - } - } - - if (![self.scrollView.subviews containsObject:view]) { - if (index < 0) { - [self.scrollView addSubview:view]; - } else { - [self.scrollView insertSubview:view atIndex:index]; - } - } - [self layoutSubviews]; - [self setCurrentIndex:_currentIndex]; -} - -- (void)removeItemView:(UIView *)view -{ - if ([self.itemViews containsObject:view]) { - [self.itemViews removeObject:view]; - } - - if ([self.scrollView.subviews containsObject:view]) { - [view removeFromSuperview]; - } - [self layoutSubviews]; - [self setCurrentIndex:_currentIndex]; -} - -#pragma mark ScrollView Delegate - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - if (_infinite) { - CGFloat offX = scrollView.contentOffset.x; - self.direction = offX > self.width ? DirectionLeft : offX < self.width ? DirectionRight : DirectionNone; - } - if (self.delegate && [self.delegate respondsToSelector:@selector(recycleSliderView:didScroll:)]) { - [self.delegate recycleSliderView:self didScroll:self.scrollView]; - } -} - -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView -{ - if (self.delegate && [self.delegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]) { - [self.delegate scrollViewWillBeginDragging:self.scrollView]; - } -} - -- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate -{ - if (self.delegate && [self.delegate respondsToSelector:@selector(scrollViewDidEndDragging: willDecelerate:)]) { - [self.delegate scrollViewDidEndDragging:self.scrollView willDecelerate:decelerate]; - } -} - -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { - if (_infinite) { - [self resetScrollView]; - } else { - NSInteger index = _scrollView.contentOffset.x / self.width; - [self setCurrentIndex:index]; - } -} - -- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { - if (_infinite) { - [self resetScrollView]; - } else { - NSInteger index = _scrollView.contentOffset.x / self.width; - [self setCurrentIndex:index]; - } -} - -@end - -@interface WXCycleSliderComponent () <WXRecycleSliderViewDelegate,WXIndicatorComponentDelegate> - -@property (nonatomic, strong) WXRecycleSliderView *recycleSliderView; -@property (nonatomic, strong) NSTimer *autoTimer; -@property (nonatomic, assign) NSInteger currentIndex; -@property (nonatomic, assign) BOOL autoPlay; -@property (nonatomic, assign) NSUInteger interval; -@property (nonatomic, assign) NSInteger index; -@property (nonatomic, assign) CGFloat lastOffsetXRatio; -@property (nonatomic, assign) CGFloat offsetXAccuracy; -@property (nonatomic, assign) BOOL sliderChangeEvent; -@property (nonatomic, assign) BOOL sliderScrollEvent; -@property (nonatomic, assign) BOOL sliderScrollStartEvent; -@property (nonatomic, assign) BOOL sliderScrollEndEvent; -@property (nonatomic, assign) BOOL sliderStartEventFired; -@property (nonatomic, strong) NSMutableArray *childrenView; -@property (nonatomic, assign) BOOL scrollable; -@property (nonatomic, assign) BOOL infinite; - -@end - -@implementation WXCycleSliderComponent - -- (void) dealloc -{ - [self _stopAutoPlayTimer]; -} - -- (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]) { - _sliderChangeEvent = NO; - _sliderScrollEvent = NO; - _interval = 3000; - _childrenView = [NSMutableArray new]; - _lastOffsetXRatio = 0; - - if (attributes[@"autoPlay"]) { - _autoPlay = [WXConvert BOOL:attributes[@"autoPlay"]]; - } - - if (attributes[@"interval"]) { - _interval = [WXConvert NSInteger:attributes[@"interval"]]; - } - - if (attributes[@"index"]) { - _index = [WXConvert NSInteger:attributes[@"index"]]; - } - _scrollable = attributes[@"scrollable"] ? [WXConvert BOOL:attributes[@"scrollable"]] : YES; - if (attributes[@"offsetXAccuracy"]) { - _offsetXAccuracy = [WXConvert CGFloat:attributes[@"offsetXAccuracy"]]; - } - _infinite = attributes[@"infinite"] ? [WXConvert BOOL:attributes[@"infinite"]] : YES; - self.cssNode->style.flex_direction = CSS_FLEX_DIRECTION_ROW; - } - return self; -} - -- (UIView *)loadView -{ - return [[WXRecycleSliderView alloc] init]; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - - _recycleSliderView = (WXRecycleSliderView *)self.view; - _recycleSliderView.delegate = self; - _recycleSliderView.scrollView.pagingEnabled = YES; - _recycleSliderView.exclusiveTouch = YES; - _recycleSliderView.scrollView.scrollEnabled = _scrollable; - _recycleSliderView.infinite = _infinite; - UIAccessibilityTraits traits = UIAccessibilityTraitAdjustable; - if (_autoPlay) { - traits |= UIAccessibilityTraitUpdatesFrequently; - [self _startAutoPlayTimer]; - } else { - [self _stopAutoPlayTimer]; - } - _recycleSliderView.accessibilityTraits = traits; -} - -- (void)layoutDidFinish -{ - _recycleSliderView.currentIndex = _index; -} - -- (void)viewDidUnload -{ - [_childrenView removeAllObjects]; -} - -- (void)insertSubview:(WXComponent *)subcomponent atIndex:(NSInteger)index -{ - if (subcomponent->_positionType == WXPositionTypeFixed) { - [self.weexInstance.rootView addSubview:subcomponent.view]; - return; - } - - // use _lazyCreateView to forbid component like cell's view creating - if(_lazyCreateView) { - subcomponent->_lazyCreateView = YES; - } - - if (!subcomponent->_lazyCreateView || (self->_lazyCreateView && [self isViewLoaded])) { - UIView *view = subcomponent.view; - - if(index < 0) { - [self.childrenView addObject:view]; - } - else { - [self.childrenView insertObject:view atIndex:index]; - } - - WXRecycleSliderView *recycleSliderView = (WXRecycleSliderView *)self.view; - if ([view isKindOfClass:[WXIndicatorView class]]) { - ((WXIndicatorComponent *)subcomponent).delegate = self; - [recycleSliderView addSubview:view]; - [self setIndicatorView:(WXIndicatorView *)view]; - return; - } - - subcomponent.isViewFrameSyncWithCalculated = NO; - - if (index == -1) { - [recycleSliderView insertItemView:view atIndex:index]; - } else { - NSInteger offset = 0; - for (int i = 0; i < [self.childrenView count]; ++i) { - if (index == i) break; - - if ([self.childrenView[i] isKindOfClass:[WXIndicatorView class]]) { - offset++; - } - } - [recycleSliderView insertItemView:view atIndex:index - offset]; - - // check if should apply current contentOffset - // in case inserting subviews after layoutDidFinish - if (index-offset == _index && _index>0) { - recycleSliderView.currentIndex = _index; - } - } - [recycleSliderView layoutSubviews]; - } -} - -- (void)willRemoveSubview:(WXComponent *)component -{ - UIView *view = component.view; - - if(self.childrenView && [self.childrenView containsObject:view]) { - [self.childrenView removeObject:view]; - } - - WXRecycleSliderView *recycleSliderView = (WXRecycleSliderView *)_view; - [recycleSliderView removeItemView:view]; - [recycleSliderView setCurrentIndex:0]; -} - -- (void)updateAttributes:(NSDictionary *)attributes -{ - if (attributes[@"autoPlay"]) { - _autoPlay = [WXConvert BOOL:attributes[@"autoPlay"]]; - if (_autoPlay) { - [self _startAutoPlayTimer]; - } else { - [self _stopAutoPlayTimer]; - } - } - - if (attributes[@"interval"]) { - _interval = [WXConvert NSInteger:attributes[@"interval"]]; - [self _stopAutoPlayTimer]; - - if (_autoPlay) { - [self _startAutoPlayTimer]; - } - } - - if (attributes[@"index"]) { - _index = [WXConvert NSInteger:attributes[@"index"]]; - self.currentIndex = _index; - self.recycleSliderView.currentIndex = _index; - } - - if (attributes[@"scrollable"]) { - _scrollable = attributes[@"scrollable"] ? [WXConvert BOOL:attributes[@"scrollable"]] : YES; - ((WXRecycleSliderView *)self.view).scrollView.scrollEnabled = _scrollable; - } - - if (attributes[@"offsetXAccuracy"]) { - _offsetXAccuracy = [WXConvert CGFloat:attributes[@"offsetXAccuracy"]]; - } - if (attributes[@"infinite"]) { - _infinite = [WXConvert BOOL:attributes[@"infinite"]]; - } -} - -- (void)addEvent:(NSString *)eventName -{ - if ([eventName isEqualToString:@"change"]) { - _sliderChangeEvent = YES; - } - if ([eventName isEqualToString:@"scroll"]) { - _sliderScrollEvent = YES; - } -} - -- (void)removeEvent:(NSString *)eventName -{ - if ([eventName isEqualToString:@"change"]) { - _sliderChangeEvent = NO; - } - if ([eventName isEqualToString:@"scroll"]) { - _sliderScrollEvent = NO; - } -} - -#pragma mark WXIndicatorComponentDelegate Methods - --(void)setIndicatorView:(WXIndicatorView *)indicatorView -{ - NSAssert(_recycleSliderView, @""); - [_recycleSliderView setIndicator:indicatorView]; -} - -- (void)resumeAutoPlay:(id)resume -{ - if (_autoPlay) { - if ([resume boolValue]) { - [self _startAutoPlayTimer]; - } else { - [self _stopAutoPlayTimer]; - } - } -} - -#pragma mark Private Methods - -- (void)_startAutoPlayTimer -{ - if (!self.autoTimer || ![self.autoTimer isValid]) { - __weak __typeof__(self) weakSelf = self; - self.autoTimer = [NSTimer wx_scheduledTimerWithTimeInterval:_interval/1000.0f block:^() { - [weakSelf _autoPlayOnTimer]; - } repeats:YES]; - [[NSRunLoop currentRunLoop] addTimer:self.autoTimer forMode:NSRunLoopCommonModes]; - } -} - -- (void)_stopAutoPlayTimer -{ - if (self.autoTimer && [self.autoTimer isValid]) { - [self.autoTimer invalidate]; - self.autoTimer = nil; - } -} - -- (void)_autoPlayOnTimer -{ - if (!_infinite && (_currentIndex == _recycleSliderView.itemViews.count - 1)) { - [self _stopAutoPlayTimer]; - }else { - [self.recycleSliderView nextPage]; - } -} - -#pragma mark ScrollView Delegate - -- (void)recycleSliderView:(WXRecycleSliderView *)recycleSliderView didScroll:(UIScrollView *)scrollView -{ - if (_sliderScrollEvent) { - CGFloat width = scrollView.frame.size.width; - CGFloat XDeviation = 0; - if (_infinite) { - XDeviation = - (scrollView.contentOffset.x - width); - } else { - XDeviation = - (scrollView.contentOffset.x - width * _currentIndex); - } - CGFloat offsetXRatio = (XDeviation / width); - if (fabs(offsetXRatio - _lastOffsetXRatio) >= _offsetXAccuracy) { - _lastOffsetXRatio = offsetXRatio; - [self fireEvent:@"scroll" params:@{@"offsetXRatio":[NSNumber numberWithFloat:offsetXRatio]} domChanges:nil]; - } - } -} - -- (void)recycleSliderView:(WXRecycleSliderView *)recycleSliderView didScrollToItemAtIndex:(NSInteger)index -{ - - if (_sliderChangeEvent) { - [self fireEvent:@"change" params:@{@"index":@(index)} domChanges:@{@"attrs": @{@"index": @(index)}}]; - } - self.currentIndex = index; -} - -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView -{ - [self _stopAutoPlayTimer]; -} - -- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate -{ - if (_autoPlay) { - [self _startAutoPlayTimer]; - } -} - -@end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Component/WXCycleSliderComponent.mm ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXCycleSliderComponent.mm b/ios/sdk/WeexSDK/Sources/Component/WXCycleSliderComponent.mm new file mode 100644 index 0000000..db77b46 --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/WXCycleSliderComponent.mm @@ -0,0 +1,692 @@ +/* + * 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 "WXCycleSliderComponent.h" +#import "WXIndicatorComponent.h" +#import "WXComponent_internal.h" +#import "NSTimer+Weex.h" +#import "WXSDKManager.h" +#import "WXUtility.h" +#import "WXComponent+Layout.h" + +typedef NS_ENUM(NSInteger, Direction) { + DirectionNone = 1 << 0, + DirectionLeft = 1 << 1, + DirectionRight = 1 << 2 +}; + +@class WXRecycleSliderView; +@class WXIndicatorView; + +@protocol WXRecycleSliderViewDelegate <UIScrollViewDelegate> + +- (void)recycleSliderView:(WXRecycleSliderView *)recycleSliderView didScroll:(UIScrollView *)scrollView; +- (void)recycleSliderView:(WXRecycleSliderView *)recycleSliderView didScrollToItemAtIndex:(NSInteger)index; +- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView; +- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate; + +@end + +@interface WXRecycleSliderView : UIView <UIScrollViewDelegate> + +@property (nonatomic, strong) WXIndicatorView *indicator; +@property (nonatomic, weak) id<WXRecycleSliderViewDelegate> delegate; + +@property (nonatomic, strong) UIScrollView *scrollView; +@property (nonatomic, strong) NSMutableArray *itemViews; +@property (nonatomic, assign) Direction direction; +@property (nonatomic, assign) NSInteger currentIndex; +@property (nonatomic, assign) NSInteger nextIndex; +@property (nonatomic, assign) CGRect currentItemFrame; +@property (nonatomic, assign) CGRect nextItemFrame; +@property (nonatomic, assign) BOOL infinite; + +- (void)insertItemView:(UIView *)view atIndex:(NSInteger)index; +- (void)removeItemView:(UIView *)view; + +@end + +@implementation WXRecycleSliderView + +- (id)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + _currentIndex = 0; + _itemViews = [[NSMutableArray alloc] init]; + _scrollView = [[UIScrollView alloc] init]; + _scrollView.backgroundColor = [UIColor clearColor]; + _scrollView.delegate = self; + _scrollView.showsHorizontalScrollIndicator = NO; + _scrollView.showsVerticalScrollIndicator = NO; + _scrollView.scrollsToTop = NO; + [self addSubview:_scrollView]; + } + return self; +} + +- (void)dealloc +{ + if (_scrollView) { + _scrollView.delegate = nil; + } +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + [self resetAllViewsFrame]; +} + +- (void)accessibilityDecrement +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [self.wx_component performSelector:NSSelectorFromString(@"resumeAutoPlay:") withObject:@(false)]; +#pragma clang diagnostic pop + + [self nextPage]; +} + +- (void)accessibilityIncrement +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [self.wx_component performSelector:NSSelectorFromString(@"resumeAutoPlay:") withObject:@(false)]; +#pragma clang diagnostic pop + + [self lastPage]; +} + +- (void)accessibilityElementDidLoseFocus +{ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [self.wx_component performSelector:NSSelectorFromString(@"resumeAutoPlay:") withObject:@(true)]; +#pragma clang diagnostic pop +} + +#pragma mark Private Methods +- (CGFloat)height { + return self.frame.size.height; +} + +- (CGFloat)width { + return self.frame.size.width; +} + +- (UIView *)getItemAtIndex:(NSInteger)index +{ + if (self.itemViews.count > index) { + return [self.itemViews objectAtIndex:index]; + }else{ + return nil; + } +} + +- (void)setCurrentIndex:(NSInteger)currentIndex +{ + if (currentIndex >= _itemViews.count || currentIndex < 0) { + currentIndex = 0; + } + NSInteger oldIndex = _currentIndex; + _currentIndex = currentIndex; + if (_infinite) { + if (_direction == DirectionRight) { + self.nextItemFrame = CGRectMake(0, 0, self.width, self.height); + self.nextIndex = self.currentIndex - 1; + if (self.nextIndex < 0) + { + self.nextIndex = _itemViews.count - 1; + } + }else if (_direction == DirectionLeft) { + self.nextItemFrame = CGRectMake(self.width * 2, 0, self.width, self.height); + self.nextIndex = _itemViews.count?(self.currentIndex + 1) % _itemViews.count:0; + }else { + self.nextIndex = _itemViews.count?(self.currentIndex + 1) % _itemViews.count:0; + } + [self resetAllViewsFrame]; + } else { + [_scrollView setContentOffset:CGPointMake(_currentIndex * self.width, 0) animated:YES]; + } + [self resetIndicatorPoint]; + if (self.delegate && [self.delegate respondsToSelector:@selector(recycleSliderView:didScrollToItemAtIndex:)]) { + if (oldIndex != _currentIndex) { + [self.delegate recycleSliderView:self didScrollToItemAtIndex:_currentIndex]; + } + } +} + +- (void)resetIndicatorPoint +{ + [self.indicator setPointCount:self.itemViews.count]; + [self.indicator setCurrentPoint:_currentIndex]; +} + +#pragma mark Scroll & Frames +- (void)setDirection:(Direction)direction { + if (_direction == direction) return; + _direction = direction; + if (_direction == DirectionNone) return; + if (_direction == DirectionRight) { + self.nextItemFrame = CGRectMake(0, 0, self.width, self.height); + self.nextIndex = self.currentIndex - 1; + if (self.nextIndex < 0) + { + self.nextIndex = _itemViews.count - 1; + } + UIView *view = [self getItemAtIndex:_nextIndex]; + if (view) { + view.frame = _nextItemFrame; + } + }else if (_direction == DirectionLeft){ + self.nextItemFrame = CGRectMake(self.width * 2, 0, self.width, self.height); + self.nextIndex = _itemViews.count?(self.currentIndex + 1) % _itemViews.count:0; + UIView *view = [self getItemAtIndex:_nextIndex]; + if (view) { + view.frame = _nextItemFrame; + } + } +} + +- (void)resetAllViewsFrame +{ + if (_infinite && _itemViews.count > 1) { + self.scrollView.frame = CGRectMake(0, 0, self.width, self.height); + self.scrollView.contentOffset = CGPointMake(self.width, 0); + if (self.itemViews.count > 1) { + self.scrollView.contentSize = CGSizeMake(self.width * 3, 0); + } else { + self.scrollView.contentSize = CGSizeZero; + } + _currentItemFrame = CGRectMake(self.width, 0, self.width, self.height); + for (int i = 0; i < self.itemViews.count; i++) { + UIView *view = [self.itemViews objectAtIndex:i]; + if (i != self.currentIndex) { + view.frame = CGRectMake(self.frame.size.width * 3, 0, self.width, self.height);; + } + } + [self getItemAtIndex:_currentIndex].frame = _currentItemFrame; + if (_itemViews.count == 2) { + _nextItemFrame = CGRectMake(self.width * 2, 0, self.width, self.height); + [self getItemAtIndex:_nextIndex].frame = _nextItemFrame; + } + } else { + self.scrollView.frame = self.bounds; + self.scrollView.contentSize = CGSizeMake(self.width * _itemViews.count, self.height); + self.scrollView.contentOffset = CGPointMake(_currentIndex * self.width, 0); + for (int i = 0; i < _itemViews.count; i ++) { + UIView *view = [_itemViews objectAtIndex:i]; + view.frame = CGRectMake(i * self.width, 0, self.width, self.height); + } + [self.scrollView setContentOffset:CGPointMake(_currentIndex * self.width, 0) animated:NO]; + } + [self resetIndicatorPoint]; +} + +- (void)nextPage { + if (_itemViews.count > 1) { + if (_infinite) { + [self.scrollView setContentOffset:CGPointMake(self.width * 2, 0) animated:YES]; + } else { + // the currentindex will be set at the end of animation + NSInteger nextIndex = self.currentIndex + 1; + if(nextIndex < _itemViews.count) { + [self.scrollView setContentOffset:CGPointMake(nextIndex * self.width, 0) animated:YES]; + } + } + } +} + +- (void)lastPage +{ + + NSInteger lastIndex = [self currentIndex]-1; + if (_itemViews.count > 1) { + if (_infinite) { + if (lastIndex < 0) { + lastIndex = [_itemViews count]-1; + } + } + [self setCurrentIndex:lastIndex]; + } +} + +- (void)resetScrollView +{ + if (WXFloatEqual(self.scrollView.contentOffset.x / self.width , 1.0)) + { + return; + } + [self setCurrentIndex:self.nextIndex]; + self.scrollView.contentOffset = CGPointMake(self.width, 0); +} + +#pragma mark Public Methods + +- (void)setIndicator:(WXIndicatorView *)indicator +{ + _indicator = indicator; + [_indicator setPointCount:self.itemViews.count]; + [_indicator setCurrentPoint:_currentIndex]; +} + +- (void)insertItemView:(UIView *)view atIndex:(NSInteger)index +{ + if (![self.itemViews containsObject:view]) { + view.tag = self.itemViews.count; + if (index < 0) { + [self.itemViews addObject:view]; + } else { + [self.itemViews insertObject:view atIndex:index]; + } + } + + if (![self.scrollView.subviews containsObject:view]) { + if (index < 0) { + [self.scrollView addSubview:view]; + } else { + [self.scrollView insertSubview:view atIndex:index]; + } + } + [self layoutSubviews]; + [self setCurrentIndex:_currentIndex]; +} + +- (void)removeItemView:(UIView *)view +{ + if ([self.itemViews containsObject:view]) { + [self.itemViews removeObject:view]; + } + + if ([self.scrollView.subviews containsObject:view]) { + [view removeFromSuperview]; + } + [self layoutSubviews]; + [self setCurrentIndex:_currentIndex]; +} + +#pragma mark ScrollView Delegate + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView +{ + if (_infinite) { + CGFloat offX = scrollView.contentOffset.x; + self.direction = offX > self.width ? DirectionLeft : offX < self.width ? DirectionRight : DirectionNone; + } + if (self.delegate && [self.delegate respondsToSelector:@selector(recycleSliderView:didScroll:)]) { + [self.delegate recycleSliderView:self didScroll:self.scrollView]; + } +} + +- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView +{ + if (self.delegate && [self.delegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]) { + [self.delegate scrollViewWillBeginDragging:self.scrollView]; + } +} + +- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate +{ + if (self.delegate && [self.delegate respondsToSelector:@selector(scrollViewDidEndDragging: willDecelerate:)]) { + [self.delegate scrollViewDidEndDragging:self.scrollView willDecelerate:decelerate]; + } +} + +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { + if (_infinite) { + [self resetScrollView]; + } else { + NSInteger index = _scrollView.contentOffset.x / self.width; + [self setCurrentIndex:index]; + } +} + +- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { + if (_infinite) { + [self resetScrollView]; + } else { + NSInteger index = _scrollView.contentOffset.x / self.width; + [self setCurrentIndex:index]; + } +} + +@end + +@interface WXCycleSliderComponent () <WXRecycleSliderViewDelegate,WXIndicatorComponentDelegate> + +@property (nonatomic, strong) WXRecycleSliderView *recycleSliderView; +@property (nonatomic, strong) NSTimer *autoTimer; +@property (nonatomic, assign) NSInteger currentIndex; +@property (nonatomic, assign) BOOL autoPlay; +@property (nonatomic, assign) NSUInteger interval; +@property (nonatomic, assign) NSInteger index; +@property (nonatomic, assign) CGFloat lastOffsetXRatio; +@property (nonatomic, assign) CGFloat offsetXAccuracy; +@property (nonatomic, assign) BOOL sliderChangeEvent; +@property (nonatomic, assign) BOOL sliderScrollEvent; +@property (nonatomic, assign) BOOL sliderScrollStartEvent; +@property (nonatomic, assign) BOOL sliderScrollEndEvent; +@property (nonatomic, assign) BOOL sliderStartEventFired; +@property (nonatomic, strong) NSMutableArray *childrenView; +@property (nonatomic, assign) BOOL scrollable; +@property (nonatomic, assign) BOOL infinite; + +@end + +@implementation WXCycleSliderComponent + +- (void) dealloc +{ + [self _stopAutoPlayTimer]; +} + +- (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]) { + _sliderChangeEvent = NO; + _sliderScrollEvent = NO; + _interval = 3000; + _childrenView = [NSMutableArray new]; + _lastOffsetXRatio = 0; + + if (attributes[@"autoPlay"]) { + _autoPlay = [WXConvert BOOL:attributes[@"autoPlay"]]; + } + + if (attributes[@"interval"]) { + _interval = [WXConvert NSInteger:attributes[@"interval"]]; + } + + if (attributes[@"index"]) { + _index = [WXConvert NSInteger:attributes[@"index"]]; + } + _scrollable = attributes[@"scrollable"] ? [WXConvert BOOL:attributes[@"scrollable"]] : YES; + if (attributes[@"offsetXAccuracy"]) { + _offsetXAccuracy = [WXConvert CGFloat:attributes[@"offsetXAccuracy"]]; + } + _infinite = attributes[@"infinite"] ? [WXConvert BOOL:attributes[@"infinite"]] : YES; +//#ifndef USE_FLEX + if (![WXComponent isUseFlex]) { + self.cssNode->style.flex_direction = CSS_FLEX_DIRECTION_ROW; + } +//#else + else + { + self.flexCssNode->setFlexDirection(WeexCore::kFlexDirectionRow,NO); + } +//#endif + + } + return self; +} + +- (UIView *)loadView +{ + return [[WXRecycleSliderView alloc] init]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + _recycleSliderView = (WXRecycleSliderView *)self.view; + _recycleSliderView.delegate = self; + _recycleSliderView.scrollView.pagingEnabled = YES; + _recycleSliderView.exclusiveTouch = YES; + _recycleSliderView.scrollView.scrollEnabled = _scrollable; + _recycleSliderView.infinite = _infinite; + UIAccessibilityTraits traits = UIAccessibilityTraitAdjustable; + if (_autoPlay) { + traits |= UIAccessibilityTraitUpdatesFrequently; + [self _startAutoPlayTimer]; + } else { + [self _stopAutoPlayTimer]; + } + _recycleSliderView.accessibilityTraits = traits; +} + +- (void)layoutDidFinish +{ + _recycleSliderView.currentIndex = _index; +} + +- (void)viewDidUnload +{ + [_childrenView removeAllObjects]; +} + +- (void)insertSubview:(WXComponent *)subcomponent atIndex:(NSInteger)index +{ + if (subcomponent->_positionType == WXPositionTypeFixed) { + [self.weexInstance.rootView addSubview:subcomponent.view]; + return; + } + + // use _lazyCreateView to forbid component like cell's view creating + if(_lazyCreateView) { + subcomponent->_lazyCreateView = YES; + } + + if (!subcomponent->_lazyCreateView || (self->_lazyCreateView && [self isViewLoaded])) { + UIView *view = subcomponent.view; + + if(index < 0) { + [self.childrenView addObject:view]; + } + else { + [self.childrenView insertObject:view atIndex:index]; + } + + WXRecycleSliderView *recycleSliderView = (WXRecycleSliderView *)self.view; + if ([view isKindOfClass:[WXIndicatorView class]]) { + ((WXIndicatorComponent *)subcomponent).delegate = self; + [recycleSliderView addSubview:view]; + [self setIndicatorView:(WXIndicatorView *)view]; + return; + } + + subcomponent.isViewFrameSyncWithCalculated = NO; + + if (index == -1) { + [recycleSliderView insertItemView:view atIndex:index]; + } else { + NSInteger offset = 0; + for (int i = 0; i < [self.childrenView count]; ++i) { + if (index == i) break; + + if ([self.childrenView[i] isKindOfClass:[WXIndicatorView class]]) { + offset++; + } + } + [recycleSliderView insertItemView:view atIndex:index - offset]; + + // check if should apply current contentOffset + // in case inserting subviews after layoutDidFinish + if (index-offset == _index && _index>0) { + recycleSliderView.currentIndex = _index; + } + } + [recycleSliderView layoutSubviews]; + } +} + +- (void)willRemoveSubview:(WXComponent *)component +{ + UIView *view = component.view; + + if(self.childrenView && [self.childrenView containsObject:view]) { + [self.childrenView removeObject:view]; + } + + WXRecycleSliderView *recycleSliderView = (WXRecycleSliderView *)_view; + [recycleSliderView removeItemView:view]; + [recycleSliderView setCurrentIndex:0]; +} + +- (void)updateAttributes:(NSDictionary *)attributes +{ + if (attributes[@"autoPlay"]) { + _autoPlay = [WXConvert BOOL:attributes[@"autoPlay"]]; + if (_autoPlay) { + [self _startAutoPlayTimer]; + } else { + [self _stopAutoPlayTimer]; + } + } + + if (attributes[@"interval"]) { + _interval = [WXConvert NSInteger:attributes[@"interval"]]; + [self _stopAutoPlayTimer]; + + if (_autoPlay) { + [self _startAutoPlayTimer]; + } + } + + if (attributes[@"index"]) { + _index = [WXConvert NSInteger:attributes[@"index"]]; + self.currentIndex = _index; + self.recycleSliderView.currentIndex = _index; + } + + if (attributes[@"scrollable"]) { + _scrollable = attributes[@"scrollable"] ? [WXConvert BOOL:attributes[@"scrollable"]] : YES; + ((WXRecycleSliderView *)self.view).scrollView.scrollEnabled = _scrollable; + } + + if (attributes[@"offsetXAccuracy"]) { + _offsetXAccuracy = [WXConvert CGFloat:attributes[@"offsetXAccuracy"]]; + } + if (attributes[@"infinite"]) { + _infinite = [WXConvert BOOL:attributes[@"infinite"]]; + } +} + +- (void)addEvent:(NSString *)eventName +{ + if ([eventName isEqualToString:@"change"]) { + _sliderChangeEvent = YES; + } + if ([eventName isEqualToString:@"scroll"]) { + _sliderScrollEvent = YES; + } +} + +- (void)removeEvent:(NSString *)eventName +{ + if ([eventName isEqualToString:@"change"]) { + _sliderChangeEvent = NO; + } + if ([eventName isEqualToString:@"scroll"]) { + _sliderScrollEvent = NO; + } +} + +#pragma mark WXIndicatorComponentDelegate Methods + +-(void)setIndicatorView:(WXIndicatorView *)indicatorView +{ + NSAssert(_recycleSliderView, @""); + [_recycleSliderView setIndicator:indicatorView]; +} + +- (void)resumeAutoPlay:(id)resume +{ + if (_autoPlay) { + if ([resume boolValue]) { + [self _startAutoPlayTimer]; + } else { + [self _stopAutoPlayTimer]; + } + } +} + +#pragma mark Private Methods + +- (void)_startAutoPlayTimer +{ + if (!self.autoTimer || ![self.autoTimer isValid]) { + __weak __typeof__(self) weakSelf = self; + self.autoTimer = [NSTimer wx_scheduledTimerWithTimeInterval:_interval/1000.0f block:^() { + [weakSelf _autoPlayOnTimer]; + } repeats:YES]; + [[NSRunLoop currentRunLoop] addTimer:self.autoTimer forMode:NSRunLoopCommonModes]; + } +} + +- (void)_stopAutoPlayTimer +{ + if (self.autoTimer && [self.autoTimer isValid]) { + [self.autoTimer invalidate]; + self.autoTimer = nil; + } +} + +- (void)_autoPlayOnTimer +{ + if (!_infinite && (_currentIndex == _recycleSliderView.itemViews.count - 1)) { + [self _stopAutoPlayTimer]; + }else { + [self.recycleSliderView nextPage]; + } +} + +#pragma mark ScrollView Delegate + +- (void)recycleSliderView:(WXRecycleSliderView *)recycleSliderView didScroll:(UIScrollView *)scrollView +{ + if (_sliderScrollEvent) { + CGFloat width = scrollView.frame.size.width; + CGFloat XDeviation = 0; + if (_infinite) { + XDeviation = - (scrollView.contentOffset.x - width); + } else { + XDeviation = - (scrollView.contentOffset.x - width * _currentIndex); + } + CGFloat offsetXRatio = (XDeviation / width); + if (fabs(offsetXRatio - _lastOffsetXRatio) >= _offsetXAccuracy) { + _lastOffsetXRatio = offsetXRatio; + [self fireEvent:@"scroll" params:@{@"offsetXRatio":[NSNumber numberWithFloat:offsetXRatio]} domChanges:nil]; + } + } +} + +- (void)recycleSliderView:(WXRecycleSliderView *)recycleSliderView didScrollToItemAtIndex:(NSInteger)index +{ + + if (_sliderChangeEvent) { + [self fireEvent:@"change" params:@{@"index":@(index)} domChanges:@{@"attrs": @{@"index": @(index)}}]; + } + self.currentIndex = index; +} + +- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView +{ + [self _stopAutoPlayTimer]; +} + +- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate +{ + if (_autoPlay) { + [self _startAutoPlayTimer]; + } +} + +@end
