You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@weex.apache.org by ac...@apache.org on 2018/04/26 12:40:05 UTC
[13/16] incubator-weex git commit: [WEEX-311] [iOS] use new
layoutEngin to replace yoga
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