blob: 854ef7fe6b252f96ebad451e5e21919901bbb9a6 [file] [log] [blame]
/*
* 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 "WXSliderComponent.h"
#import "WXIndicatorComponent.h"
#import "WXComponent_internal.h"
#import "NSTimer+Weex.h"
#import "WXSDKManager.h"
#import "WXUtility.h"
@class WXSliderView;
@class WXIndicatorView;
@protocol WXSliderViewDelegate <UIScrollViewDelegate>
- (void)sliderView:(WXSliderView *)sliderView sliderViewDidScroll:(UIScrollView *)scrollView;
- (void)sliderView:(WXSliderView *)sliderView didScrollToItemAtIndex:(NSInteger)index;
@optional
- (void)sliderView:(WXSliderView *)sliderView scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView;
@end
@interface WXSliderView : UIView <UIScrollViewDelegate>
@property (nonatomic, strong) WXIndicatorView *indicator;
@property (nonatomic, weak) id<WXSliderViewDelegate> delegate;
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) NSMutableArray *itemViews;
@property (nonatomic, assign) NSInteger currentIndex;
- (UIScrollView *)scrollView;
- (void)insertItemView:(UIView *)view atIndex:(NSInteger)index;
- (void)removeItemView:(UIView *)view;
- (void)scroll2ItemView:(NSInteger)index animated:(BOOL)animated;
- (void)layoutItemViews;
- (void)loadData;
@end
@implementation WXSliderView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UIView *tempBackGround = [[UIView alloc] init];
tempBackGround.backgroundColor = [UIColor clearColor];
[self addSubview:tempBackGround];
_scrollView = [[UIScrollView alloc] init];
_scrollView.backgroundColor = [UIColor clearColor];
_scrollView.delegate = self;
_scrollView.showsHorizontalScrollIndicator = NO;
_scrollView.showsVerticalScrollIndicator = NO;
_scrollView.scrollsToTop = NO;
[self addSubview:_scrollView];
_itemViews = [NSMutableArray array];
_currentIndex = -1;
}
return self;
}
- (void)dealloc
{
if (_scrollView) {
_scrollView.delegate = nil;
}
//[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
- (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
NSInteger lastIndex = _currentIndex;
lastIndex --;
if (lastIndex < 0) {
lastIndex = 0;
}
[self setCurrentIndex:lastIndex];
}
- (void)accessibilityDecrement
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.wx_component performSelector:NSSelectorFromString(@"resumeAutoPlay:") withObject:@(NO)];
#pragma clang diagnostic pop
NSInteger nextIndex = _currentIndex;
nextIndex ++;
if (nextIndex >= [_itemViews count]) {
nextIndex = [_itemViews count]-1;
}
[self setCurrentIndex:nextIndex];
}
- (void)accessibilityElementDidLoseFocus
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.wx_component performSelector:NSSelectorFromString(@"resumeAutoPlay:") withObject:@(YES)];
#pragma clang diagnostic pop
}
- (void)setIndicator:(WXIndicatorView *)indicator
{
_indicator = indicator;
[_indicator setPointCount:self.itemViews.count];
[_indicator setCurrentPoint:_currentIndex];
}
- (void)setCurrentIndex:(NSInteger)currentIndex
{
if (_currentIndex == currentIndex) return;
_currentIndex = currentIndex;
[self.indicator setCurrentPoint:_currentIndex];
[self _configSubViews];
if (self.delegate && [self.delegate respondsToSelector:@selector(sliderView:didScrollToItemAtIndex:)]) {
[self.delegate sliderView:self didScrollToItemAtIndex:_currentIndex];
}
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.scrollView.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
self.scrollView.contentSize = CGSizeMake(self.itemViews.count * self.frame.size.width, self.frame.size.height);
}
#pragma mark Public Methods
- (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.indicator setPointCount:self.itemViews.count];
[self setNeedsLayout];
}
- (void)removeItemView:(UIView *)view
{
if ([self.itemViews containsObject:view]) {
[self.itemViews removeObject:view];
}
if ([self.scrollView.subviews containsObject:view]) {
[view removeFromSuperview];
}
[self.indicator setPointCount:self.itemViews.count];
[self setNeedsLayout];
}
- (void)scroll2ItemView:(NSInteger)index animated:(BOOL)animated
{
UIView *itemView = nil;
for (itemView in self.itemViews) {
if (itemView.tag == index) {
break;
}
}
if (itemView) {
[self.scrollView setContentOffset:itemView.frame.origin animated:YES];
}
}
- (void)layoutItemViews {
[self _resortItemViews];
[self _resetItemFrames];
}
- (void)loadData
{
self.scrollView.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
self.scrollView.contentSize = CGSizeMake(self.itemViews.count * self.frame.size.width, self.frame.size.height);
[self _configSubViews];
}
#pragma mark Private Methods
- (void)_configSubViews {
[self layoutItemViews];
[self _scroll2Center];
[self _resetItemCountLessThanOrEqualToTwo];
[self setNeedsLayout];
}
- (void)_resortItemViews
{
if (self.itemViews.count <= 2) return;
NSInteger center = [self _centerItemIndex];
NSInteger index = 0;
if (self.currentIndex >= 0) {
[self _validateCurrentIndex];
if (self.currentIndex > center) {
index = self.currentIndex - center;
} else {
index = self.currentIndex + self.itemViews.count - center;
}
}
else {
index = self.itemViews.count - center;
}
__weak typeof(self) weakSelf = self;
[self.itemViews sortUsingComparator:^NSComparisonResult(UIView *obj1, UIView *obj2) {
NSInteger tag1 = obj1.tag >= index ? obj1.tag - index : obj1.tag + weakSelf.itemViews.count - index;
NSInteger tag2 = obj2.tag >= index ? obj2.tag - index : obj2.tag + weakSelf.itemViews.count - index;
if (tag1 > tag2) {
return 1;
} else if (tag1 < tag2) {
return -1;
} else {
return 0;
}
}];
}
- (void)_resetItemFrames
{
CGFloat xOffset = 0; CGRect frame = CGRectZero;
for(UIView *itemView in self.itemViews) {
frame = itemView.frame;
frame.origin.x = xOffset;
frame.size.width = self.frame.size.width;
itemView.frame = frame;
xOffset += frame.size.width;
}
}
- (NSInteger)_centerItemIndex
{
if (self.itemViews.count > 2) {
return self.itemViews.count % 2 ? self.itemViews.count / 2 : self.itemViews.count / 2 - 1;
}
return 0;
}
- (void)_scroll2Center
{
if (self.itemViews.count > 2) {
UIView *itemView = [self.itemViews objectAtIndex:[self _centerItemIndex]];
//[self.scrollView scrollRectToVisible:itemView.frame animated:NO];
[self.scrollView setContentOffset:CGPointMake(itemView.frame.origin.x, itemView.frame.origin.y) animated:NO];
}
}
- (void)_resetItemCountLessThanOrEqualToTwo {
if (self.itemViews.count > 0 && self.itemViews.count <= 2) {
[self _validateCurrentIndex];
for (UIView *itemView in self.itemViews) {
if (itemView.tag == _currentIndex) {
[self.scrollView scrollRectToVisible:itemView.frame animated:NO];
break;
}
}
}
}
- (BOOL)_validateCurrentIndex {
if (_currentIndex > 0 && _currentIndex < self.itemViews.count) {
return YES;
}
_currentIndex = 0;
return NO;
}
- (BOOL)_isItemViewVisiable:(UIView *)itemView
{
CGRect itemFrame = itemView.frame;
CGFloat vx = self.scrollView.contentInset.left + self.scrollView.contentOffset.x;
CGFloat vy = self.scrollView.contentInset.top + self.scrollView.contentOffset.y;
CGFloat vw = self.scrollView.frame.size.width - self.scrollView.contentInset.left - self.scrollView.contentInset.right;
CGFloat vh = self.scrollView.frame.size.height - self.scrollView.contentInset.top - self.scrollView.contentInset.bottom;
CGRect visiableRect = CGRectMake(vx - 2, vy, vw + 4, vh);
CGRect intersection = CGRectIntersection(visiableRect, itemFrame);
if (intersection.size.width > visiableRect.size.width - 10) {
return YES;
} else {
return NO;
}
}
#pragma mark ScrollView Delegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
UIView *itemView = nil;
for (itemView in self.itemViews) {
if ([self _isItemViewVisiable:itemView]) {
break;
}
}
if (itemView) {
self.currentIndex = itemView.tag;
}
if (self.delegate && [self.delegate respondsToSelector:@selector(sliderView:sliderViewDidScroll:)]) {
[self.delegate sliderView:self sliderViewDidScroll: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];
}
}
// called when setContentOffset/scrollRectVisible:animated: finishes. called from the performselector in scrollViewDidScroll if not animating.
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
if (self.delegate && [self.delegate respondsToSelector:@selector(sliderView:scrollViewDidEndScrollingAnimation:)]) {
[self.delegate sliderView:self scrollViewDidEndScrollingAnimation:scrollView];
}
}
@end
@interface WXSliderComponent () <WXSliderViewDelegate,WXIndicatorComponentDelegate>
@property (nonatomic, strong) WXSliderView *sliderView;
@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, strong) NSMutableArray *childrenView;
@property (nonatomic, assign) BOOL scrollable;
@end
@implementation WXSliderComponent
- (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 = [attributes[@"autoPlay"] boolValue];
}
if (attributes[@"interval"]) {
_interval = [attributes[@"interval"] integerValue];
}
if (attributes[@"index"]) {
_index = [attributes[@"index"] integerValue];
}
_scrollable = attributes[@"scrollable"] ? [WXConvert BOOL:attributes[@"scrollable"]] : YES;
if (attributes[@"offsetXAccuracy"]) {
_offsetXAccuracy = [WXConvert CGFloat:attributes[@"offsetXAccuracy"]];
}
self.cssNode->style.flex_direction = CSS_FLEX_DIRECTION_ROW;
}
return self;
}
- (UIView *)loadView
{
return [[WXSliderView alloc] init];
}
- (void)viewDidLoad
{
[super viewDidLoad];
_sliderView = (WXSliderView *)self.view;
_sliderView.delegate = self;
_sliderView.scrollView.pagingEnabled = YES;
_sliderView.exclusiveTouch = YES;
_sliderView.scrollView.scrollEnabled = _scrollable;
UIAccessibilityTraits traits = UIAccessibilityTraitAdjustable;
if (_autoPlay) {
traits |= UIAccessibilityTraitUpdatesFrequently;
[self _startAutoPlayTimer];
} else {
[self _stopAutoPlayTimer];
}
_sliderView.accessibilityTraits = traits;
}
- (void)layoutDidFinish
{
_sliderView.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];
}
WXSliderView *sliderView = (WXSliderView *)self.view;
if ([view isKindOfClass:[WXIndicatorView class]]) {
((WXIndicatorComponent *)subcomponent).delegate = self;
[sliderView addSubview:view];
[self setIndicatorView:(WXIndicatorView *)view];
return;
}
subcomponent.isViewFrameSyncWithCalculated = NO;
if (index == -1) {
[sliderView 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++;
}
}
[sliderView insertItemView:view atIndex:index - offset];
}
[sliderView loadData];
}
}
- (void)willRemoveSubview:(WXComponent *)component
{
UIView *view = component.view;
if(self.childrenView && [self.childrenView containsObject:view]) {
[self.childrenView removeObject:view];
}
WXSliderView *sliderView = (WXSliderView *)_view;
[sliderView removeItemView:view];
[sliderView setCurrentIndex:0];
}
- (void)updateAttributes:(NSDictionary *)attributes
{
if (attributes[@"autoPlay"]) {
_autoPlay = [attributes[@"autoPlay"] boolValue];
if (_autoPlay) {
[self _startAutoPlayTimer];
} else {
[self _stopAutoPlayTimer];
}
}
if (attributes[@"interval"]) {
_interval = [attributes[@"interval"] integerValue];
[self _stopAutoPlayTimer];
if (_autoPlay) {
[self _startAutoPlayTimer];
}
}
if (attributes[@"index"]) {
_index = [attributes[@"index"] integerValue];
self.currentIndex = _index;
self.sliderView.currentIndex = _index;
[self.sliderView layoutItemViews];
}
if (attributes[@"scrollable"]) {
_scrollable = attributes[@"scrollable"] ? [WXConvert BOOL:attributes[@"scrollable"]] : YES;
((WXSliderView *)self.view).scrollView.scrollEnabled = _scrollable;
}
if (attributes[@"offsetXAccuracy"]) {
_offsetXAccuracy = [WXConvert CGFloat:attributes[@"offsetXAccuracy"]];
}
}
- (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(_sliderView, @"");
[_sliderView 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
{
WXSliderView *sliderView = (WXSliderView *)self.view;
int indicatorCnt = 0;
for (int i = 0; i < [self.childrenView count]; ++i) {
if ([self.childrenView[i] isKindOfClass:[WXIndicatorView class]]) {
indicatorCnt++;
}
}
self.currentIndex ++;
if (self.currentIndex < self.childrenView.count - indicatorCnt) {
[sliderView scroll2ItemView:self.currentIndex animated:YES];
} else {
self.currentIndex = 0;
[sliderView scroll2ItemView:self.currentIndex animated:YES];
}
}
#pragma mark ScrollView Delegate
- (void)sliderView:(WXSliderView *)sliderView sliderViewDidScroll:(UIScrollView *)scrollView
{
if (_sliderScrollEvent) {
CGFloat width = scrollView.frame.size.width;
CGFloat XDeviation = scrollView.frame.origin.x - (scrollView.contentOffset.x - width);
CGFloat offsetXRatio = (XDeviation / width);
if (fabs(offsetXRatio - _lastOffsetXRatio) >= _offsetXAccuracy) {
_lastOffsetXRatio = offsetXRatio;
[self fireEvent:@"scroll" params:@{@"offsetXRatio":[NSNumber numberWithFloat:offsetXRatio]} domChanges:nil];
}
}
}
- (void)sliderView:(WXSliderView *)sliderView didScrollToItemAtIndex:(NSInteger)index
{
self.currentIndex = index;
if (_sliderChangeEvent) {
[self fireEvent:@"change" params:@{@"index":@(index)} domChanges:@{@"attrs": @{@"index": @(index)}}];
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
[self _stopAutoPlayTimer];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (_autoPlay) {
[self _startAutoPlayTimer];
}
}
@end