http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Component/WXSliderNeighborComponent.mm ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXSliderNeighborComponent.mm b/ios/sdk/WeexSDK/Sources/Component/WXSliderNeighborComponent.mm new file mode 100644 index 0000000..86124ff --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/WXSliderNeighborComponent.mm @@ -0,0 +1,1812 @@ +/* + * 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 "WXSliderNeighborComponent.h" +#import "WXConvert.h" +#import "WXUtility.h" +#import "WXComponent_internal.h" +#import "WXIndicatorComponent.h" +#import "WXSDKInstance.h" +#import "NSTimer+Weex.h" +#import "WXComponent+Layout.h" + +#define MAX_VISIBLE_ITEMS 30 +#define MIN_TOGGLE_DURATION 0.2 +#define MAX_TOGGLE_DURATION 0.4 +#define SCROLL_DURATION 0.4 +#define INSERT_DURATION 0.4 +#define DECELERATE_THRESHOLD 0.1 +#define SCROLL_SPEED_THRESHOLD 2.0 +#define SCROLL_DISTANCE_THRESHOLD 0.1 +#define DECELERATION_MULTIPLIER 30.0 +#define FLOAT_ERROR_MARGIN 0.000001 + +@protocol WXSliderNeighborDelegate, WXSliderNeighborDataSource; + +@interface WXSliderNeighborView: UIView +@property (nonatomic, strong) WXIndicatorView *indicator; +@property (nonatomic, weak) id<WXSliderNeighborDelegate> delegate; +@property (nonatomic, weak) id<WXSliderNeighborDataSource> dataSource; +@property (nonatomic, assign) CGFloat perspective; +@property (nonatomic, readonly, getter = isDragging) BOOL dragging; +@property (nonatomic, readonly, getter = isScrolling) BOOL scrolling; +@property (nonatomic, assign) CGFloat autoscroll; +@property (nonatomic, assign) CGFloat scrollOffset; +@property (nonatomic, assign) CGFloat previousTranslation; +@property (nonatomic, assign, getter = isVertical) BOOL vertical; +@property (nonatomic, assign) CGFloat decelerationRate; +@property (nonatomic, assign, getter = isScrollEnabled) BOOL scrollEnabled; +@property (nonatomic, assign, getter = isPagingEnabled) BOOL pagingEnabled; +@property (nonatomic, assign) BOOL bounces; +@property (nonatomic, assign) BOOL didDrag; +@property (nonatomic, readonly, getter = isDecelerating) BOOL decelerating; +@property (nonatomic, strong, readonly) UIView *contentView; +@property (nonatomic, strong) NSMutableDictionary *itemViews; +@property (nonatomic, readonly) NSInteger numberOfVisibleItems; +@property (nonatomic, readonly) NSInteger numberOfItems; +@property (nonatomic, readonly) NSInteger numberOfPlaceholders; +@property (nonatomic, strong) NSMutableSet *itemViewPool; +@property (nonatomic, strong) NSMutableSet *placeholderViewPool; +@property (nonatomic, assign) NSTimeInterval startTime; +@property (nonatomic, assign) NSTimeInterval lastTime; +@property (nonatomic, assign) CGFloat startVelocity; +@property (nonatomic, assign) CGFloat offsetMultiplier; +@property (nonatomic, assign) CGFloat startOffset; +@property (nonatomic, assign) CGFloat endOffset; +@property (nonatomic, assign) CGSize contentOffset; +@property (nonatomic, assign) CGSize viewpointOffset; +@property (nonatomic, assign) CGFloat scrollSpeed; +@property (nonatomic, assign) CGFloat bounceDistance; +@property (nonatomic, assign) BOOL stopAtItemBoundary; +@property (nonatomic, assign) BOOL scrollToItemBoundary; +@property (nonatomic, assign) BOOL centerItemWhenSelected; +@property (nonatomic, assign) NSTimeInterval scrollDuration; +@property (nonatomic, readonly, getter = isWrapEnabled) BOOL wrapEnabled; +@property (nonatomic, strong) NSTimer *timer; +@property (nonatomic, assign) NSInteger numberOfPlaceholdersToShow; +@property (nonatomic, assign) CGFloat previousScrollOffset; +@property (nonatomic, assign) NSTimeInterval toggleTime; +@property (nonatomic, readonly) CGFloat toggle; +@property (nonatomic, assign) NSInteger previousItemIndex; +@property (nonatomic, readonly) CGFloat itemWidth; +@property (nonatomic, assign) BOOL inited; +@end + +@implementation WXSliderNeighborView + +- (instancetype)init { + + self = [super init]; + if (!self) { + } + _decelerationRate = 0.01; + _scrollEnabled = YES; + _bounces = YES; + _offsetMultiplier = 1.0; + _perspective = -1.0/500.0; + _contentOffset = CGSizeZero; + _viewpointOffset = CGSizeZero; + _scrollSpeed = 1.0; + _bounceDistance = 1.0; + _stopAtItemBoundary = YES; + _scrollToItemBoundary = YES; + _centerItemWhenSelected = YES; + _inited = NO; + + _contentView = [[UIView alloc] initWithFrame:self.bounds]; + + //pan gesture recognizer + self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(didPan:)]; + panGesture.delegate = (id <UIGestureRecognizerDelegate>)self; + [_contentView addGestureRecognizer:panGesture]; + + //tap gesture recognizer + UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTap:)]; + tapGesture.delegate = (id <UIGestureRecognizerDelegate>)self; + [_contentView addGestureRecognizer:tapGesture]; + + self.accessibilityTraits = UIAccessibilityTraitAllowsDirectInteraction; + self.isAccessibilityElement = YES; + + [self addSubview:_contentView]; + + if (_dataSource) { + [self reloadData]; + } + + return self; +} + +- (void)dealloc +{ + [self stopAnimation]; +} + +- (void)accessibilityIncrement +{ + [self setCurrentItemIndex: [self lastItemIndex]]; +} + +- (void)accessibilityDecrement +{ + [self setCurrentItemIndex:[self nextItemIndex]]; +} + +- (void)setDataSource:(id<WXSliderNeighborDataSource>)dataSource +{ + if (_dataSource != dataSource) { + _dataSource = dataSource; + if (_dataSource) { + [self reloadData]; + } + } +} + +- (void)setDelegate:(id<WXSliderNeighborDelegate>)delegate +{ + if (_delegate != delegate) { + _delegate = delegate; + if (_delegate && _dataSource) { + [self setNeedsLayout]; + } + } +} + +- (void)setVertical:(BOOL)vertical +{ + if (_vertical != vertical) + { + _vertical = vertical; + [self layOutItemViews]; + } +} + +#pragma clang diagnostic push +#pragma GCC diagnostic ignored "-Wundeclared-selector" +- (void)setCurrentItemIndex:(NSInteger)currentItemIndex +{ + if ([self currentItemIndex] == currentItemIndex) return; + + [self setScrollOffset:currentItemIndex]; + [self.indicator setCurrentPoint:currentItemIndex]; + + if (self.delegate && [self.delegate respondsToSelector:@selector(sliderView:didScrollToItemAtIndex:)]) { + [self.delegate sliderNeighbor:self didScrollToItemAtIndex:currentItemIndex]; + } + [self scroll2ItemViewAtIndex:currentItemIndex animated:YES]; + +} +#pragma clang diagnostic pop + +- (void)updateItemWidth +{ + _itemWidth = [_delegate sliderNeighborItemWidth:self] ?: _itemWidth; + if (_numberOfItems > 0) { + if ([_itemViews count] == 0) { + [self loadViewAtIndex:0]; + } + } else if (_numberOfPlaceholders > 0) { + if ([_itemViews count] == 0) { + [self loadViewAtIndex:-1]; + } + } +} + +- (NSInteger)circularCarouselItemCount +{ + NSInteger count = 0; + CGFloat spacing = [self valueForOption:WXSliderNeighborOptionSpacing withDefault:1.0]; + CGFloat width = _vertical ? self.bounds.size.height: self.bounds.size.width; + count = MIN(MAX_VISIBLE_ITEMS, MAX(12, ceil(width / (spacing * _itemWidth)) * M_PI)); + count = MIN(_numberOfItems + _numberOfPlaceholdersToShow, count); + return [self valueForOption:WXSliderNeighborOptionCount withDefault:count]; +} + +- (void)updateNumberOfVisibleItems +{ + //get number of visible items + //based on count value + CGFloat spacing = [self valueForOption:WXSliderNeighborOptionSpacing withDefault:1.0]; + CGFloat width = _vertical ? self.bounds.size.height: self.bounds.size.width; + CGFloat itemWidth = _itemWidth * spacing; + _numberOfVisibleItems = ceil(width / itemWidth) + 2; + _numberOfVisibleItems = MIN(MAX_VISIBLE_ITEMS, _numberOfVisibleItems); + _numberOfVisibleItems = [self valueForOption:WXSliderNeighborOptionVisibleItems withDefault:_numberOfVisibleItems]; + _numberOfVisibleItems = MAX(0, MIN(_numberOfVisibleItems, _numberOfItems + _numberOfPlaceholdersToShow)); + +} + +- (CGFloat)offsetForItemAtIndex:(NSInteger)index +{ + //calculate relative position + CGFloat offset = index - _scrollOffset; + if (_wrapEnabled) { + if (offset > _numberOfItems/2.0) { + offset -= _numberOfItems; + } else if (offset < -_numberOfItems/2.0) { + offset += _numberOfItems; + } + } + + return offset; +} + +- (CGFloat)alphaForItemWithOffset:(CGFloat)offset +{ + CGFloat fadeMin = -INFINITY; + CGFloat fadeMax = INFINITY; + CGFloat fadeRange = 1.0; + CGFloat fadeMinAlpha = 0.0; + fadeMin = [self valueForOption:WXSliderNeighborOptionFadeMin withDefault:fadeMin]; + fadeMax = [self valueForOption:WXSliderNeighborOptionFadeMax withDefault:fadeMax]; + fadeRange = [self valueForOption:WXSliderNeighborOptionFadeRange withDefault:fadeRange]; + fadeMinAlpha = [self valueForOption:WXSliderNeighborOptionFadeMinAlpha withDefault:fadeMinAlpha]; + + CGFloat factor = 0.0; + if (offset > fadeMax) { + factor = offset - fadeMax; + } else if (offset < fadeMin) { + factor = fadeMin - offset; + } + return 1.0 - MIN(factor, fadeRange) / fadeRange * (1.0 - fadeMinAlpha); +} + + +- (UIView *)containView:(UIView *)view +{ + //set item width + if (!_itemWidth) { + _itemWidth = _vertical? view.frame.size.height: view.frame.size.width; + } + + //set container frame + CGRect frame = view.frame; + frame.size.width = _vertical? frame.size.width: _itemWidth; + frame.size.height = _vertical? _itemWidth: frame.size.height; + UIView *containerView = [[UIView alloc] initWithFrame:frame]; + + //set view frame + frame = view.frame; + frame.origin.x = (containerView.bounds.size.width - frame.size.width) / 2.0; + frame.origin.y = (containerView.bounds.size.height - frame.size.height) / 2.0; + view.frame = frame; + [containerView addSubview:view]; + containerView.layer.opacity = 0; + containerView.transform = CGAffineTransformMakeScale(0.8, 1.0); + return containerView; +} + +- (void)transformItemViews +{ + for (NSNumber *number in _itemViews) + { + NSInteger index = [number integerValue]; + UIView *view = _itemViews[number]; + [self transformItemView:view atIndex:index]; + } +} + +- (CATransform3D)transformForItemViewWithOffset:(CGFloat)offset +{ + //set up base transform + CATransform3D transform = CATransform3DIdentity; + transform.m34 = _perspective; + transform = CATransform3DTranslate(transform, -_viewpointOffset.width, -_viewpointOffset.height, 0.0); + + //perform transforms + CGFloat spacing = [self valueForOption:WXSliderNeighborOptionSpacing withDefault:1.0]; + if (_vertical) { + return CATransform3DTranslate(transform, 0.0, offset * _itemWidth * spacing, 0.0); + } else { + return CATransform3DTranslate(transform, offset * _itemWidth * spacing, 0.0, 0.0); + } +} + +- (void)transformItemView:(UIView *)view atIndex:(NSInteger)index +{ + //calculate offset + CGFloat offset = [self offsetForItemAtIndex:index]; + + //update alpha + view.superview.layer.opacity = [self alphaForItemWithOffset:offset]; + + //center view + view.superview.center = CGPointMake(self.bounds.size.width/2.0 + _contentOffset.width, + self.bounds.size.height/2.0 + _contentOffset.height); + + //enable/disable interaction + view.superview.userInteractionEnabled = (!_centerItemWhenSelected || index == self.currentItemIndex); + view.superview.layer.rasterizationScale = [UIScreen mainScreen].scale; + + [view layoutIfNeeded]; + + CGFloat clampedOffset = MAX(-1.0, MIN(1.0, offset)); + if (_decelerating || (_scrolling && !_dragging && !_didDrag) || (_autoscroll && !_dragging) || + (!_wrapEnabled && (_scrollOffset < 0 || _scrollOffset >= _numberOfItems - 1))) { + if (offset > 0) { + _toggle = (offset <= 0.5)? -clampedOffset: (1.0 - clampedOffset); + } else { + _toggle = (offset > -0.5)? -clampedOffset: (- 1.0 - clampedOffset); + } + } + + //calculate transform + CATransform3D transform = [self transformForItemViewWithOffset:offset]; + + //transform view + view.superview.layer.transform = transform; + + //backface culling + BOOL showBackfaces = view.layer.doubleSided; + if (showBackfaces) { + showBackfaces = YES; + } + showBackfaces = !![self valueForOption:WXSliderNeighborOptionShowBackfaces withDefault:showBackfaces]; + + view.superview.hidden = !(showBackfaces ?: (transform.m33 > 0.0)); +} + +NSComparisonResult sliderNeighorCompareViewDepth(UIView *view1, UIView *view2, WXSliderNeighborView *self) +{ + //compare depths + CATransform3D t1 = view1.superview.layer.transform; + CATransform3D t2 = view2.superview.layer.transform; + CGFloat z1 = t1.m13 + t1.m23 + t1.m33 + t1.m43; + CGFloat z2 = t2.m13 + t2.m23 + t2.m33 + t2.m43; + CGFloat difference = z1 - z2; + + //if depths are equal, compare distance from current view + if (difference == 0.0) { + CATransform3D t3 = [self currentItemView].superview.layer.transform; + if (self.vertical) { + CGFloat y1 = t1.m12 + t1.m22 + t1.m32 + t1.m42; + CGFloat y2 = t2.m12 + t2.m22 + t2.m32 + t2.m42; + CGFloat y3 = t3.m12 + t3.m22 + t3.m32 + t3.m42; + difference = fabs(y2 - y3) - fabs(y1 - y3); + } else { + CGFloat x1 = t1.m11 + t1.m21 + t1.m31 + t1.m41; + CGFloat x2 = t2.m11 + t2.m21 + t2.m31 + t2.m41; + CGFloat x3 = t3.m11 + t3.m21 + t3.m31 + t3.m41; + difference = fabs(x2 - x3) - fabs(x1 - x3); + } + } + return (difference < 0.0)? NSOrderedAscending: NSOrderedDescending; +} + +- (void)depthSortViews +{ + for (UIView *view in [[_itemViews allValues] sortedArrayUsingFunction:(NSInteger (*)(id, id, void *))sliderNeighorCompareViewDepth context:(__bridge void *)self]) + { + [_contentView bringSubviewToFront:(UIView *__nonnull)view.superview]; + } +} + +- (void)layOutItemViews +{ + //bail out if not set up yet + if (!_dataSource || !_contentView) { + return; + } + + //update wrap + _wrapEnabled = YES; + _wrapEnabled = !![self valueForOption:WXSliderNeighborOptionWrap withDefault:_wrapEnabled]; + + //no placeholders on wrapped carousels + _numberOfPlaceholdersToShow = _wrapEnabled? 0: _numberOfPlaceholders; + + //set item width + [self updateItemWidth]; + + //update number of visible items + [self updateNumberOfVisibleItems]; + + //prevent false index changed event + _previousScrollOffset = self.scrollOffset; + + //update offset multiplier + _offsetMultiplier = 1.0; + _offsetMultiplier = [self valueForOption:WXSliderNeighborOptionOffsetMultiplier withDefault:_offsetMultiplier]; + + //align + if (!_scrolling && !_decelerating && !_autoscroll) { + if (_scrollToItemBoundary && self.currentItemIndex != -1) { + [self scroll2ItemViewAtIndex:self.currentItemIndex animated:YES]; + } else { + _scrollOffset = [self clampedOffset:_scrollOffset]; + } + } + //update views + if (_inited) { + [self didScroll]; + } + + _inited = YES; +} + +- (CGFloat)valueForOption:(WXSliderNeighborOption)option withDefault:(CGFloat)value +{ + if (_delegate && [_delegate respondsToSelector:@selector(sliderNeighbor:valueForOption:withDefault:)]) { + return [_delegate sliderNeighbor:self valueForOption:option withDefault:value]; + } + return value; +} + +#pragma mark - +#pragma mark Gestures and taps + +- (NSInteger)viewOrSuperviewIndex:(UIView *)view +{ + if (view == nil || view == _contentView) { + return NSNotFound; + } + NSInteger index = [self indexOfItemView:view]; + if (index == NSNotFound) { + return [self viewOrSuperviewIndex:view.superview]; + } + return index; +} + +- (BOOL)viewOrSuperview:(UIView *)view implementsSelector:(SEL)selector +{ + if (!view || view == self.contentView) { + return NO; + } + + Class viewClass = [view class]; + while (viewClass && viewClass != [UIView class]) + { + unsigned int numberOfMethods; + Method *methods = class_copyMethodList(viewClass, &numberOfMethods); + for (unsigned int i = 0; i < numberOfMethods; i++) + { + if (method_getName(methods[i]) == selector) { + free(methods); + return YES; + } + } + if (methods) free(methods); + viewClass = [viewClass superclass]; + } + + return [self viewOrSuperview:view.superview implementsSelector:selector]; +} + +- (id)viewOrSuperview:(UIView *)view ofClass:(Class)swizzClass +{ + if (!view || view == self.contentView) { + return nil; + } else if ([view isKindOfClass:swizzClass]) { + return view; + } + return [self viewOrSuperview:view.superview ofClass:swizzClass]; +} + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gesture shouldReceiveTouch:(UITouch *)touch +{ + if (_scrollEnabled) { + _dragging = NO; + _scrolling = NO; + _decelerating = NO; + } + + if ([gesture isKindOfClass:[UITapGestureRecognizer class]]) { + //handle tap + NSInteger index = [self viewOrSuperviewIndex:touch.view]; + if (index == NSNotFound && _centerItemWhenSelected) { + //view is a container view + index = [self viewOrSuperviewIndex:[touch.view.subviews lastObject]]; + } + if (index != NSNotFound) { + if ([self viewOrSuperview:touch.view implementsSelector:@selector(touchesBegan:withEvent:)]) { + return NO; + } + } + } else if ([gesture isKindOfClass:[UIPanGestureRecognizer class]]) { + if (!_scrollEnabled) { + return NO; + } else if ([self viewOrSuperview:touch.view implementsSelector:@selector(touchesMoved:withEvent:)]) { + UIScrollView *scrollView = [self viewOrSuperview:touch.view ofClass:[UIScrollView class]]; + if (scrollView) { + return !scrollView.scrollEnabled || + (self.vertical && scrollView.contentSize.height <= scrollView.frame.size.height) || + (!self.vertical && scrollView.contentSize.width <= scrollView.frame.size.width); + } + if ([self viewOrSuperview:touch.view ofClass:[UIButton class]] || + [self viewOrSuperview:touch.view ofClass:[UIBarButtonItem class]]) { + return YES; + } + return NO; + } + } + return YES; +} + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{ + //if the view which the otherGestureRecognizer works on is a scrollview and also it is scrollEnabled vertically ,at this time,we should not block the guesture from being recognized by the otherGestureRecognize + if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) { + if ([otherGestureRecognizer.view isKindOfClass:[UIScrollView class]]) { + UIScrollView* scrollview = (UIScrollView *)otherGestureRecognizer.view; + if (scrollview.scrollEnabled) { + UIPanGestureRecognizer* panRcgn= (UIPanGestureRecognizer *)gestureRecognizer; + //check offset for confirming vertival movement + if (fabs([panRcgn translationInView:panRcgn.view].y) > fabs([panRcgn translationInView:panRcgn.view].x)*16) { + return YES; + } + } + } + } + return NO; +} + + +- (void)didPan:(UIPanGestureRecognizer *)panGesture +{ + if (_scrollEnabled && _numberOfItems) { + switch (panGesture.state) + { + case UIGestureRecognizerStateBegan: + { + _dragging = YES; + _scrolling = NO; + _decelerating = NO; + _previousTranslation = _vertical? [panGesture translationInView:self].y: [panGesture translationInView:self].x; + + [_delegate sliderNeighborWillBeginDragging:self]; + break; + } + case UIGestureRecognizerStateEnded: + case UIGestureRecognizerStateCancelled: + case UIGestureRecognizerStateFailed: + { + _dragging = NO; + _didDrag = YES; + if ([self shouldDecelerate]) { + _didDrag = NO; + [self startDecelerating]; + } + + [self pushAnimationState:YES]; + [_delegate sliderNeighborDidEndDragging:self willDecelerate:_decelerating]; + [self popAnimationState]; + + if (!_decelerating) { + if ((_scrollToItemBoundary || fabs(_scrollOffset - [self clampedOffset:_scrollOffset]) > FLOAT_ERROR_MARGIN) && !_autoscroll) { + if (fabs(_scrollOffset - self.currentItemIndex) < FLOAT_ERROR_MARGIN) { + //call scroll to trigger events for legacy support reasons + //even though technically we don't need to scroll at all + [self scroll2ItemViewAtIndex:self.currentItemIndex duration:0.01]; + } else { + [self scroll2ItemViewAtIndex:self.currentItemIndex animated:YES]; + } + } else { + [self depthSortViews]; + } + } else { + [self pushAnimationState:YES]; + [_delegate sliderNeighborWillBeginDecelerating:self]; + [self popAnimationState]; + } + break; + } + case UIGestureRecognizerStateChanged: + { + CGFloat translation = _vertical? [panGesture translationInView:self].y: [panGesture translationInView:self].x; + CGFloat velocity = _vertical? [panGesture velocityInView:self].y: [panGesture velocityInView:self].x; + + CGFloat factor = 1.0; + if (!_wrapEnabled && _bounces) { + factor = 1.0 - MIN(fabs(_scrollOffset - [self clampedOffset:_scrollOffset]), + _bounceDistance) / _bounceDistance; + } + + _startVelocity = -velocity * factor * _scrollSpeed / _itemWidth; + _scrollOffset -= (translation - _previousTranslation) * factor * _offsetMultiplier / _itemWidth; + _previousTranslation = translation; + [self didScroll]; + break; + } + case UIGestureRecognizerStatePossible: + { + //do nothing + break; + } + } + } +} + +- (void)didTap:(UITapGestureRecognizer *)tapGesture +{ + //check for tapped view + NSInteger index = [self indexOfItemView:[self itemViewAtPoint:[tapGesture locationInView:_contentView]]]; + if (index != NSNotFound) { + if (!_delegate || [_delegate sliderNeighbor:self shouldSelectItemAtIndex:index]) { + if ((index != self.currentItemIndex && _centerItemWhenSelected) || + (index == self.currentItemIndex && _scrollToItemBoundary)) { + [self scroll2ItemViewAtIndex:index animated:YES]; + } + [_delegate sliderNeighbor:self didSelectItemAtIndex:index]; + } else if (_scrollEnabled && _scrollToItemBoundary && _autoscroll) { + [self scroll2ItemViewAtIndex:self.currentItemIndex animated:YES]; + } + } else { + [self scroll2ItemViewAtIndex:self.currentItemIndex animated:YES]; + } +} + +- (void)pushAnimationState:(BOOL)enabled +{ + [CATransaction begin]; + [CATransaction setDisableActions:!enabled]; +} + +- (void)popAnimationState +{ + [CATransaction commit]; +} + +- (void)reloadData +{ + //remove old views + for (UIView *view in [_itemViews allValues]) + { + [view.superview removeFromSuperview]; + } + + //bail out if not set up yet + if (!_dataSource || !_contentView) + { + return; + } + + //get number of items and placeholders + _numberOfVisibleItems = 0; + _numberOfItems = [_dataSource numberOfItemsInSliderNeighbor:self]; + if ([_dataSource respondsToSelector:@selector(numberOfPlaceholdersInsliderNeighbor:)]) { + _numberOfPlaceholders = [_dataSource numberOfPlaceholdersInsliderNeighbor:self]; + } + + //reset view pools + self.itemViews = [NSMutableDictionary dictionary]; + self.itemViewPool = [NSMutableSet set]; + self.placeholderViewPool = [NSMutableSet setWithCapacity:_numberOfPlaceholders]; + + //layout views + [self setNeedsLayout]; + + //fix scroll offset + if (_numberOfItems > 0 && _scrollOffset < 0.0) + { + [self scroll2ItemViewAtIndex:0 animated:(_numberOfPlaceholders > 0)]; + } +} + +#pragma mark scrolling + +- (NSInteger)currentItemIndex +{ + return [self clampedIndex:round(_scrollOffset)]; +} + +- (NSInteger)nextItemIndex +{ + return ([self currentItemIndex]+1)%_numberOfItems; +} + +- (NSInteger)lastItemIndex +{ + if ([self currentItemIndex] == 0) { + return _numberOfItems - 1; + } + return ((NSInteger)round(abs((int)[self currentItemIndex] - 1))) % _numberOfItems; +} + +- (NSInteger)minScrollDistanceFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex +{ + NSInteger directDistance = toIndex - fromIndex; + if (_wrapEnabled) + { + NSInteger wrappedDistance = MIN(toIndex, fromIndex) + _numberOfItems - MAX(toIndex, fromIndex); + if (fromIndex < toIndex) + { + wrappedDistance = -wrappedDistance; + } + return (ABS(directDistance) <= ABS(wrappedDistance))? directDistance: wrappedDistance; + } + return directDistance; +} + +- (NSInteger)clampedIndex:(NSInteger)index +{ + if (_numberOfItems == 0) + { + return -1; + } else if (_wrapEnabled) { + return index - floor((CGFloat)index / (CGFloat)_numberOfItems) * _numberOfItems; + } else { + return MIN(MAX(0, index), MAX(0, _numberOfItems - 1)); + } +} + +- (CGFloat)clampedOffset:(CGFloat)offset +{ + if (_numberOfItems == 0) { + return -1.0; + } else if (_wrapEnabled) { + return offset - floor(offset / (CGFloat)_numberOfItems) * _numberOfItems; + } else { + return MIN(MAX(0.0, offset), MAX(0.0, (CGFloat)_numberOfItems - 1.0)); + } +} + +- (CGFloat)minScrollDistanceFromOffset:(CGFloat)fromOffset toOffset:(CGFloat)toOffset +{ + CGFloat directDistance = toOffset - fromOffset; + if (_wrapEnabled) { + CGFloat wrappedDistance = MIN(toOffset, fromOffset) + _numberOfItems - MAX(toOffset, fromOffset); + if (fromOffset < toOffset) { + wrappedDistance = -wrappedDistance; + } + return (fabs(directDistance) <= fabs(wrappedDistance))? directDistance: wrappedDistance; + } + return directDistance; +} + +- (void)scrollByOffset:(CGFloat)offset duration:(NSTimeInterval)duration +{ + if (duration > 0.0) { + _decelerating = NO; + _scrolling = YES; + _startTime = CACurrentMediaTime(); + _startOffset = _scrollOffset; + _scrollDuration = duration; + _endOffset = _startOffset + offset; + if (!_wrapEnabled) { + _endOffset = [self clampedOffset:_endOffset]; + } + [_delegate sliderNeighborWillBeginScrollingAnimation:self]; + [self startAnimation]; + } else { + self.scrollOffset += offset; + } +} + +- (void)scroll2Offset:(CGFloat)offset duration:(NSTimeInterval)duration +{ + [self scrollByOffset:[self minScrollDistanceFromOffset:_scrollOffset toOffset:offset] duration:duration]; + if (_delegate && [_delegate respondsToSelector:@selector(sliderNeighbor:didScrollToItemAtIndex:)]) { + [_delegate sliderNeighbor:self didScrollToItemAtIndex:offset]; + } +} + +- (void)scroll2ItemViewAtIndex:(NSInteger)index duration:(NSTimeInterval)duration +{ + [self scroll2Offset:index duration:duration]; +} + +- (void)scroll2ItemViewAtIndex:(NSInteger)index animated:(BOOL)animated +{ + [self scroll2ItemViewAtIndex:index duration:animated? 0.6: 0]; +} + +- (void)scrollByNumberOfItems:(NSInteger)itemCount duration:(NSTimeInterval)duration +{ + if (duration > 0.0) { + CGFloat offset = 0.0; + if (itemCount > 0) { + offset = (floor(_scrollOffset) + itemCount) - _scrollOffset; + } else if (itemCount < 0) { + offset = (ceil(_scrollOffset) + itemCount) - _scrollOffset; + } else { + offset = round(_scrollOffset) - _scrollOffset; + } + [self scrollByOffset:offset duration:duration]; + } else { + self.scrollOffset = [self clampedIndex:_previousItemIndex + itemCount]; + } +} + +- (void)removeItemAtIndex:(NSInteger)index animated:(BOOL)animated +{ + index = [self clampedIndex:index]; + UIView *itemView = [self itemViewAtIndex:index]; + + if (animated) { + [UIView beginAnimations:nil context:nil]; + [UIView setAnimationDuration:0.1]; + [UIView setAnimationDelegate:itemView.superview]; + [UIView setAnimationDidStopSelector:@selector(removeFromSuperview)]; + [self performSelector:@selector(queueItemView:) withObject:itemView afterDelay:0.1]; + itemView.superview.layer.opacity = 0.0; + [UIView commitAnimations]; + + [UIView beginAnimations:nil context:nil]; + [UIView setAnimationDelay:0.1]; + [UIView setAnimationDuration:INSERT_DURATION]; + [UIView setAnimationDelegate:self]; + [UIView setAnimationDidStopSelector:@selector(depthSortViews)]; + [self removeViewAtIndex:index]; + _numberOfItems --; + _wrapEnabled = !![self valueForOption:WXSliderNeighborOptionWrap withDefault:_wrapEnabled]; + [self updateNumberOfVisibleItems]; + _scrollOffset = self.currentItemIndex; + [self didScroll]; + [UIView commitAnimations]; + } else { + [self pushAnimationState:NO]; + [self queueItemView:itemView]; + [itemView.superview removeFromSuperview]; + [self removeViewAtIndex:index]; + _numberOfItems --; + _wrapEnabled = !![self valueForOption:WXSliderNeighborOptionWrap withDefault:_wrapEnabled]; + _scrollOffset = self.currentItemIndex; + [self didScroll]; + [self depthSortViews]; + [self popAnimationState]; + } +} + +- (void)insertItemAtIndex:(NSInteger)index animated:(BOOL)animated +{ + _numberOfItems ++; + _wrapEnabled = !![self valueForOption:WXSliderNeighborOptionWrap withDefault:_wrapEnabled]; + [self updateNumberOfVisibleItems]; + + index = [self clampedIndex:index]; + [self insertView:nil atIndex:index]; + [self loadViewAtIndex:index]; + + if (fabs(_itemWidth) < FLOAT_ERROR_MARGIN) { + [self updateItemWidth]; + } + + if (animated) { + [UIView beginAnimations:nil context:nil]; + [UIView setAnimationDuration:INSERT_DURATION]; + [UIView setAnimationDelegate:self]; + [UIView setAnimationDidStopSelector:@selector(didScroll)]; + [self transformItemViews]; + [UIView commitAnimations]; + } else { + [self pushAnimationState:NO]; + [self didScroll]; + [self popAnimationState]; + } + + if (_scrollOffset < 0.0) { + [self scroll2ItemViewAtIndex:0 animated:(animated && _numberOfPlaceholders)]; + } +} + +- (void)reloadItemAtIndex:(NSInteger)index animated:(BOOL)animated +{ + //get container view + UIView *containerView = [[self itemViewAtIndex:index] superview]; + if (containerView) { + if (animated) { + //fade transition + CATransition *transition = [CATransition animation]; + transition.duration = INSERT_DURATION; + transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + transition.type = kCATransitionFade; + [containerView.layer addAnimation:transition forKey:nil]; + } + + //reload view + [self loadViewAtIndex:index withContainerView:containerView]; + } +} + +#pragma mark Animation +- (void)step +{ + [self pushAnimationState:NO]; + NSTimeInterval currentTime = CACurrentMediaTime(); + double delta = currentTime - _lastTime; + _lastTime = currentTime; + + if (_scrolling && !_dragging) { + NSTimeInterval time = MIN(1.0, (currentTime - _startTime) / _scrollDuration); + delta = [self easeInOut:time]; + _scrollOffset = _startOffset + (_endOffset - _startOffset) * delta; + [self didScroll]; + if (time >= 1.0) { + _scrolling = NO; + [self depthSortViews]; + [self pushAnimationState:YES]; + [_delegate sliderNeighborDidEndScrollingAnimation:self]; + [self popAnimationState]; + } + } else if (_decelerating) { + CGFloat time = MIN(_scrollDuration, currentTime - _startTime); + CGFloat acceleration = -_startVelocity/_scrollDuration; + CGFloat distance = _startVelocity * time + 0.5 * acceleration * pow(time, 2.0); + _scrollOffset = _startOffset + distance; + [self didScroll]; + if (fabs(time - _scrollDuration) < FLOAT_ERROR_MARGIN) { + _decelerating = NO; + [self pushAnimationState:YES]; + [_delegate sliderNeighborDidEndDecelerating:self]; + [self popAnimationState]; + if ((_scrollToItemBoundary || fabs(_scrollOffset - [self clampedOffset:_scrollOffset]) > FLOAT_ERROR_MARGIN) && !_autoscroll) { + if (fabs(_scrollOffset - self.currentItemIndex) < FLOAT_ERROR_MARGIN) { + //call scroll to trigger events for legacy support reasons + //even though technically we don't need to scroll at all + [self scroll2ItemViewAtIndex:self.currentItemIndex duration:0.01]; + } else { + [self scroll2ItemViewAtIndex:self.currentItemIndex animated:YES]; + } + } else { + CGFloat difference = round(_scrollOffset) - _scrollOffset; + if (difference > 0.5) { + difference = difference - 1.0; + } else if (difference < -0.5) { + difference = 1.0 + difference; + } + _toggleTime = currentTime - MAX_TOGGLE_DURATION * fabs(difference); + _toggle = MAX(-1.0, MIN(1.0, -difference)); + } + } + } else if (_autoscroll && !_dragging) { + //autoscroll goes backwards from what you'd expect, for historical reasons + self.scrollOffset = [self clampedOffset:_scrollOffset - delta * _autoscroll]; + } else if (fabs(_toggle) > FLOAT_ERROR_MARGIN) { + NSTimeInterval toggleDuration = _startVelocity? MIN(1.0, MAX(0.0, 1.0 / fabs(_startVelocity))): 1.0; + toggleDuration = MIN_TOGGLE_DURATION + (MAX_TOGGLE_DURATION - MIN_TOGGLE_DURATION) * toggleDuration; + NSTimeInterval time = MIN(1.0, (currentTime - _toggleTime) / toggleDuration); + delta = [self easeInOut:time]; + _toggle = (_toggle < 0.0)? (delta - 1.0): (1.0 - delta); + [self didScroll]; + } else if (!_autoscroll) { + [self stopAnimation]; + } + + [self popAnimationState]; +} + +- (CGFloat)easeInOut:(CGFloat)time +{ + return (time < 0.5)? 0.5 * pow(time * 2.0, 3.0): 0.5 * pow(time * 2.0 - 2.0, 3.0) + 1.0; +} + +- (CGFloat)decelerationDistance +{ + CGFloat acceleration = -_startVelocity * DECELERATION_MULTIPLIER * (1.0 - _decelerationRate); + return -pow(_startVelocity, 2.0) / (2.0 * acceleration); +} + +- (void)startDecelerating +{ + CGFloat distance = [self decelerationDistance]; + _startOffset = _scrollOffset; + _endOffset = _startOffset + distance; + if (_pagingEnabled) { + if (distance > 0.0) { + _endOffset = ceil(_startOffset); + } else { + _endOffset = floor(_startOffset); + } + } else if (_stopAtItemBoundary) { + if (distance > 0.0) { + _endOffset = ceil(_endOffset); + } else { + _endOffset = floor(_endOffset); + } + } + if (!_wrapEnabled) { + if (_bounces) { + _endOffset = MAX(-_bounceDistance, MIN(_numberOfItems - 1.0 + _bounceDistance, _endOffset)); + } else { + _endOffset = [self clampedOffset:_endOffset]; + } + } + distance = _endOffset - _startOffset; + + _startTime = CACurrentMediaTime(); + _scrollDuration = fabs(distance) / fabs(0.5 * _startVelocity); + + if (distance != 0.0) { + _decelerating = YES; + [self startAnimation]; + } +} + + +- (BOOL)shouldDecelerate +{ + return (fabs(_startVelocity) > SCROLL_SPEED_THRESHOLD) && + (fabs([self decelerationDistance]) > DECELERATE_THRESHOLD); +} + +- (BOOL)shouldScroll +{ + return (fabs(_startVelocity) > SCROLL_SPEED_THRESHOLD) && + (fabs(_scrollOffset - self.currentItemIndex) > SCROLL_DISTANCE_THRESHOLD); +} + +- (void)didScroll +{ + if (_wrapEnabled || !_bounces) { + _scrollOffset = [self clampedOffset:_scrollOffset]; + } else { + CGFloat min = -_bounceDistance; + CGFloat max = MAX(_numberOfItems - 1, 0.0) + _bounceDistance; + if (_scrollOffset < min) { + _scrollOffset = min; + _startVelocity = 0.0; + } else if (_scrollOffset > max) { + _scrollOffset = max; + _startVelocity = 0.0; + } + } + + //check if index has changed + NSInteger difference = [self minScrollDistanceFromIndex:self.currentItemIndex toIndex:self.previousItemIndex]; + if (difference) { + _toggleTime = CACurrentMediaTime(); + _toggle = MAX(-1, MIN(1, difference)); + + [self startAnimation]; + } + + [self loadUnloadViews]; + [self transformItemViews]; + + //notify delegate of offset change + if (fabs(_scrollOffset - _previousScrollOffset) > 0.000001) + { + [self pushAnimationState:YES]; + [_delegate sliderNeighborDidScroll:self]; + [self popAnimationState]; + } + + //notify delegate of index change + if (_previousItemIndex != self.currentItemIndex) + { + [self pushAnimationState:YES]; + if([_delegate respondsToSelector:@selector(sliderNeighborCurrentItemIndexDidChange:from:to:)]) { + [_delegate sliderNeighborCurrentItemIndexDidChange:self from:_previousItemIndex to:self.currentItemIndex]; + } + [self popAnimationState]; + } + + //update previous index + _previousScrollOffset = _scrollOffset; + _previousItemIndex = self.currentItemIndex; + [self.indicator setCurrentPoint:self.currentItemIndex]; +} + +- (void)startAnimation +{ + if (!_timer) { + self.timer = [NSTimer timerWithTimeInterval:1.0/60.0 + target:self + selector:@selector(step) + userInfo:nil + repeats:YES]; + + [[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode]; + [[NSRunLoop mainRunLoop] addTimer:_timer forMode:UITrackingRunLoopMode]; + + } +} + +- (void)stopAnimation +{ + [_timer invalidate]; + _timer = nil; +} + +#pragma mark - +#pragma mark view management + +- (NSArray *)indexesForVisibleItems +{ + return [[_itemViews allKeys] sortedArrayUsingSelector:@selector(compare:)]; +} + +- (NSArray *)visibleItemViews +{ + NSArray *indexes = [self indexesForVisibleItems]; + return [_itemViews objectsForKeys:indexes notFoundMarker:[NSNull null]]; +} + +- (UIView *)itemViewAtIndex:(NSInteger)index +{ + return _itemViews[@(index)]; +} + +- (UIView *)currentItemView +{ + return [self itemViewAtIndex:self.currentItemIndex]; +} + +- (NSInteger)indexOfItemView:(UIView *)view +{ + NSInteger index = [[_itemViews allValues] indexOfObject:view]; + if (index != NSNotFound) { + return [[_itemViews allKeys][index] integerValue]; + } + return NSNotFound; +} + +- (NSInteger)indexOfItemViewOrSubview:(UIView *)view +{ + NSInteger index = [self indexOfItemView:view]; + if (index == NSNotFound && view != nil && view != _contentView) { + return [self indexOfItemViewOrSubview:view.superview]; + } + return index; +} + +- (UIView *)itemViewAtPoint:(CGPoint)point +{ + for (UIView *view in [[[_itemViews allValues] sortedArrayUsingFunction:(NSInteger (*)(id, id, void *))sliderNeighorCompareViewDepth context:(__bridge void *)self] reverseObjectEnumerator]) + { + if ([view.superview.layer hitTest:point]) { + return view; + } + } + return nil; +} + +- (void)setItemView:(UIView *)view forIndex:(NSInteger)index +{ + _itemViews[@(index)] = view; +} + +- (void)removeViewAtIndex:(NSInteger)index +{ + NSMutableDictionary *newItemViews = [NSMutableDictionary dictionaryWithCapacity:[_itemViews count] - 1]; + for (NSNumber *number in [self indexesForVisibleItems]) + { + NSInteger i = [number integerValue]; + if (i < index) { + newItemViews[number] = _itemViews[number]; + } else if (i > index) { + newItemViews[@(i - 1)] = _itemViews[number]; + } + } + self.itemViews = newItemViews; + [self.indicator setPointCount:self.itemViews.count]; +} + +- (void)insertView:(UIView *)view atIndex:(NSInteger)index +{ + NSMutableDictionary *newItemViews = [NSMutableDictionary dictionaryWithCapacity:[_itemViews count] + 1]; + for (NSNumber *number in [self indexesForVisibleItems]) + { + NSInteger i = [number integerValue]; + if (i < index) { + newItemViews[number] = _itemViews[number]; + } else { + newItemViews[@(i + 1)] = _itemViews[number]; + } + } + if (view) { + [self setItemView:view forIndex:index]; + } + self.itemViews = newItemViews; + + [self.indicator setPointCount:self.itemViews.count]; +} + +#pragma mark - +#pragma mark View loading + +- (UIView *)loadViewAtIndex:(NSInteger)index withContainerView:(UIView *)containerView +{ + [self pushAnimationState:NO]; + + UIView *view = nil; + if (index < 0) { + view = [_dataSource sliderNeighbor:self placeholderViewAtIndex:(NSInteger)(ceil((CGFloat)_numberOfPlaceholdersToShow/2.0)) + index reusingView:[self dequeuePlaceholderView]]; + } else if (index >= _numberOfItems) { + view = [_dataSource sliderNeighbor:self placeholderViewAtIndex:_numberOfPlaceholdersToShow/2.0 + index - _numberOfItems reusingView:[self dequeuePlaceholderView]]; + } else { + view = [_dataSource sliderNeighbor:self viewForItemAtIndex:index reusingView:[self dequeueItemView]]; + } + + if (view == nil) { + view = [[UIView alloc] init]; + } + + [self setItemView:view forIndex:index]; + if (containerView) { + //get old item view + UIView *oldItemView = [containerView.subviews lastObject]; + if (index < 0 || index >= _numberOfItems) { + [self queuePlaceholderView:oldItemView]; + } else { + [self queueItemView:oldItemView]; + } + + //set container frame + CGRect frame = containerView.bounds; + if(_vertical) { + frame.size.width = view.frame.size.width; + frame.size.height = MIN(_itemWidth, view.frame.size.height); + } else { + frame.size.width = MIN(_itemWidth, view.frame.size.width); + frame.size.height = view.frame.size.height; + } + containerView.bounds = frame; + + //set view frame + frame = view.frame; + frame.origin.x = (containerView.bounds.size.width - frame.size.width) / 2.0; + frame.origin.y = (containerView.bounds.size.height - frame.size.height) / 2.0; + view.frame = frame; + + //switch views + [oldItemView removeFromSuperview]; + [containerView addSubview:view]; + } else { + [_contentView addSubview:[self containView:view]]; + } + view.superview.layer.opacity = 0.0; + [self transformItemView:view atIndex:index]; + + [self popAnimationState]; + + return view; +} + +- (UIView *)loadViewAtIndex:(NSInteger)index +{ + return [self loadViewAtIndex:index withContainerView:nil]; +} + +- (void)loadUnloadViews +{ + //set item width + [self updateItemWidth]; + + //update number of visible items + [self updateNumberOfVisibleItems]; + + //calculate visible view indices + NSMutableSet *visibleIndices = [NSMutableSet setWithCapacity:_numberOfVisibleItems]; + NSInteger min = -(NSInteger)(ceil((CGFloat)_numberOfPlaceholdersToShow/2.0)); + NSInteger max = _numberOfItems - 1 + _numberOfPlaceholdersToShow/2; + NSInteger offset = self.currentItemIndex - _numberOfVisibleItems/2; + if (!_wrapEnabled) { + offset = MAX(min, MIN(max - _numberOfVisibleItems + 1, offset)); + } + for (NSInteger i = 0; i < _numberOfVisibleItems; i++) + { + NSInteger index = i + offset; + if (_wrapEnabled) { + index = [self clampedIndex:index]; + } + CGFloat alpha = [self alphaForItemWithOffset:[self offsetForItemAtIndex:index]]; + if (alpha) { + //only add views with alpha > 0 + [visibleIndices addObject:@(index)]; + } + } + + //remove offscreen views + for (NSNumber *number in [_itemViews allKeys]) + { + if (![visibleIndices containsObject:number]) { + UIView *view = _itemViews[number]; + if ([number integerValue] < 0 || [number integerValue] >= _numberOfItems) { + [self queuePlaceholderView:view]; + } else { + [self queueItemView:view]; + } + [view.superview removeFromSuperview]; + [(NSMutableDictionary *)_itemViews removeObjectForKey:number]; + } + } + + //add onscreen views + for (NSNumber *number in visibleIndices) + { + UIView *view = _itemViews[number]; + if (view == nil) { + [self loadViewAtIndex:[number integerValue]]; + } + } +} + +#pragma mark View queing + +- (void)queueItemView:(UIView *)view +{ + if (view) { + [_itemViewPool addObject:view]; + } +} + +- (void)queuePlaceholderView:(UIView *)view +{ + if (view) { + [_placeholderViewPool addObject:view]; + } +} + +- (UIView *)dequeueItemView +{ + UIView *view = [_itemViewPool anyObject]; + if (view) { + [_itemViewPool removeObject:view]; + } + return view; +} + +- (UIView *)dequeuePlaceholderView +{ + UIView *view = [_placeholderViewPool anyObject]; + if (view) { + [_placeholderViewPool removeObject:view]; + } + return view; +} + +- (void)setIndicator:(WXIndicatorView *)indicator +{ + _indicator = indicator; + [_indicator setPointCount:self.numberOfItems]; + [_indicator setCurrentPoint:[self currentItemIndex]]; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + _contentView.frame = self.bounds; + [self layOutItemViews]; +} + +@end + +@implementation NSObject (WXSliderNeighborView) + +- (NSUInteger)numberOfPlaceholdersInSliderNeighborView:(__unused WXSliderNeighborView *)sliderNeighbor { return 0; } +- (void)sliderNeighborWillBeginScrollingAnimation:(__unused WXSliderNeighborView *)sliderNeighbor {} +- (void)sliderNeighborDidEndScrollingAnimation:(__unused WXSliderNeighborView *)sliderNeighbor {} +- (void)sliderNeighborDidScroll:(__unused WXSliderNeighborView *)sliderNeighbor {} + +- (void)sliderNeighborCurrentItemIndexDidChange:(__unused WXSliderNeighborView *)sliderNeighbor {} +- (void)sliderNeighborWillBeginDragging:(__unused WXSliderNeighborView *)sliderNeighbor {} +- (void)sliderNeighborDidEndDragging:(__unused WXSliderNeighborView *)sliderNeighbor willDecelerate:(__unused BOOL)decelerate {} +- (void)sliderNeighborWillBeginDecelerating:(__unused WXSliderNeighborView *)sliderNeighbor {} +- (void)sliderNeighborDidEndDecelerating:(__unused WXSliderNeighborView *)sliderNeighbor {} + +- (BOOL)sliderNeighbor:(__unused WXSliderNeighborView *)sliderNeighbor shouldSelectItemAtIndex:(__unused NSInteger)index { return YES; } +- (void)sliderNeighbor:(__unused WXSliderNeighborView *)sliderNeighbor didSelectItemAtIndex:(__unused NSInteger)index {} + +- (CGFloat)sliderNeighborItemWidth:(__unused WXSliderNeighborView *)sliderNeighbor { return 0; } +- (CATransform3D)sliderNeighbor:(__unused WXSliderNeighborView *)sliderNeighbor + itemTransformForOffset:(__unused CGFloat)offset + baseTransform:(CATransform3D)transform { return transform; } +- (CGFloat)sliderNeighbor:(__unused WXSliderNeighborView *)sliderNeighbor + valueForOption:(__unused WXSliderNeighborOption)option + withDefault:(CGFloat)value { return value; } + +@end + +@interface WXSliderNeighborComponent () <WXIndicatorComponentDelegate>{ + WXPixelType neighborSpace; + CGFloat neighborAlpha; + CGFloat neighborScale; + CGFloat currentItemScale; +} + +@property (nonatomic, strong) WXSliderNeighborView *sliderView; +@property (nonatomic, assign) BOOL autoPlay; +@property (nonatomic, assign) NSUInteger interval; +@property (nonatomic, assign) NSInteger index; +@property (nonatomic, strong) NSMutableArray *items; +@property (nonatomic, strong) NSTimer *autoTimer; +@property (nonatomic, assign) BOOL sliderChangeEvent; +@property (nonatomic, assign) NSInteger currentIndex; +@property (nonatomic) CGRect itemRect; +@property (nonatomic, assign) BOOL scrollable; + +@end + +#define DEFAULT_NEIGHBOR_ITEM_SCALE 0.8 +#define DEFAULT_CURRENT_ITEM_SCALE 0.9 +#define DEFAULT_NEIGHBOR_ALPHA 0.6 +#define DEFAULT_ANIMATION_DURATION 0.3 +#define DEFAULT_NEIGHBOR_SPACE 25 + + +@implementation WXSliderNeighborComponent + +- (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; + _interval = 3000; + _items = [NSMutableArray array]; + _itemRect = CGRectNull; + self->neighborAlpha = DEFAULT_NEIGHBOR_ALPHA; + self->neighborScale = DEFAULT_NEIGHBOR_ITEM_SCALE; + self->currentItemScale = DEFAULT_CURRENT_ITEM_SCALE; + self->neighborSpace = [WXConvert WXPixelType:@(DEFAULT_NEIGHBOR_SPACE) scaleFactor:self.weexInstance.pixelScaleFactor]; + _scrollable = YES; + [self setAttributes:attributes]; + + _scrollable = attributes[@"scrollable"] ? [WXConvert BOOL:attributes[@"scrollable"]] : 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; +} + + +#pragma mark life circle + +- (UIView *)loadView +{ + _sliderView = [[WXSliderNeighborView alloc] init]; + return _sliderView; +} + +- (void)dealloc +{ + _sliderView.delegate = nil; + _sliderView.dataSource = nil; + if (_autoPlay) { + [self _stopAutoPlayTimer]; + } + _sliderView = nil; + [self.items removeAllObjects]; +} + +- (void)viewDidUnload +{ + [self.items removeAllObjects]; +} + +- (void)viewDidLoad +{ + _sliderView = (WXSliderNeighborView *)self.view; + _sliderView.delegate = self; + _sliderView.dataSource = self; + _sliderView.contentView.clipsToBounds = YES; + _sliderView.scrollEnabled = _scrollable; + UIAccessibilityTraits traits = UIAccessibilityTraitAdjustable; + + if (_autoPlay) { + traits |= UIAccessibilityTraitUpdatesFrequently; + [self _startAutoPlayTimer]; + } else { + [self _stopAutoPlayTimer]; + } + _sliderView.accessibilityTraits = traits; +} + +- (void)layoutDidFinish { + + [_sliderView setCurrentItemIndex:_index]; +} + +- (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; + + WXSliderNeighborView *sliderView = (WXSliderNeighborView *)self.view; + if ([view isKindOfClass:[WXIndicatorView class]]) { + ((WXIndicatorComponent *)subcomponent).delegate = self; + [sliderView addSubview:view]; + return; + } + + if(index < 0) { + [self.items addObject:view]; + } else { + [self.items insertObject:view atIndex:index]; + } + + subcomponent.isViewFrameSyncWithCalculated = NO; + + if (index == -1) { + [sliderView insertView:view atIndex:index]; + } else { + NSInteger offset = 0; + for (int i = 0; i < [self.items count]; ++i) { + if (index == i) break; + + if ([self.items[i] isKindOfClass:[WXIndicatorView class]]) { + offset++; + } + } + [sliderView insertView:view atIndex:index - offset]; + } + + [sliderView reloadData]; + } +} + +#pragma mark attributes update +- (void)setAttributes:(NSDictionary *)attributes +{ + if (attributes[@"index"]) { + _index = [attributes[@"index"] integerValue]; + _currentIndex = _index; + } + 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[@"neighborScale"]) { + [self setNeighborScale:attributes]; + } + if (attributes[@"currentItemScale"]) { + [self setCurrentItemScale:attributes]; + } + if (attributes[@"neighborAlpha"]) { + [self setNeighborAlpha:attributes]; + } + if (attributes[@"neighborSpace"]) { + [self setNeighborSpace:attributes]; + } + if (attributes[@"scrollable"]) { + _scrollable = attributes[@"scrollable"] ? [WXConvert BOOL:attributes[@"scrollable"]] : YES; + ((WXSliderNeighborView *)self.view).scrollEnabled = _scrollable; + } +} + +- (void)updateAttributes:(NSDictionary *)attributes +{ + [self setAttributes:attributes]; + [self.sliderView setCurrentItemIndex:_index]; + [self updateSliderPage:YES]; +} + +#pragma mark styles update +- (void)updateStyles:(NSDictionary *)styles { + +} + +#pragma mark event +- (void)addEvent:(NSString *)eventName +{ + if ([eventName isEqualToString:@"change"]) { + _sliderChangeEvent = YES; + } +} + +- (void)removeEvent:(NSString *)eventName +{ + if ([eventName isEqualToString:@"change"]) { + _sliderChangeEvent = NO; + } +} + +#pragma mark WXIndicatorComponentDelegate Methods + +- (void)setIndicatorView:(WXIndicatorView *)indicatorView +{ + NSAssert(_sliderView, @""); + [_sliderView setIndicator:indicatorView]; +} + +- (void)setNeighborSpace:(NSDictionary *)attributes{ + if(attributes[@"neighborSpace"]) { + self->neighborSpace = [WXConvert WXPixelType:attributes[@"neighborSpace"] scaleFactor:self.weexInstance.pixelScaleFactor]; + } +} + +- (void)setNeighborAlpha:(NSDictionary *)attributes { + if (attributes[@"neighborAlpha"]) { + self->neighborAlpha = [WXConvert CGFloat:attributes[@"neighborAlpha"]]; + self->neighborAlpha = self->neighborAlpha >= 0 ? self->neighborAlpha : 0; + self->neighborAlpha = self->neighborAlpha <= 1 ? self->neighborAlpha: 1; + } +} + +- (void)setCurrentItemScale:(NSDictionary *)attributes { + if (attributes[@"currentItemScale"]) { + self->currentItemScale = [WXConvert CGFloat:attributes[@"currentItemScale"]]; + self->currentItemScale = self->currentItemScale >= 0 ? self->currentItemScale : 0; + self->currentItemScale = self->currentItemScale <= 1 ? self->currentItemScale: 1; + } +} + +- (void)setNeighborScale:(NSDictionary *)attributes +{ + if (attributes[@"neighborScale"]) { + self->neighborScale = [WXConvert CGFloat:attributes[@"neighborScale"]]; + self->neighborScale = self->neighborScale >= 0? self->neighborScale : 0; + self->neighborScale = self->neighborScale <= 1? self->neighborScale :1; + } +} + +#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 +{ + WXSliderNeighborView *sliderNeighborView = (WXSliderNeighborView *)self.view; + + int indicatorCnt = 0; + for (int i = 0; i < [self.items count]; ++i) { + if ([self.items[i] isKindOfClass:[WXIndicatorView class]]) { + indicatorCnt++; + } + } + + self.currentIndex ++; + if (self.currentIndex >= self.items.count - indicatorCnt) { + self.currentIndex = 0; + } + + [sliderNeighborView setCurrentItemIndex:self.currentIndex]; +} + +#pragma mark sliderNeighbor Delegate && dataSource + +- (NSInteger)numberOfItemsInSliderNeighbor:(WXSliderNeighborView *)sliderNeighbor { + return [self.items count]; +} + +- (NSInteger)numberOfPlaceholdersInsliderNeighbor:(WXSliderNeighborView *)sliderNeighbor { + return 2; +} + +- (UIView *)sliderNeighbor:(WXSliderNeighborView *)sliderNeighbor viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view { + + if (!view) { + if (index < [self.items count]) { + view = self.items[index]; + }else { + return nil; + } + } else { + view.tag = 1; + } + + if (CGRectIsNull(_itemRect)) { + _itemRect = view.frame; + } + if (index == [self.items count] - 1) { + [self updateSliderPage:NO]; + } + + return view; +} + +- (BOOL)sliderNeighbor:(WXSliderNeighborView *)sliderNeighbor shouldSelectItemAtIndex:(NSInteger)index { + + return YES; +} + +- (UIView *)sliderNeighbor:(WXSliderNeighborView *)sliderNeighbor placeholderViewAtIndex:(NSInteger)index reusingView:(UIView *)view +{ + + return view; +} + +- (void)sliderNeighborDidEndScrollingAnimation:(WXSliderNeighborView *)sliderNeighbor +{ + [self updateSliderPage:YES]; +} + +- (void)sliderNeighbor:(WXSliderNeighborView *)sliderView didScrollToItemAtIndex:(NSInteger)index +{ + self.currentIndex = index; + + if (_sliderChangeEvent) { + [self fireEvent:@"change" params:@{@"index":@(index)} domChanges:@{@"attrs": @{@"index": @(index)}}]; + } +} + +- (void)sliderNeighborWillBeginDragging:(WXSliderNeighborView *)sliderNeighbor +{ + [self _stopAutoPlayTimer]; +} + +- (void)sliderNeighborDidEndDragging:(WXSliderNeighborView *)sliderNeighbor willDecelerate:(BOOL)decelerate { + if (_autoPlay) { + [self _startAutoPlayTimer]; + } +} + +- (void)updateSliderPage:(BOOL)animate +{ + __block UIView * currentView = [self.sliderView itemViewAtIndex:[_sliderView currentItemIndex]]; + __block UIView * lastView = [self.sliderView itemViewAtIndex:[_sliderView lastItemIndex]]; + __block UIView * nextView = [self.sliderView itemViewAtIndex:[_sliderView nextItemIndex]]; + + __block CGAffineTransform transfrom = CGAffineTransformIdentity; + float duration = 0; + __weak typeof(self) weakSelf = self; + if (animate) { + duration = DEFAULT_ANIMATION_DURATION; + } + + [UIView animateWithDuration:duration animations:^{ + __strong typeof(self) strongSelf = weakSelf; + if (strongSelf) { + currentView.alpha = 1.0; + if (fabs(strongSelf->currentItemScale) > CGFLOAT_MIN) { + transfrom = CGAffineTransformConcat(transfrom,CGAffineTransformMakeScale(strongSelf->currentItemScale, strongSelf->currentItemScale)); + } + currentView.transform = transfrom; + transfrom = CGAffineTransformIdentity; + if (fabs(strongSelf->neighborScale) <= CGFLOAT_MIN) { + strongSelf->neighborScale = DEFAULT_NEIGHBOR_ITEM_SCALE; + } + + CGFloat tx = 0.5*_itemRect.size.width*((1-self->neighborScale)+(1-self->currentItemScale))-self->neighborSpace; + transfrom = CGAffineTransformConcat(transfrom, CGAffineTransformMakeScale(strongSelf->neighborScale, strongSelf->neighborScale)); + nextView.transform = CGAffineTransformConcat(transfrom, CGAffineTransformMakeTranslation(-tx, 0)); + lastView.transform = CGAffineTransformConcat(transfrom, CGAffineTransformMakeTranslation(tx, 0)); + lastView.alpha = strongSelf->neighborAlpha; + nextView.alpha = strongSelf->neighborAlpha; + } + }]; +} + +@end
http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Component/WXSwitchComponent.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXSwitchComponent.m b/ios/sdk/WeexSDK/Sources/Component/WXSwitchComponent.m deleted file mode 100644 index f5f9ce2..0000000 --- a/ios/sdk/WeexSDK/Sources/Component/WXSwitchComponent.m +++ /dev/null @@ -1,151 +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 "WXSwitchComponent.h" -#import "WXConvert.h" -#import "WXComponent_internal.h" - -@interface WXSwitchView : UISwitch - -@end - -@implementation WXSwitchView - -@end - -@interface WXSwitchComponent() - -@property (nonatomic, strong) WXSwitchView *switchView; -@property (nonatomic, assign) BOOL changeEvent; -@property (nonatomic, assign) BOOL checked; -@property (nonatomic, assign) BOOL disabled; - -//Background color when the switch is turned on. -@property (nonatomic, strong) UIColor *onTintColor; - - -//Color of the foreground switch grip. -@property (nonatomic, strong) UIColor *thumbTintColor; - -//Border color and background color on Android when the switch is turned off -@property (nonatomic, strong) UIColor *tintColor; - -@end - -@implementation WXSwitchComponent - -- (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]) { - _checked = attributes[@"checked"] ? [WXConvert BOOL:attributes[@"checked"]] : NO; - _disabled = attributes[@"disabled"] ? [WXConvert BOOL:attributes[@"disabled"]] : NO; - - if(attributes[@"onTintColor"]){ - _onTintColor = [WXConvert UIColor:attributes[@"onTintColor"]]; - } - - if(attributes[@"thumbTintColor"]){ - _thumbTintColor = [WXConvert UIColor:attributes[@"thumbTintColor"]]; - } - - if(attributes[@"tintColor"]){ - _tintColor = [WXConvert UIColor:attributes[@"tintColor"]]; - } - - self.cssNode->style.dimensions[CSS_WIDTH] = 51; - self.cssNode->style.dimensions[CSS_HEIGHT] = 31; - } - return self; -} - -- (UIView *)loadView -{ - return [[WXSwitchView alloc] init]; -} - -- (void)viewDidLoad -{ - _switchView = (WXSwitchView *)self.view; - _switchView.isAccessibilityElement = YES; - [_switchView setOn:_checked animated:YES]; - [_switchView setEnabled:!_disabled]; - [_switchView addTarget:self action:@selector(checkChanged) forControlEvents:UIControlEventValueChanged]; - - if(_onTintColor){ - _switchView.onTintColor = _onTintColor; - } - - if(_tintColor){ - _switchView.tintColor = _tintColor; - } - - if(_thumbTintColor){ - _switchView.thumbTintColor = _thumbTintColor; - } -} - -- (void)addEvent:(NSString *)eventName -{ - if ([eventName isEqualToString:@"change"]) { - _changeEvent = YES; - } -} - -- (void)removeEvent:(NSString *)eventName -{ - if ([eventName isEqualToString:@"change"]) { - _changeEvent = NO; - } -} - -- (void)updateAttributes:(NSDictionary *)attributes -{ - if (attributes[@"checked"]) { - _checked = [WXConvert BOOL:attributes[@"checked"]]; - [_switchView setOn:_checked animated:YES]; - } - else if (attributes[@"disabled"]) { - _disabled = [WXConvert BOOL:attributes[@"disabled"]]; - [_switchView setEnabled:!_disabled]; - } - - if(attributes[@"onTintColor"]){ - _onTintColor = [WXConvert UIColor:attributes[@"onTintColor"]]; - _switchView.onTintColor = _onTintColor; - } - - if(attributes[@"thumbTintColor"]){ - _thumbTintColor = [WXConvert UIColor:attributes[@"thumbTintColor"]]; - _switchView.thumbTintColor = _thumbTintColor; - } - - if(attributes[@"tintColor"]){ - _tintColor = [WXConvert UIColor:attributes[@"tintColor"]]; - _switchView.tintColor = _tintColor; - } -} - -- (void)checkChanged -{ - if (_changeEvent) { - [self fireEvent:@"change" params:@{@"value":@([_switchView isOn])} domChanges:@{@"attrs": @{@"checked": @([_switchView isOn])}}]; - } -} - -@end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Component/WXSwitchComponent.mm ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXSwitchComponent.mm b/ios/sdk/WeexSDK/Sources/Component/WXSwitchComponent.mm new file mode 100644 index 0000000..16903a0 --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/WXSwitchComponent.mm @@ -0,0 +1,162 @@ +/* + * 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 "WXSwitchComponent.h" +#import "WXConvert.h" +#import "WXComponent_internal.h" +#import "WXComponent+Layout.h" + +@interface WXSwitchView : UISwitch + +@end + +@implementation WXSwitchView + +@end + +@interface WXSwitchComponent() + +@property (nonatomic, strong) WXSwitchView *switchView; +@property (nonatomic, assign) BOOL changeEvent; +@property (nonatomic, assign) BOOL checked; +@property (nonatomic, assign) BOOL disabled; + +//Background color when the switch is turned on. +@property (nonatomic, strong) UIColor *onTintColor; + + +//Color of the foreground switch grip. +@property (nonatomic, strong) UIColor *thumbTintColor; + +//Border color and background color on Android when the switch is turned off +@property (nonatomic, strong) UIColor *tintColor; + +@end + +@implementation WXSwitchComponent + +- (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]) { + _checked = attributes[@"checked"] ? [WXConvert BOOL:attributes[@"checked"]] : NO; + _disabled = attributes[@"disabled"] ? [WXConvert BOOL:attributes[@"disabled"]] : NO; + + if(attributes[@"onTintColor"]){ + _onTintColor = [WXConvert UIColor:attributes[@"onTintColor"]]; + } + + if(attributes[@"thumbTintColor"]){ + _thumbTintColor = [WXConvert UIColor:attributes[@"thumbTintColor"]]; + } + + if(attributes[@"tintColor"]){ + _tintColor = [WXConvert UIColor:attributes[@"tintColor"]]; + } +//#ifndef USE_FLEX + if (![WXComponent isUseFlex]) + { + self.cssNode->style.dimensions[CSS_WIDTH] = 51; + self.cssNode->style.dimensions[CSS_HEIGHT] = 31; + } +//#else + else + { + self.flexCssNode->setStyleWidth(51,NO); + self.flexCssNode->setStyleHeight(31); + } +//#endif + } + return self; +} + +- (UIView *)loadView +{ + return [[WXSwitchView alloc] init]; +} + +- (void)viewDidLoad +{ + _switchView = (WXSwitchView *)self.view; + _switchView.isAccessibilityElement = YES; + [_switchView setOn:_checked animated:YES]; + [_switchView setEnabled:!_disabled]; + [_switchView addTarget:self action:@selector(checkChanged) forControlEvents:UIControlEventValueChanged]; + + if(_onTintColor){ + _switchView.onTintColor = _onTintColor; + } + + if(_tintColor){ + _switchView.tintColor = _tintColor; + } + + if(_thumbTintColor){ + _switchView.thumbTintColor = _thumbTintColor; + } +} + +- (void)addEvent:(NSString *)eventName +{ + if ([eventName isEqualToString:@"change"]) { + _changeEvent = YES; + } +} + +- (void)removeEvent:(NSString *)eventName +{ + if ([eventName isEqualToString:@"change"]) { + _changeEvent = NO; + } +} + +- (void)updateAttributes:(NSDictionary *)attributes +{ + if (attributes[@"checked"]) { + _checked = [WXConvert BOOL:attributes[@"checked"]]; + [_switchView setOn:_checked animated:YES]; + } + else if (attributes[@"disabled"]) { + _disabled = [WXConvert BOOL:attributes[@"disabled"]]; + [_switchView setEnabled:!_disabled]; + } + + if(attributes[@"onTintColor"]){ + _onTintColor = [WXConvert UIColor:attributes[@"onTintColor"]]; + _switchView.onTintColor = _onTintColor; + } + + if(attributes[@"thumbTintColor"]){ + _thumbTintColor = [WXConvert UIColor:attributes[@"thumbTintColor"]]; + _switchView.thumbTintColor = _thumbTintColor; + } + + if(attributes[@"tintColor"]){ + _tintColor = [WXConvert UIColor:attributes[@"tintColor"]]; + _switchView.tintColor = _tintColor; + } +} + +- (void)checkChanged +{ + if (_changeEvent) { + [self fireEvent:@"change" params:@{@"value":@([_switchView isOn])} domChanges:@{@"attrs": @{@"checked": @([_switchView isOn])}}]; + } +} + +@end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Component/WXTextAreaComponent.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXTextAreaComponent.m b/ios/sdk/WeexSDK/Sources/Component/WXTextAreaComponent.m deleted file mode 100644 index 24ee17f..0000000 --- a/ios/sdk/WeexSDK/Sources/Component/WXTextAreaComponent.m +++ /dev/null @@ -1,233 +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 "WXTextAreaComponent.h" -#import "WXUtility.h" -#import "WXComponent+Layout.h" -#import "WXComponent_internal.h" - -#define CorrectX 4 //textview fill text 4 pixel from left. so placeholderlabel have 4 pixel too -#define CorrectY 8 // textview fill text 8 pixel from top -typedef UITextView WXTextAreaView; - -@interface WXTextAreaComponent() - -@property (nonatomic, strong) WXTextAreaView *textView; -@property (nonatomic) NSUInteger rows; - -@end - -@implementation WXTextAreaComponent { - UIEdgeInsets _border; - UIEdgeInsets _padding; -} - --(void)viewDidLoad -{ - _padding = UIEdgeInsetsZero; - _border = UIEdgeInsetsZero; - if (self.placeholderString) { - self.placeHolderLabel = [[UILabel alloc] init]; - self.placeHolderLabel.numberOfLines = 0; - [_textView addSubview:self.placeHolderLabel]; - } - // default placeholder hide from voice over - self.placeHolderLabel.isAccessibilityElement = NO; - _textView.isAccessibilityElement = YES; - _textView.delegate = self; - [_textView setNeedsDisplay]; - [_textView setClipsToBounds:YES]; - [super viewDidLoad]; -} - -- (void)viewWillUnload -{ - _textView = nil; -} - -- (UIView *)loadView -{ - _textView = [[WXTextAreaView alloc] init]; - return _textView; -} - -#pragma mark measure frame -- (CGSize (^)(CGSize))measureBlock -{ - __weak typeof(self) weakSelf = self; - return ^CGSize (CGSize constrainedSize) { - - CGSize computedSize = [[[NSString alloc] init]sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:weakSelf.textView.font.pointSize]}]; - computedSize.height = _rows? computedSize.height *weakSelf.rows + (CorrectY + CorrectY/2):0; - if (!isnan(weakSelf.cssNode->style.minDimensions[CSS_WIDTH])) { - computedSize.width = MAX(computedSize.width, weakSelf.cssNode->style.minDimensions[CSS_WIDTH]); - } - - if (!isnan(weakSelf.cssNode->style.maxDimensions[CSS_WIDTH])) { - computedSize.width = MIN(computedSize.width, weakSelf.cssNode->style.maxDimensions[CSS_WIDTH]); - } - - if (!isnan(weakSelf.cssNode->style.minDimensions[CSS_HEIGHT])) { - computedSize.height = MAX(computedSize.height, weakSelf.cssNode->style.minDimensions[CSS_HEIGHT]); - } - - if (!isnan(weakSelf.cssNode->style.maxDimensions[CSS_HEIGHT])) { - computedSize.height = MIN(computedSize.height, weakSelf.cssNode->style.maxDimensions[CSS_HEIGHT]); - } - - return (CGSize) { - WXCeilPixelValue(computedSize.width), - WXCeilPixelValue(computedSize.height) - }; - }; -} - -#pragma mark -Overwrite method --(NSString *)text -{ - return _textView.text; -} - -- (void)setText:(NSString *)text -{ - _textView.text = text; - if ([text length] >0) { - self.placeHolderLabel.text = @""; - } -} - --(void)setTextColor:(UIColor *)color -{ - [_textView setTextColor:color]; -} - --(void)setTextAlignment:(NSTextAlignment)textAlignForStyle -{ - [_textView setTextAlignment:textAlignForStyle]; -} - --(void)setUserInteractionEnabled:(BOOL)userInteractionEnabled -{ - [_textView setUserInteractionEnabled:userInteractionEnabled]; -} - --(void)setEnabled:(BOOL)enabled -{ - _textView.editable = enabled; - _textView.selectable = enabled; -} - --(void)setReturnKeyType:(UIReturnKeyType)returnKeyType -{ - [_textView setReturnKeyType:returnKeyType]; -} - --(void)setInputAccessoryView:(UIView *)inputAccessoryView -{ - [_textView setInputAccessoryView:inputAccessoryView]; -} - --(void)setEditSelectionRange:(NSInteger)selectionStart selectionEnd:(NSInteger)selectionEnd -{ - [self.textView becomeFirstResponder]; - UITextPosition *startPos = [self.textView positionFromPosition:self.textView.beginningOfDocument offset:selectionStart]; - UITextPosition *endPos = [self.textView positionFromPosition:self.textView.beginningOfDocument offset:selectionEnd]; - UITextRange *textRange = [self.textView textRangeFromPosition:startPos - toPosition:endPos]; - self.textView.selectedTextRange = textRange; -} - --(NSDictionary *)getEditSelectionRange -{ - NSInteger selectionStart = [self.textView offsetFromPosition:self.textView.beginningOfDocument toPosition:self.textView.selectedTextRange.start]; - NSInteger selectionEnd = [self.textView offsetFromPosition:self.textView.beginningOfDocument toPosition:self.textView.selectedTextRange.end]; - NSDictionary *res = @{@"selectionStart":@(selectionStart),@"selectionEnd":@(selectionEnd)}; - return res; -} - --(void)setKeyboardType:(UIKeyboardType)keyboardType -{ - [_textView setKeyboardType:keyboardType]; -} - --(void)setSecureTextEntry:(BOOL)secureTextEntry -{ - [_textView setSecureTextEntry:secureTextEntry]; -} - --(void)setEditPadding:(UIEdgeInsets)padding -{ - _padding = padding; - [self _updateTextContentInset]; -} - --(void)setEditBorder:(UIEdgeInsets)border -{ - _border = border; - [self _updateTextContentInset]; -} - --(void)setAttributedPlaceholder:(NSMutableAttributedString *)attributedString font:(UIFont *)font -{ - if (self.placeholderColor) { - [attributedString addAttribute:NSForegroundColorAttributeName value:self.placeholderColor range:NSMakeRange(0, self.placeholderString.length)]; - [attributedString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, self.placeholderString.length)]; - } - self.placeHolderLabel.backgroundColor = [UIColor clearColor]; - CGRect expectedLabelSize = [attributedString boundingRectWithSize:(CGSize){self.view.frame.size.width, CGFLOAT_MAX} - options:NSStringDrawingUsesLineFragmentOrigin - context:nil]; - - self.placeHolderLabel.clipsToBounds = NO; - CGRect newFrame = self.placeHolderLabel.frame; - newFrame.size.height = ceil(expectedLabelSize.size.height); - newFrame.size.width = _textView.frame.size.width- CorrectX*2; - newFrame.origin.x = CorrectX + _padding.left + _border.left; // the cursor origin.x - self.placeHolderLabel.frame = newFrame; - self.placeHolderLabel.attributedText = attributedString; -} - --(void)setFont:(UIFont *)font -{ - [_textView setFont:font]; -} - --(void)setRows:(NSUInteger)rows -{ - _rows = rows; - [self setNeedsLayout]; -} - -#pragma mark -Private Method -- (void)_updateTextContentInset -{ - [_textView setTextContainerInset:UIEdgeInsetsMake(_padding.top + _border.top, - _padding.left + _border.left, - _padding.bottom + _border.bottom, - _border.right + _border.right)]; - - //when textview update, placeHolderLabel update too - CGRect newFrame = self.placeHolderLabel.frame; - newFrame.size.width = self.textView.frame.size.width - (_padding.left + _border.left) -CorrectX*2; - newFrame.origin.x = CorrectX + _padding.left + _border.left; // the cursor origin.x - newFrame.origin.y = _padding.top + _border.top; - self.placeHolderLabel.frame = newFrame; -} - -@end
