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