http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Component/WXRefreshComponent.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXRefreshComponent.m b/ios/sdk/WeexSDK/Sources/Component/WXRefreshComponent.m deleted file mode 100644 index 585c9f3..0000000 --- a/ios/sdk/WeexSDK/Sources/Component/WXRefreshComponent.m +++ /dev/null @@ -1,207 +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 "WXRefreshComponent.h" -#import "WXScrollerComponent.h" -#import "WXLoadingIndicator.h" -#import "WXComponent_internal.h" -#import "WXLog.h" - -@interface WXRefreshComponent() - -@property (nonatomic) BOOL displayState; -@property (nonatomic) BOOL initFinished; -@property (nonatomic) BOOL refreshEvent; -@property (nonatomic) BOOL pullingdownEvent; - -@property (nonatomic, weak) WXLoadingIndicator *indicator; - -@end - -@implementation WXRefreshComponent - -- (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) { - _refreshEvent = NO; - _pullingdownEvent = NO; - if (attributes[@"display"]) { - if ([attributes[@"display"] isEqualToString:@"show"]) { - _displayState = YES; - } else if ([attributes[@"display"] isEqualToString:@"hide"]) { - _displayState = NO; - } else { - WXLogError(@""); - } - } - self.cssNode->style.position_type = CSS_POSITION_ABSOLUTE; - } - return self; -} - -- (void)viewDidLoad -{ - _initFinished = YES; - - if (!_displayState) { - [_indicator.view setHidden:YES]; - } -} - -- (void)layoutDidFinish -{ - [self.view setFrame: (CGRect) { - .size = self.calculatedFrame.size, - .origin.x = self.calculatedFrame.origin.x, - .origin.y = self.view.frame.origin.y - CGRectGetHeight(self.calculatedFrame) - }]; -} - -- (void)viewWillUnload -{ - _displayState = NO; - _refreshEvent = NO; - _initFinished = NO; -} - -- (void)refresh -{ - if (!_refreshEvent || _displayState) { - return; - } - [self fireEvent:@"refresh" params:nil]; -} - -- (void)pullingdown:(NSDictionary*)param -{ - if (!_pullingdownEvent) { - return ; - } - - [self fireEvent:@"pullingdown" params:param]; -} - -- (void)_insertSubcomponent:(WXComponent *)subcomponent atIndex:(NSInteger)index -{ - if (subcomponent) { - [super _insertSubcomponent:subcomponent atIndex:index]; - if ([subcomponent isKindOfClass:[WXLoadingIndicator class]]) { - _indicator = (WXLoadingIndicator*)subcomponent; - } - } -} - -- (void)updateAttributes:(NSDictionary *)attributes -{ - if (attributes[@"display"]) { - if ([attributes[@"display"] isEqualToString:@"show"]) { - _displayState = YES; - } else if ([attributes[@"display"] isEqualToString:@"hide"]) { - _displayState = NO; - } else { - WXLogError(@""); - } - [self setDisplay]; - } -} - -- (void)addEvent:(NSString *)eventName -{ - if ([eventName isEqualToString:@"refresh"]) { - _refreshEvent = YES; - } - if ([eventName isEqualToString:@"pullingdown"]) { - _pullingdownEvent = YES; - } -} - -- (void)removeEvent:(NSString *)evetName -{ - if ([evetName isEqualToString:@"refresh"]) { - _refreshEvent = NO; - } - if ([evetName isEqualToString:@"pullingdown"]) { - _pullingdownEvent = NO; - } -} - -- (void)setDisplay -{ - id<WXScrollerProtocol> scrollerProtocol = self.ancestorScroller; - if (scrollerProtocol == nil || !_initFinished) - return; - - if ([scrollerProtocol respondsToSelector:@selector(refreshType)] && - [[scrollerProtocol refreshType] isEqualToString:@"refreshForAppear"]) { - UIEdgeInsets inset = [scrollerProtocol contentInset]; - if (_displayState) { - inset.top = self.calculatedFrame.size.height; - if ([_indicator.view isHidden]) { - [_indicator.view setHidden:NO]; - } - [_indicator start]; - } else { - inset.top = 0; - [_indicator stop]; - } - [scrollerProtocol setContentInset:inset]; - } else { - CGPoint offset = [scrollerProtocol contentOffset]; - if (_displayState) { - offset.y = -self.calculatedFrame.size.height; - if ([_indicator.view isHidden]) { - [_indicator.view setHidden:NO]; - } - [_indicator start]; - } else { - offset.y = 0; - [_indicator stop]; - } - [scrollerProtocol setContentOffset:offset animated:YES]; - } - -} - -- (BOOL)displayState -{ - return _displayState; -} - -- (void)setIndicatorHidden:(BOOL)hidden { - [_indicator.view setHidden:hidden]; - - id<WXScrollerProtocol> scrollerProtocol = self.ancestorScroller; - if (scrollerProtocol == nil || !_initFinished) - return; - if ([scrollerProtocol respondsToSelector:@selector(refreshType)] && - [[scrollerProtocol refreshType] isEqualToString:@"refreshForAppear"]) { - UIEdgeInsets inset = [scrollerProtocol contentInset]; - if (!hidden) { - inset.top = self.calculatedFrame.size.height; - [_indicator start]; - } else { - inset.top = 0; - [_indicator stop]; - } - [scrollerProtocol setContentInset:inset]; - } -} - -@end
http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Component/WXRefreshComponent.mm ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXRefreshComponent.mm b/ios/sdk/WeexSDK/Sources/Component/WXRefreshComponent.mm new file mode 100644 index 0000000..f940e3a --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/WXRefreshComponent.mm @@ -0,0 +1,224 @@ +/* + * 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 "WXRefreshComponent.h" +#import "WXScrollerComponent.h" +#import "WXLoadingIndicator.h" +#import "WXComponent_internal.h" +#import "WXLog.h" +#import "WXComponent+Layout.h" + +@interface WXRefreshComponent() + +@property (nonatomic) BOOL displayState; +@property (nonatomic) BOOL initFinished; +@property (nonatomic) BOOL refreshEvent; +@property (nonatomic) BOOL pullingdownEvent; + +@property (nonatomic, weak) WXLoadingIndicator *indicator; + +@end + +@implementation WXRefreshComponent + +- (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) { + _refreshEvent = NO; + _pullingdownEvent = NO; + if (attributes[@"display"]) { + if ([attributes[@"display"] isEqualToString:@"show"]) { + _displayState = YES; + } else if ([attributes[@"display"] isEqualToString:@"hide"]) { + _displayState = NO; + } else { + WXLogError(@""); + } + } +//#ifndef USE_FLEX + if (![WXComponent isUseFlex]) { + self.cssNode->style.position_type = CSS_POSITION_ABSOLUTE; + } +//#else + else + { + self.flexCssNode->setStylePositionType(WeexCore::kAbsolute); + } + +//#endif + } + return self; +} + +- (void)viewDidLoad +{ + _initFinished = YES; + + if (!_displayState) { + [_indicator.view setHidden:YES]; + } +} + +- (void)layoutDidFinish +{ + [self.view setFrame: (CGRect) { + .size = self.calculatedFrame.size, + .origin.x = self.calculatedFrame.origin.x, + .origin.y = self.view.frame.origin.y - CGRectGetHeight(self.calculatedFrame) + }]; +} + +- (void)viewWillUnload +{ + _displayState = NO; + _refreshEvent = NO; + _initFinished = NO; +} + +- (void)refresh +{ + if (!_refreshEvent || _displayState) { + return; + } +#ifdef DEBUG + WXLogDebug(@"flexLayout -> refreshComponent : refresh ref:%@",self.ref); +#endif + [self fireEvent:@"refresh" params:nil]; +} + +- (void)pullingdown:(NSDictionary*)param +{ + if (!_pullingdownEvent) { + return ; + } +#ifdef DEBUG + WXLogDebug(@"flexLayout -> refreshComponent : pullingdown ,ref:%@",self.ref); +#endif + + [self fireEvent:@"pullingdown" params:param]; +} + +- (void)_insertSubcomponent:(WXComponent *)subcomponent atIndex:(NSInteger)index +{ + if (subcomponent) { + [super _insertSubcomponent:subcomponent atIndex:index]; + if ([subcomponent isKindOfClass:[WXLoadingIndicator class]]) { + _indicator = (WXLoadingIndicator*)subcomponent; + } + } +} + +- (void)updateAttributes:(NSDictionary *)attributes +{ + if (attributes[@"display"]) { + if ([attributes[@"display"] isEqualToString:@"show"]) { + _displayState = YES; + } else if ([attributes[@"display"] isEqualToString:@"hide"]) { + _displayState = NO; + } else { + WXLogError(@""); + } + [self setDisplay]; + } +} + +- (void)addEvent:(NSString *)eventName +{ + if ([eventName isEqualToString:@"refresh"]) { + _refreshEvent = YES; + } + if ([eventName isEqualToString:@"pullingdown"]) { + _pullingdownEvent = YES; + } +} + +- (void)removeEvent:(NSString *)evetName +{ + if ([evetName isEqualToString:@"refresh"]) { + _refreshEvent = NO; + } + if ([evetName isEqualToString:@"pullingdown"]) { + _pullingdownEvent = NO; + } +} + +- (void)setDisplay +{ + id<WXScrollerProtocol> scrollerProtocol = self.ancestorScroller; + if (scrollerProtocol == nil || !_initFinished) + return; + + if ([scrollerProtocol respondsToSelector:@selector(refreshType)] && + [[scrollerProtocol refreshType] isEqualToString:@"refreshForAppear"]) { + UIEdgeInsets inset = [scrollerProtocol contentInset]; + if (_displayState) { + inset.top = self.calculatedFrame.size.height; + if ([_indicator.view isHidden]) { + [_indicator.view setHidden:NO]; + } + [_indicator start]; + } else { + inset.top = 0; + [_indicator stop]; + } + [scrollerProtocol setContentInset:inset]; + } else { + CGPoint offset = [scrollerProtocol contentOffset]; + if (_displayState) { + offset.y = -self.calculatedFrame.size.height; + if ([_indicator.view isHidden]) { + [_indicator.view setHidden:NO]; + } + [_indicator start]; + } else { + offset.y = 0; + [_indicator stop]; + } + [scrollerProtocol setContentOffset:offset animated:YES]; + } + +} + +- (BOOL)displayState +{ + return _displayState; +} + +- (void)setIndicatorHidden:(BOOL)hidden { + [_indicator.view setHidden:hidden]; + + id<WXScrollerProtocol> scrollerProtocol = self.ancestorScroller; + if (scrollerProtocol == nil || !_initFinished) + return; + if ([scrollerProtocol respondsToSelector:@selector(refreshType)] && + [[scrollerProtocol refreshType] isEqualToString:@"refreshForAppear"]) { + UIEdgeInsets inset = [scrollerProtocol contentInset]; + if (!hidden) { + inset.top = self.calculatedFrame.size.height; + [_indicator start]; + } else { + inset.top = 0; + [_indicator stop]; + } + [scrollerProtocol setContentInset:inset]; + } +} + +@end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h index db0c155..8f2b587 100644 --- a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h +++ b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h @@ -28,8 +28,6 @@ @property (nonatomic, assign) CGSize contentSize; -@property (nonatomic, readonly, assign) css_node_t *scrollerCSSNode; - - (NSUInteger)childrenCountForScrollerLayout; - (void)handleAppear; http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.m ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.m b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.m deleted file mode 100644 index 5f197d1..0000000 --- a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.m +++ /dev/null @@ -1,884 +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 "WXScrollerComponent.h" -#import "WXComponent_internal.h" -#import "WXComponent.h" -#import "WXDefine.h" -#import "WXConvert.h" -#import "WXSDKInstance.h" -#import "WXUtility.h" -#import "WXLoadingComponent.h" -#import "WXRefreshComponent.h" -#import "WXConfigCenterProtocol.h" -#import "WXSDKEngine.h" -#import "WXComponent+Events.h" - -@interface WXScrollerComponentView:UIScrollView -@end - -@implementation WXScrollerComponentView -- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch -{ - if ([(id <WXScrollerProtocol>) self.wx_component respondsToSelector:@selector(requestGestureShouldStopPropagation:shouldReceiveTouch:)]) { - return [(id <WXScrollerProtocol>) self.wx_component requestGestureShouldStopPropagation:gestureRecognizer shouldReceiveTouch:touch]; - } - else{ - return YES; - } -} -@end - -@interface WXScrollToTarget : NSObject - -@property (nonatomic, weak) WXComponent *target; -@property (nonatomic, assign) BOOL hasAppear; - -@end - -@implementation WXScrollToTarget - -@end - - -@interface WXScrollerComponent() - -@property (nonatomic, strong) NSMutableArray * stickyArray; -@property (nonatomic, strong) NSMutableArray * listenerArray; -@property (nonatomic, weak) WXRefreshComponent *refreshComponent; -@property (nonatomic, weak) WXLoadingComponent *loadingComponent; - -@end - -@implementation WXScrollerComponent -{ - CGSize _contentSize; - BOOL _listenLoadMore; - BOOL _scrollEvent; - BOOL _scrollStartEvent; - BOOL _scrollEndEvent; - BOOL _isScrolling; - CGFloat _loadMoreOffset; - CGFloat _previousLoadMoreContentHeight; - CGFloat _offsetAccuracy; - CGPoint _lastContentOffset; - CGPoint _lastScrollEventFiredOffset; - BOOL _scrollable; - NSString * _alwaysScrollableVertical; - NSString * _alwaysScrollableHorizontal; - BOOL _bounces; - - // refreshForAppear: load more when refresh component begin appear(if scroll is dragging or decelerating, should delay) - // refreshForWholeVisible: load more until the whole refresh component visible - NSString *_refreshType; - - // vertical & horizontal - WXScrollDirection _scrollDirection; - // left & right & up & down - NSString *_direction; - BOOL _showScrollBar; - BOOL _pagingEnabled; - - BOOL _shouldNotifiAppearDescendantView; - BOOL _shouldRemoveScrollerListener; - - css_node_t *_scrollerCSSNode; - - NSHashTable* _delegates; -} - -WX_EXPORT_METHOD(@selector(resetLoadmore)) - -- (void)resetLoadmore -{ - _previousLoadMoreContentHeight=0; -} - -- (css_node_t *)scrollerCSSNode -{ - return _scrollerCSSNode; -} - -- (void)_insertSubcomponent:(WXComponent *)subcomponent atIndex:(NSInteger)index -{ - [super _insertSubcomponent:subcomponent atIndex:index]; - - if ([subcomponent isKindOfClass:[WXRefreshComponent class]]) { - _refreshComponent = (WXRefreshComponent*)subcomponent; - } - else if ([subcomponent isKindOfClass:[WXLoadingComponent class]]) { - _loadingComponent = (WXLoadingComponent*)subcomponent; - } -} - --(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) { - - _stickyArray = [NSMutableArray array]; - _listenerArray = [NSMutableArray array]; - _scrollEvent = NO; - _scrollStartEvent = NO; - _scrollEndEvent = NO; - _lastScrollEventFiredOffset = CGPointMake(0, 0); - _scrollDirection = attributes[@"scrollDirection"] ? [WXConvert WXScrollDirection:attributes[@"scrollDirection"]] : WXScrollDirectionVertical; - _showScrollBar = attributes[@"showScrollbar"] ? [WXConvert BOOL:attributes[@"showScrollbar"]] : YES; - - if (attributes[@"alwaysScrollableVertical"]) { - _alwaysScrollableVertical = [WXConvert NSString:attributes[@"alwaysScrollableVertical"]]; - } - if (attributes[@"alwaysScrollableHorizontal"]) { - _alwaysScrollableHorizontal = [WXConvert NSString:attributes[@"alwaysScrollableHorizontal"]]; - } - _bounces = attributes[@"bounce"]?[WXConvert BOOL:attributes[@"bounce"]]:YES; - _refreshType = [WXConvert NSString:attributes[@"refreshType"]]?:@"refreshForWholeVisible"; - _pagingEnabled = attributes[@"pagingEnabled"] ? [WXConvert BOOL:attributes[@"pagingEnabled"]] : NO; - _loadMoreOffset = attributes[@"loadmoreoffset"] ? [WXConvert WXPixelType:attributes[@"loadmoreoffset"] scaleFactor:self.weexInstance.pixelScaleFactor] : 0; - _loadmoreretry = attributes[@"loadmoreretry"] ? [WXConvert NSUInteger:attributes[@"loadmoreretry"]] : 0; - _listenLoadMore = [events containsObject:@"loadmore"]; - _scrollable = attributes[@"scrollable"] ? [WXConvert BOOL:attributes[@"scrollable"]] : YES; - _offsetAccuracy = attributes[@"offsetAccuracy"] ? [WXConvert WXPixelType:attributes[@"offsetAccuracy"] scaleFactor:self.weexInstance.pixelScaleFactor] : 0; - _scrollerCSSNode = new_css_node(); - - // let scroller fill the rest space if it is a child component and has no fixed height & width - if (((_scrollDirection == WXScrollDirectionVertical && - isUndefined(self.cssNode->style.dimensions[CSS_HEIGHT])) || - (_scrollDirection == WXScrollDirectionHorizontal && - isUndefined(self.cssNode->style.dimensions[CSS_WIDTH]))) && - self.cssNode->style.flex <= 0.0) { - self.cssNode->style.flex = 1.0; - } - id configCenter = [WXSDKEngine handlerForProtocol:@protocol(WXConfigCenterProtocol)]; - if ([configCenter respondsToSelector:@selector(configForKey:defaultValue:isDefault:)]) { - BOOL shouldNotifiAppearDescendantView = [[configCenter configForKey:@"iOS_weex_ext_config.shouldNotifiAppearDescendantView" defaultValue:@(YES) isDefault:NULL] boolValue]; - _shouldNotifiAppearDescendantView = shouldNotifiAppearDescendantView; - BOOL shouldRemoveScrollerListener = [[configCenter configForKey:@"iOS_weex_ext_config.shouldRemoveScrollerListener" defaultValue:@(YES) isDefault:NULL] boolValue]; - _shouldRemoveScrollerListener = shouldRemoveScrollerListener; - - } - } - - return self; -} - -- (UIView *)loadView -{ - return [[WXScrollerComponentView alloc] init]; -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; - [self setContentSize:_contentSize]; - WXScrollerComponentView* scrollView = (WXScrollerComponentView *)self.view; - scrollView.delegate = self; - scrollView.exclusiveTouch = YES; - scrollView.autoresizesSubviews = NO; - scrollView.clipsToBounds = YES; - scrollView.showsVerticalScrollIndicator = _showScrollBar; - scrollView.showsHorizontalScrollIndicator = _showScrollBar; - scrollView.scrollEnabled = _scrollable; - scrollView.pagingEnabled = _pagingEnabled; - - if (scrollView.bounces != _bounces) { - scrollView.bounces = _bounces; - } - - if (_alwaysScrollableHorizontal) { - scrollView.alwaysBounceHorizontal = [WXConvert BOOL:_alwaysScrollableHorizontal]; - } - if (_alwaysScrollableVertical) { - scrollView.alwaysBounceVertical = [WXConvert BOOL:_alwaysScrollableVertical]; - } - - if (@available(iOS 11.0, *)) { - if ([scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) { - scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; - } - } else { - // Fallback on earlier versions - } - - if (self.ancestorScroller) { - scrollView.scrollsToTop = NO; - } else { - scrollView.scrollsToTop = YES; - } -} - -- (void)layoutDidFinish -{ - if ([self isViewLoaded]) { - [self setContentSize:_contentSize]; - [self adjustSticky]; - [self handleAppear]; - } - - [_loadingComponent resizeFrame]; -} - -- (void)viewWillUnload -{ - ((UIScrollView *)_view).delegate = nil; -} - -- (void)dealloc -{ - ((UIScrollView *)_view).delegate = nil; - [self.stickyArray removeAllObjects]; - [self.listenerArray removeAllObjects]; - - free(_scrollerCSSNode); -} - -- (void)updateAttributes:(NSDictionary *)attributes -{ - if (attributes[@"showScrollbar"]) { - _showScrollBar = [WXConvert BOOL:attributes[@"showScrollbar"]]; - ((UIScrollView *)self.view).showsHorizontalScrollIndicator = _showScrollBar; - ((UIScrollView *)self.view).showsVerticalScrollIndicator = _showScrollBar; - } - - if (attributes[@"pagingEnabled"]) { - _pagingEnabled = [WXConvert BOOL:attributes[@"pagingEnabled"]]; - ((UIScrollView *)self.view).pagingEnabled = _pagingEnabled; - } - - if (attributes[@"loadmoreoffset"]) { - _loadMoreOffset = [WXConvert WXPixelType:attributes[@"loadmoreoffset"] scaleFactor:self.weexInstance.pixelScaleFactor]; - } - if (attributes[@"bounce"]) { - _bounces = [WXConvert BOOL:attributes[@"bounce"]]; - ((UIScrollView *)self.view).bounces = _bounces; - } - - if (attributes[@"loadmoreretry"]) { - NSUInteger loadmoreretry = [WXConvert NSUInteger:attributes[@"loadmoreretry"]]; - if (loadmoreretry != _loadmoreretry) { - _previousLoadMoreContentHeight = 0; - } - self.loadmoreretry = loadmoreretry; - } - if (attributes[@"scrollable"]) { - _scrollable = attributes[@"scrollable"] ? [WXConvert BOOL:attributes[@"scrollable"]] : YES; - ((UIScrollView *)self.view).scrollEnabled = _scrollable; - } - if (attributes[@"alwaysScrollableHorizontal"]) { - _alwaysScrollableHorizontal = [WXConvert NSString:attributes[@"alwaysScrollableHorizontal"]]; - ((UIScrollView*)self.view).alwaysBounceHorizontal = [WXConvert BOOL:_alwaysScrollableHorizontal]; - } - - if (attributes[@"alwaysScrollableVertical"]) { - _alwaysScrollableVertical = [WXConvert NSString:attributes[@"alwaysScrollableVertical"]]; - ((UIScrollView*)self.view).alwaysBounceVertical = [WXConvert BOOL:_alwaysScrollableVertical]; - } - - if (attributes[@"refreshType"]) { - _refreshType = [WXConvert NSString:attributes[@"refreshType"]]; - } - - if (attributes[@"offsetAccuracy"]) { - _offsetAccuracy = [WXConvert WXPixelType:attributes[@"offsetAccuracy"] scaleFactor:self.weexInstance.pixelScaleFactor]; - } - - if (attributes[@"scrollDirection"]) { - _scrollDirection = attributes[@"scrollDirection"] ? [WXConvert WXScrollDirection:attributes[@"scrollDirection"]] : WXScrollDirectionVertical; - } -} - -- (void)addEvent:(NSString *)eventName -{ - if ([eventName isEqualToString:@"loadmore"]) { - _listenLoadMore = YES; - } - if ([eventName isEqualToString:@"scroll"]) { - _scrollEvent = YES; - } - if ([eventName isEqualToString:@"scrollstart"]) { - _scrollStartEvent = YES; - } - if ([eventName isEqualToString:@"scrollend"]) { - _scrollEndEvent = YES; - } -} - -- (void)removeEvent:(NSString *)eventName -{ - if ([eventName isEqualToString:@"loadmore"]) { - _listenLoadMore = NO; - } - if ([eventName isEqualToString:@"scroll"]) { - _scrollEvent = NO; - } - if ([eventName isEqualToString:@"scrollstart"]) { - _scrollStartEvent = NO; - } - if ([eventName isEqualToString:@"scrollend"]) { - _scrollEndEvent = NO; - } -} - -#pragma mark WXScrollerProtocol - -- (void)addStickyComponent:(WXComponent *)sticky -{ - if(![self.stickyArray containsObject:sticky]) { - [self.stickyArray addObject:sticky]; - [self adjustSticky]; - } -} - -- (void)removeStickyComponent:(WXComponent *)sticky -{ - if([self.stickyArray containsObject:sticky]) { - [self.stickyArray removeObject:sticky]; - [self adjustSticky]; - } -} - -- (void)adjustSticky -{ - if (![self isViewLoaded]) { - return; - } - CGFloat scrollOffsetY = ((UIScrollView *)self.view).contentOffset.y; - for(WXComponent *component in self.stickyArray) { - if (isnan(component->_absolutePosition.x) && isnan(component->_absolutePosition.y)) { - component->_absolutePosition = [component.supercomponent.view convertPoint:component.view.frame.origin toView:self.view]; - } - CGPoint relativePosition = component->_absolutePosition; - if (isnan(relativePosition.y)) { - continue; - } - - WXComponent *supercomponent = component.supercomponent; - if(supercomponent != self && component.view.superview != self.view) { - [component.view removeFromSuperview]; - [self.view addSubview:component.view]; - } else { - [self.view bringSubviewToFront:component.view]; - } - - CGFloat relativeY = relativePosition.y; - BOOL needSticky = NO; - - if (scrollOffsetY >= relativeY) { - needSticky = YES; - } else { - // important: reset views' frame - component.view.frame = CGRectMake(relativePosition.x, relativePosition.y, component.calculatedFrame.size.width, component.calculatedFrame.size.height); - } - - if (!needSticky) { - continue; - } - - // The minimum Y sticky view can reach is its original position - CGFloat minY = relativeY; - CGPoint superRelativePosition = supercomponent == self ? CGPointZero : [supercomponent.supercomponent.view convertPoint:supercomponent.view.frame.origin toView:self.view]; - CGFloat maxY = superRelativePosition.y + supercomponent.calculatedFrame.size.height - component.calculatedFrame.size.height; - - CGFloat stickyY = scrollOffsetY; - if (stickyY < minY) { - stickyY = minY; - } else if (stickyY > maxY && ![supercomponent conformsToProtocol:@protocol(WXScrollerProtocol)]) { - // Sticky component can not go beyond its parent's bounds when its parent is not scroller; - stickyY = maxY; - } - - UIView *stickyView = component.view; - CGPoint origin = stickyView.frame.origin; - origin.y = stickyY; - stickyView.frame = (CGRect){origin,stickyView.frame.size}; - } -} - -- (void)addScrollToListener:(WXComponent *)target -{ - BOOL has = NO; - NSMutableArray *listenerArray = [self.listenerArray copy]; - for (WXScrollToTarget *targetData in listenerArray) { - if (targetData.target == target) { - has = YES; - break; - } - } - if (!has) { - WXScrollToTarget *scrollTarget = [[WXScrollToTarget alloc] init]; - scrollTarget.target = target; - scrollTarget.hasAppear = NO; - [self.listenerArray addObject:scrollTarget]; - } -} - -- (void)removeScrollToListener:(WXComponent *)target -{ - if (_shouldRemoveScrollerListener) { - WXScrollToTarget *targetData = nil; - NSMutableArray *listenerArray = [self.listenerArray copy]; - for (WXScrollToTarget *targetDataTemp in listenerArray) { - if (targetDataTemp.target == target) { - targetData = targetDataTemp; - break; - } - } - if(targetData) { - [self.listenerArray removeObject:targetData]; - } - } -} - -- (void)scrollToComponent:(WXComponent *)component withOffset:(CGFloat)offset animated:(BOOL)animated -{ - UIScrollView *scrollView = (UIScrollView *)self.view; - - CGPoint contentOffset = scrollView.contentOffset; - CGFloat scaleFactor = self.weexInstance.pixelScaleFactor; - - if (_scrollDirection == WXScrollDirectionHorizontal) { - CGFloat contentOffetX = [component.supercomponent.view convertPoint:component.view.frame.origin toView:self.view].x; - contentOffetX += offset * scaleFactor; - - if (scrollView.contentSize.width >= scrollView.frame.size.width && contentOffetX > scrollView.contentSize.width - scrollView.frame.size.width) { - contentOffset.x = scrollView.contentSize.width - scrollView.frame.size.width; - } else { - contentOffset.x = contentOffetX; - } - } else { - CGFloat contentOffetY = [component.supercomponent.view convertPoint:component.view.frame.origin toView:self.view].y; - contentOffetY += offset * scaleFactor; - - if (scrollView.contentSize.height >= scrollView.frame.size.height && contentOffetY > scrollView.contentSize.height - scrollView.frame.size.height) { - contentOffset.y = scrollView.contentSize.height - scrollView.frame.size.height; - } else { - contentOffset.y = contentOffetY; - } - } - - [scrollView setContentOffset:contentOffset animated:animated]; -} - -- (BOOL)isNeedLoadMore -{ - if (WXScrollDirectionVertical == _scrollDirection) { - if (_loadMoreOffset >= 0.0 && ((UIScrollView *)self.view).contentOffset.y >= 0) { - return _previousLoadMoreContentHeight != ((UIScrollView *)self.view).contentSize.height && ((UIScrollView *)self.view).contentSize.height - ((UIScrollView *)self.view).contentOffset.y - self.view.frame.size.height <= _loadMoreOffset; - } - } else if (WXScrollDirectionHorizontal == _scrollDirection) { - if (_loadMoreOffset >= 0.0 && ((UIScrollView *)self.view).contentOffset.x >= 0) { - return _previousLoadMoreContentHeight != ((UIScrollView *)self.view).contentSize.width && ((UIScrollView *)self.view).contentSize.width - ((UIScrollView *)self.view).contentOffset.x - self.view.frame.size.width <= _loadMoreOffset; - } - } - - return NO; -} - -- (void)loadMore -{ - [self fireEvent:@"loadmore" params:nil]; - - if (WXScrollDirectionVertical == _scrollDirection) { - _previousLoadMoreContentHeight = ((UIScrollView *)self.view).contentSize.height; - } else if (WXScrollDirectionHorizontal == _scrollDirection) { - _previousLoadMoreContentHeight = ((UIScrollView *)self.view).contentSize.width; - } -} - -- (CGPoint)contentOffset -{ - CGPoint rtv = CGPointZero; - UIScrollView *scrollView = (UIScrollView *)self.view; - if (scrollView) { - rtv = scrollView.contentOffset; - } - return rtv; -} - -- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated -{ - UIScrollView *scrollView = (UIScrollView *)self.view; - [scrollView setContentOffset:contentOffset animated:animated]; -} - -- (CGSize)contentSize -{ - return ((UIScrollView *)self.view).contentSize; -} - -- (void)setContentSize:(CGSize)size -{ - UIScrollView *scrollView = (UIScrollView *)self.view; - scrollView.contentSize = size; -} - -- (UIEdgeInsets)contentInset -{ - UIEdgeInsets rtv = UIEdgeInsetsZero; - UIScrollView *scrollView = (UIScrollView *)self.view; - if (scrollView) { - rtv = scrollView.contentInset; - } - return rtv; -} - -- (void)setContentInset:(UIEdgeInsets)contentInset -{ - UIScrollView *scrollView = (UIScrollView *)self.view; - [scrollView setContentInset:contentInset]; -} - -- (void)addScrollDelegate:(id<UIScrollViewDelegate>)delegate -{ - if (delegate) { - if (!_delegates) { - _delegates = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory]; - } - [_delegates addObject:delegate]; - } -} - -- (void)removeScrollDelegate:(id<UIScrollViewDelegate>)delegate -{ - if (delegate) { - [_delegates removeObject:delegate]; - } -} - -- (WXScrollDirection)scrollDirection -{ - return _scrollDirection; -} - -- (NSString*)refreshType -{ - return _refreshType; -} -- (BOOL)requestGestureShouldStopPropagation:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch -{ - return [self gestureShouldStopPropagation:gestureRecognizer shouldReceiveTouch:touch]; -} - -#pragma mark UIScrollViewDelegate -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView -{ - if ([_refreshType isEqualToString:@"refreshForAppear"] && _refreshComponent) { - [_refreshComponent setIndicatorHidden:NO]; - } - - if (_scrollStartEvent) { - CGFloat scaleFactor = self.weexInstance.pixelScaleFactor; - NSDictionary *contentSizeData = @{@"width":[NSNumber numberWithFloat:scrollView.contentSize.width / scaleFactor],@"height":[NSNumber numberWithFloat:scrollView.contentSize.height / scaleFactor]}; - NSDictionary *contentOffsetData = @{@"x":[NSNumber numberWithFloat:-scrollView.contentOffset.x / scaleFactor],@"y":[NSNumber numberWithFloat:-scrollView.contentOffset.y / scaleFactor]}; - [self fireEvent:@"scrollstart" params:@{@"contentSize":contentSizeData,@"contentOffset":contentOffsetData} domChanges:nil]; - } -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - //apply block which are registered - WXSDKInstance *instance = self.weexInstance; - if ([self.ref isEqualToString:WX_SDK_ROOT_REF] && - [self isKindOfClass:[WXScrollerComponent class]]) { - if (instance.onScroll) { - instance.onScroll(scrollView.contentOffset); - } - } - - if (_lastContentOffset.x > scrollView.contentOffset.x) { - _direction = @"right"; - } else if (_lastContentOffset.x < scrollView.contentOffset.x) { - _direction = @"left"; - if (WXScrollDirectionHorizontal == _scrollDirection) { - [self handleLoadMore]; - } - } else if(_lastContentOffset.y > scrollView.contentOffset.y) { - _direction = @"down"; - } else if(_lastContentOffset.y < scrollView.contentOffset.y) { - _direction = @"up"; - if (WXScrollDirectionVertical == _scrollDirection) { - [self handleLoadMore]; - } - } - - CGFloat scaleFactor = self.weexInstance.pixelScaleFactor; - [_refreshComponent pullingdown:@{ - REFRESH_DISTANCE_Y: @(fabs((scrollView.contentOffset.y - _lastContentOffset.y)/scaleFactor)), - REFRESH_VIEWHEIGHT: @(_refreshComponent.view.frame.size.height/scaleFactor), - REFRESH_PULLINGDISTANCE: @(scrollView.contentOffset.y/scaleFactor), - @"type":@"pullingdown" - }]; - _lastContentOffset = scrollView.contentOffset; - // check sticky - [self adjustSticky]; - [self handleAppear]; - - if (self.onScroll) { - self.onScroll(scrollView); - } - if (_scrollEvent) { - NSDictionary *contentSizeData = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithFloat:scrollView.contentSize.width / scaleFactor],@"width",[NSNumber numberWithFloat:scrollView.contentSize.height / scaleFactor],@"height", nil]; - //contentOffset values are replaced by (-contentOffset.x,-contentOffset.y) ,in order to be consistent with Android client. - NSDictionary *contentOffsetData = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithFloat:-scrollView.contentOffset.x / scaleFactor],@"x",[NSNumber numberWithFloat:-scrollView.contentOffset.y / scaleFactor],@"y", nil]; - CGFloat distance = 0; - if (_scrollDirection == WXScrollDirectionHorizontal) { - distance = scrollView.contentOffset.x - _lastScrollEventFiredOffset.x; - } else { - distance = scrollView.contentOffset.y - _lastScrollEventFiredOffset.y; - } - if (fabs(distance) >= _offsetAccuracy) { - [self fireEvent:@"scroll" params:@{@"contentSize":contentSizeData,@"contentOffset":contentOffsetData} domChanges:nil]; - _lastScrollEventFiredOffset = scrollView.contentOffset; - } - } - NSHashTable *delegates = [_delegates copy]; - for (id<UIScrollViewDelegate> delegate in delegates) { - if ([delegate respondsToSelector:@selector(scrollViewDidScroll:)]) { - [delegate scrollViewDidScroll:scrollView]; - } - } -} - -- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView -{ - UIEdgeInsets inset = [scrollView contentInset]; - -// currently only set contentInset when loading -// if ([_refreshComponent displayState]) { -// inset.top = _refreshComponent.view.frame.size.height; -// } -// else { -// inset.top = 0; -// } - - if ([_loadingComponent displayState]) { - inset.bottom = _loadingComponent.view.frame.size.height; - } else { - inset.bottom = 0; - } - [scrollView setContentInset:inset]; -} - -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView -{ - if (_scrollEndEvent) { - if (!_isScrolling) { - CGFloat scaleFactor = self.weexInstance.pixelScaleFactor; - NSDictionary *contentSizeData = @{@"width":[NSNumber numberWithFloat:scrollView.contentSize.width / scaleFactor],@"height":[NSNumber numberWithFloat:scrollView.contentSize.height / scaleFactor]}; - NSDictionary *contentOffsetData = @{@"x":[NSNumber numberWithFloat:-scrollView.contentOffset.x / scaleFactor],@"y":[NSNumber numberWithFloat:-scrollView.contentOffset.y / scaleFactor]}; - [self fireEvent:@"scrollend" params:@{@"contentSize":contentSizeData,@"contentOffset":contentOffsetData} domChanges:nil]; - } - } -} - -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset -{ - if ([_refreshType isEqualToString:@"refreshForAppear"]) { - if(targetContentOffset == nil) - return; - CGPoint offset = *targetContentOffset; - if(velocity.y <= 0) { - // drop down - if( offset.y <= _refreshComponent.calculatedFrame.size.height ) { - [self loadMoreIfNeed]; - } - } else if (velocity.y > 0) { - // drop up - } - } -} - -- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate -{ - [_loadingComponent.view setHidden:NO]; - [_refreshComponent.view setHidden:NO]; - - //refresh - if ([_refreshType isEqualToString:@"refreshForWholeVisible"]) { - if (_refreshComponent && scrollView.contentOffset.y < 0 && scrollView.contentOffset.y + _refreshComponent.calculatedFrame.size.height < _refreshComponent.calculatedFrame.origin.y) { - [_refreshComponent refresh]; - } - } - - //loading - if (_loadingComponent && scrollView.contentOffset.y > 0 && - scrollView.contentOffset.y + scrollView.frame.size.height > _loadingComponent.view.frame.origin.y + _loadingComponent.calculatedFrame.size.height) { - [_loadingComponent loading]; - } - if (!decelerate) { - _isScrolling = NO; - [self performSelector:@selector(scrollViewDidEndDecelerating:) withObject:nil afterDelay:0.1]; - } -} - -- (void)loadMoreIfNeed -{ - WXScrollerComponentView* scrollView = (WXScrollerComponentView *)self.view; - if (scrollView.isDragging || scrollView.isTracking || scrollView.isDecelerating) { - [self performSelector:@selector(loadMoreIfNeed) withObject:nil afterDelay:0.1]; - return; - } - [_refreshComponent refresh]; -} - -- (void)handleAppear -{ - if (![self isViewLoaded]) { - return; - } - UIScrollView *scrollView = (UIScrollView *)self.view; - CGFloat vx = scrollView.contentInset.left + scrollView.contentOffset.x; - CGFloat vy = scrollView.contentInset.top + scrollView.contentOffset.y; - CGFloat vw = scrollView.frame.size.width - scrollView.contentInset.left - scrollView.contentInset.right; - CGFloat vh = scrollView.frame.size.height - scrollView.contentInset.top - scrollView.contentInset.bottom; - CGRect scrollRect = CGRectMake(vx, vy, vw, vh);; - - // notify action for appear - NSArray *listenerArrayCopy = [self.listenerArray copy]; - for(WXScrollToTarget *target in listenerArrayCopy){ - if (_shouldNotifiAppearDescendantView) { - // if target component is descendant of scrollerview, it should notify the appear event handler, or here will skip this appear calculation. - if ([target.target isViewLoaded] && [target.target.view isDescendantOfView:self.view]) { - [self scrollToTarget:target scrollRect:scrollRect]; - } - } else { - [self scrollToTarget:target scrollRect:scrollRect]; - } - } -} - -- (CGPoint)absolutePositionForComponent:(WXComponent *)component -{ - return [component->_view.superview convertPoint:component->_view.frame.origin toView:_view]; -} - -#pragma mark Private Methods - -- (void)scrollToTarget:(WXScrollToTarget *)target scrollRect:(CGRect)rect -{ - WXComponent *component = target.target; - if (![component isViewLoaded]) { - return; - } - - CGFloat ctop; - if (component && component->_view && component->_view.superview) { - ctop = [self absolutePositionForComponent:component].y; - } else { - ctop = 0.0; - } - CGFloat cbottom = ctop + CGRectGetHeight(component.calculatedFrame); - CGFloat cleft; - if (component && component->_view && component->_view.superview) { - cleft = [self absolutePositionForComponent:component].x; - } else { - cleft = 0.0; - } - CGFloat cright = cleft + CGRectGetWidth(component.calculatedFrame); - - CGFloat vtop = CGRectGetMinY(rect), vbottom = CGRectGetMaxY(rect), vleft = CGRectGetMinX(rect), vright = CGRectGetMaxX(rect); - if(cbottom > vtop && ctop <= vbottom && cleft <= vright && cright > vleft){ - if(!target.hasAppear && component){ - target.hasAppear = YES; - if (component->_appearEvent) { -// NSLog(@"appear:%@, %.2f", component, ctop); - [component fireEvent:@"appear" params:_direction ? @{@"direction":_direction} : nil]; - } - } - } else { - if(target.hasAppear && component){ - target.hasAppear = NO; - if(component->_disappearEvent){ -// NSLog(@"disappear:%@", component); - [component fireEvent:@"disappear" params:_direction ? @{@"direction":_direction} : nil]; - } - } - } -} - -- (void)handleLoadMore -{ - if (_listenLoadMore && [self isNeedLoadMore]) { - [self loadMore]; - } -} - -#pragma mark Layout - -- (NSUInteger)_childrenCountForLayout; -{ - return 0; -} - -- (NSUInteger)childrenCountForScrollerLayout -{ - return [super _childrenCountForLayout]; -} - -- (void)_calculateFrameWithSuperAbsolutePosition:(CGPoint)superAbsolutePosition - gatherDirtyComponents:(NSMutableSet<WXComponent *> *)dirtyComponents -{ - /** - * Pretty hacky way - * layout from root to scroller to get scroller's frame, - * layout from children to scroller to get scroller's contentSize - */ - if ([self needsLayout]) { - memcpy(_scrollerCSSNode, self.cssNode, sizeof(css_node_t)); - _scrollerCSSNode->children_count = (int)[self childrenCountForScrollerLayout]; - - _scrollerCSSNode->style.position[CSS_LEFT] = 0; - _scrollerCSSNode->style.position[CSS_TOP] = 0; - - if (_scrollDirection == WXScrollDirectionVertical) { - _scrollerCSSNode->style.flex_direction = CSS_FLEX_DIRECTION_COLUMN; - _scrollerCSSNode->style.dimensions[CSS_WIDTH] = _cssNode->layout.dimensions[CSS_WIDTH]; - _scrollerCSSNode->style.dimensions[CSS_HEIGHT] = CSS_UNDEFINED; - } else { - _scrollerCSSNode->style.flex_direction = CSS_FLEX_DIRECTION_ROW; - _scrollerCSSNode->style.dimensions[CSS_HEIGHT] = _cssNode->layout.dimensions[CSS_HEIGHT]; - _scrollerCSSNode->style.dimensions[CSS_WIDTH] = CSS_UNDEFINED; - } - - _scrollerCSSNode->layout.dimensions[CSS_WIDTH] = CSS_UNDEFINED; - _scrollerCSSNode->layout.dimensions[CSS_HEIGHT] = CSS_UNDEFINED; - - layoutNode(_scrollerCSSNode, CSS_UNDEFINED, CSS_UNDEFINED, CSS_DIRECTION_INHERIT); - if ([WXLog logLevel] >= WXLogLevelDebug) { - print_css_node(_scrollerCSSNode, CSS_PRINT_LAYOUT | CSS_PRINT_STYLE | CSS_PRINT_CHILDREN); - } - CGSize size = { - WXRoundPixelValue(_scrollerCSSNode->layout.dimensions[CSS_WIDTH]), - WXRoundPixelValue(_scrollerCSSNode->layout.dimensions[CSS_HEIGHT]) - }; - - if (!CGSizeEqualToSize(size, _contentSize)) { - // content size - _contentSize = size; - [dirtyComponents addObject:self]; - } - - _scrollerCSSNode->layout.dimensions[CSS_WIDTH] = CSS_UNDEFINED; - _scrollerCSSNode->layout.dimensions[CSS_HEIGHT] = CSS_UNDEFINED; - } - - [super _calculateFrameWithSuperAbsolutePosition:superAbsolutePosition gatherDirtyComponents:dirtyComponents]; -} - -@end http://git-wip-us.apache.org/repos/asf/incubator-weex/blob/b77b4259/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.mm ---------------------------------------------------------------------- diff --git a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.mm b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.mm new file mode 100644 index 0000000..215d9ff --- /dev/null +++ b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.mm @@ -0,0 +1,951 @@ +/* + * 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 "WXScrollerComponent.h" +#import "WXComponent_internal.h" +#import "WXComponent.h" +#import "WXDefine.h" +#import "WXConvert.h" +#import "WXSDKInstance.h" +#import "WXUtility.h" +#import "WXLoadingComponent.h" +#import "WXRefreshComponent.h" +#import "WXConfigCenterProtocol.h" +#import "WXSDKEngine.h" +#import "WXComponent+Events.h" +#import "WXScrollerComponent+Layout.h" +#import "WXCoreLayout.h" + +@interface WXScrollerComponentView:UIScrollView +@end + +@implementation WXScrollerComponentView +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch +{ + if ([(id <WXScrollerProtocol>) self.wx_component respondsToSelector:@selector(requestGestureShouldStopPropagation:shouldReceiveTouch:)]) { + return [(id <WXScrollerProtocol>) self.wx_component requestGestureShouldStopPropagation:gestureRecognizer shouldReceiveTouch:touch]; + } + else{ + return YES; + } +} +@end + +@interface WXScrollToTarget : NSObject + +@property (nonatomic, weak) WXComponent *target; +@property (nonatomic, assign) BOOL hasAppear; + +@end + +@implementation WXScrollToTarget + +@end + + +@interface WXScrollerComponent() + +@property (nonatomic, strong) NSMutableArray * stickyArray; +@property (nonatomic, strong) NSMutableArray * listenerArray; +@property (nonatomic, weak) WXRefreshComponent *refreshComponent; +@property (nonatomic, weak) WXLoadingComponent *loadingComponent; + +@end + +@implementation WXScrollerComponent +{ + CGSize _contentSize; + BOOL _listenLoadMore; + BOOL _scrollEvent; + BOOL _scrollStartEvent; + BOOL _scrollEndEvent; + BOOL _isScrolling; + CGFloat _loadMoreOffset; + CGFloat _previousLoadMoreContentHeight; + CGFloat _offsetAccuracy; + CGPoint _lastContentOffset; + CGPoint _lastScrollEventFiredOffset; + BOOL _scrollable; + NSString * _alwaysScrollableVertical; + NSString * _alwaysScrollableHorizontal; + BOOL _bounces; + + // refreshForAppear: load more when refresh component begin appear(if scroll is dragging or decelerating, should delay) + // refreshForWholeVisible: load more until the whole refresh component visible + NSString *_refreshType; + + // vertical & horizontal + WXScrollDirection _scrollDirection; + // left & right & up & down + NSString *_direction; + BOOL _showScrollBar; + BOOL _pagingEnabled; + + BOOL _shouldNotifiAppearDescendantView; + BOOL _shouldRemoveScrollerListener; + + //css_node_t *_scrollerCSSNode; + + NSHashTable* _delegates; +} + +WX_EXPORT_METHOD(@selector(resetLoadmore)) + +- (void)resetLoadmore +{ + _previousLoadMoreContentHeight=0; +} + +- (void)_insertSubcomponent:(WXComponent *)subcomponent atIndex:(NSInteger)index +{ + [super _insertSubcomponent:subcomponent atIndex:index]; + + if ([subcomponent isKindOfClass:[WXRefreshComponent class]]) { + _refreshComponent = (WXRefreshComponent*)subcomponent; + } + else if ([subcomponent isKindOfClass:[WXLoadingComponent class]]) { + _loadingComponent = (WXLoadingComponent*)subcomponent; + } +} + +-(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) { + + _stickyArray = [NSMutableArray array]; + _listenerArray = [NSMutableArray array]; + _scrollEvent = NO; + _scrollStartEvent = NO; + _scrollEndEvent = NO; + _lastScrollEventFiredOffset = CGPointMake(0, 0); + _scrollDirection = attributes[@"scrollDirection"] ? [WXConvert WXScrollDirection:attributes[@"scrollDirection"]] : WXScrollDirectionVertical; + _showScrollBar = attributes[@"showScrollbar"] ? [WXConvert BOOL:attributes[@"showScrollbar"]] : YES; + + if (attributes[@"alwaysScrollableVertical"]) { + _alwaysScrollableVertical = [WXConvert NSString:attributes[@"alwaysScrollableVertical"]]; + } + if (attributes[@"alwaysScrollableHorizontal"]) { + _alwaysScrollableHorizontal = [WXConvert NSString:attributes[@"alwaysScrollableHorizontal"]]; + } + _bounces = attributes[@"bounce"]?[WXConvert BOOL:attributes[@"bounce"]]:YES; + _refreshType = [WXConvert NSString:attributes[@"refreshType"]]?:@"refreshForWholeVisible"; + _pagingEnabled = attributes[@"pagingEnabled"] ? [WXConvert BOOL:attributes[@"pagingEnabled"]] : NO; + _loadMoreOffset = attributes[@"loadmoreoffset"] ? [WXConvert WXPixelType:attributes[@"loadmoreoffset"] scaleFactor:self.weexInstance.pixelScaleFactor] : 0; + _loadmoreretry = attributes[@"loadmoreretry"] ? [WXConvert NSUInteger:attributes[@"loadmoreretry"]] : 0; + _listenLoadMore = [events containsObject:@"loadmore"]; + _scrollable = attributes[@"scrollable"] ? [WXConvert BOOL:attributes[@"scrollable"]] : YES; + _offsetAccuracy = attributes[@"offsetAccuracy"] ? [WXConvert WXPixelType:attributes[@"offsetAccuracy"] scaleFactor:self.weexInstance.pixelScaleFactor] : 0; + +//#ifndef USE_FLEX + if(![WXComponent isUseFlex]) + { + _scrollerCSSNode = new_css_node(); + + // let scroller fill the rest space if it is a child component and has no fixed height & width + if (((_scrollDirection == WXScrollDirectionVertical && + isUndefined(self.cssNode->style.dimensions[CSS_HEIGHT])) || + (_scrollDirection == WXScrollDirectionHorizontal && + isUndefined(self.cssNode->style.dimensions[CSS_WIDTH]))) && + self.cssNode->style.flex <= 0.0) { + self.cssNode->style.flex = 1.0; + } + } +//#else + else + { + _flexScrollerCSSNode = new WeexCore::WXCoreLayoutNode(); + // let scroller fill the rest space if it is a child component and has no fixed height & width + if (((_scrollDirection == WXScrollDirectionVertical && + flexIsUndefined(self.flexCssNode->getStyleHeight())) || + (_scrollDirection == WXScrollDirectionHorizontal && + flexIsUndefined(self.flexCssNode->getStyleWidth()))) && + self.flexCssNode->getFlex() <= 0.0) { + self.flexCssNode->setFlex(1.0); + } + } +//#endif + id configCenter = [WXSDKEngine handlerForProtocol:@protocol(WXConfigCenterProtocol)]; + if ([configCenter respondsToSelector:@selector(configForKey:defaultValue:isDefault:)]) { + BOOL shouldNotifiAppearDescendantView = [[configCenter configForKey:@"iOS_weex_ext_config.shouldNotifiAppearDescendantView" defaultValue:@(YES) isDefault:NULL] boolValue]; + _shouldNotifiAppearDescendantView = shouldNotifiAppearDescendantView; + BOOL shouldRemoveScrollerListener = [[configCenter configForKey:@"iOS_weex_ext_config.shouldRemoveScrollerListener" defaultValue:@(YES) isDefault:NULL] boolValue]; + _shouldRemoveScrollerListener = shouldRemoveScrollerListener; + + } + } + + return self; +} + +- (UIView *)loadView +{ + return [[WXScrollerComponentView alloc] init]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + [self setContentSize:_contentSize]; + WXScrollerComponentView* scrollView = (WXScrollerComponentView *)self.view; + scrollView.delegate = self; + scrollView.exclusiveTouch = YES; + scrollView.autoresizesSubviews = NO; + scrollView.clipsToBounds = YES; + scrollView.showsVerticalScrollIndicator = _showScrollBar; + scrollView.showsHorizontalScrollIndicator = _showScrollBar; + scrollView.scrollEnabled = _scrollable; + scrollView.pagingEnabled = _pagingEnabled; + + if (scrollView.bounces != _bounces) { + scrollView.bounces = _bounces; + } + + if (_alwaysScrollableHorizontal) { + scrollView.alwaysBounceHorizontal = [WXConvert BOOL:_alwaysScrollableHorizontal]; + } + if (_alwaysScrollableVertical) { + scrollView.alwaysBounceVertical = [WXConvert BOOL:_alwaysScrollableVertical]; + } + + if (@available(iOS 11.0, *)) { + if ([scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) { + scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } + } else { + // Fallback on earlier versions + } + + if (self.ancestorScroller) { + scrollView.scrollsToTop = NO; + } else { + scrollView.scrollsToTop = YES; + } +} + +- (void)layoutDidFinish +{ + + + if ([self isViewLoaded]) { + [self setContentSize:_contentSize]; + [self adjustSticky]; + [self handleAppear]; + } + + [_loadingComponent resizeFrame]; +} + +- (void)viewWillUnload +{ + ((UIScrollView *)_view).delegate = nil; +} + +- (void)dealloc +{ + ((UIScrollView *)_view).delegate = nil; + [self.stickyArray removeAllObjects]; + [self.listenerArray removeAllObjects]; +//#ifndef USE_FLEX + if (![WXComponent isUseFlex]) { + free(_scrollerCSSNode); + } +//#else + else + { + if(_flexScrollerCSSNode){ + delete _flexScrollerCSSNode; + + //WeexCore::WXCoreLayoutNode::freeNodeTree(_flexScrollerCSSNode); + + _flexScrollerCSSNode=nullptr; + } + } +//#endif +} + +- (void)updateAttributes:(NSDictionary *)attributes +{ + if (attributes[@"showScrollbar"]) { + _showScrollBar = [WXConvert BOOL:attributes[@"showScrollbar"]]; + ((UIScrollView *)self.view).showsHorizontalScrollIndicator = _showScrollBar; + ((UIScrollView *)self.view).showsVerticalScrollIndicator = _showScrollBar; + } + + if (attributes[@"pagingEnabled"]) { + _pagingEnabled = [WXConvert BOOL:attributes[@"pagingEnabled"]]; + ((UIScrollView *)self.view).pagingEnabled = _pagingEnabled; + } + + if (attributes[@"loadmoreoffset"]) { + _loadMoreOffset = [WXConvert WXPixelType:attributes[@"loadmoreoffset"] scaleFactor:self.weexInstance.pixelScaleFactor]; + } + if (attributes[@"bounce"]) { + _bounces = [WXConvert BOOL:attributes[@"bounce"]]; + ((UIScrollView *)self.view).bounces = _bounces; + } + + if (attributes[@"loadmoreretry"]) { + NSUInteger loadmoreretry = [WXConvert NSUInteger:attributes[@"loadmoreretry"]]; + if (loadmoreretry != _loadmoreretry) { + _previousLoadMoreContentHeight = 0; + } + self.loadmoreretry = loadmoreretry; + } + if (attributes[@"scrollable"]) { + _scrollable = attributes[@"scrollable"] ? [WXConvert BOOL:attributes[@"scrollable"]] : YES; + ((UIScrollView *)self.view).scrollEnabled = _scrollable; + } + if (attributes[@"alwaysScrollableHorizontal"]) { + _alwaysScrollableHorizontal = [WXConvert NSString:attributes[@"alwaysScrollableHorizontal"]]; + ((UIScrollView*)self.view).alwaysBounceHorizontal = [WXConvert BOOL:_alwaysScrollableHorizontal]; + } + + if (attributes[@"alwaysScrollableVertical"]) { + _alwaysScrollableVertical = [WXConvert NSString:attributes[@"alwaysScrollableVertical"]]; + ((UIScrollView*)self.view).alwaysBounceVertical = [WXConvert BOOL:_alwaysScrollableVertical]; + } + + if (attributes[@"refreshType"]) { + _refreshType = [WXConvert NSString:attributes[@"refreshType"]]; + } + + if (attributes[@"offsetAccuracy"]) { + _offsetAccuracy = [WXConvert WXPixelType:attributes[@"offsetAccuracy"] scaleFactor:self.weexInstance.pixelScaleFactor]; + } + + if (attributes[@"scrollDirection"]) { + _scrollDirection = attributes[@"scrollDirection"] ? [WXConvert WXScrollDirection:attributes[@"scrollDirection"]] : WXScrollDirectionVertical; + } +} + +- (void)addEvent:(NSString *)eventName +{ + if ([eventName isEqualToString:@"loadmore"]) { + _listenLoadMore = YES; + } + if ([eventName isEqualToString:@"scroll"]) { + _scrollEvent = YES; + } + if ([eventName isEqualToString:@"scrollstart"]) { + _scrollStartEvent = YES; + } + if ([eventName isEqualToString:@"scrollend"]) { + _scrollEndEvent = YES; + } +} + +- (void)removeEvent:(NSString *)eventName +{ + if ([eventName isEqualToString:@"loadmore"]) { + _listenLoadMore = NO; + } + if ([eventName isEqualToString:@"scroll"]) { + _scrollEvent = NO; + } + if ([eventName isEqualToString:@"scrollstart"]) { + _scrollStartEvent = NO; + } + if ([eventName isEqualToString:@"scrollend"]) { + _scrollEndEvent = NO; + } +} + +#pragma mark WXScrollerProtocol + +- (void)addStickyComponent:(WXComponent *)sticky +{ + if(![self.stickyArray containsObject:sticky]) { + [self.stickyArray addObject:sticky]; + [self adjustSticky]; + } +} + +- (void)removeStickyComponent:(WXComponent *)sticky +{ + if([self.stickyArray containsObject:sticky]) { + [self.stickyArray removeObject:sticky]; + [self adjustSticky]; + } +} + +- (void)adjustSticky +{ + if (![self isViewLoaded]) { + return; + } + CGFloat scrollOffsetY = ((UIScrollView *)self.view).contentOffset.y; + for(WXComponent *component in self.stickyArray) { + if (isnan(component->_absolutePosition.x) && isnan(component->_absolutePosition.y)) { + component->_absolutePosition = [component.supercomponent.view convertPoint:component.view.frame.origin toView:self.view]; + } + CGPoint relativePosition = component->_absolutePosition; + if (isnan(relativePosition.y)) { + continue; + } + + WXComponent *supercomponent = component.supercomponent; + if(supercomponent != self && component.view.superview != self.view) { + [component.view removeFromSuperview]; + [self.view addSubview:component.view]; + } else { + [self.view bringSubviewToFront:component.view]; + } + + CGFloat relativeY = relativePosition.y; + BOOL needSticky = NO; + + if (scrollOffsetY >= relativeY) { + needSticky = YES; + } else { + // important: reset views' frame + component.view.frame = CGRectMake(relativePosition.x, relativePosition.y, component.calculatedFrame.size.width, component.calculatedFrame.size.height); + } + + if (!needSticky) { + continue; + } + + // The minimum Y sticky view can reach is its original position + CGFloat minY = relativeY; + CGPoint superRelativePosition = supercomponent == self ? CGPointZero : [supercomponent.supercomponent.view convertPoint:supercomponent.view.frame.origin toView:self.view]; + CGFloat maxY = superRelativePosition.y + supercomponent.calculatedFrame.size.height - component.calculatedFrame.size.height; + + CGFloat stickyY = scrollOffsetY; + if (stickyY < minY) { + stickyY = minY; + } else if (stickyY > maxY && ![supercomponent conformsToProtocol:@protocol(WXScrollerProtocol)]) { + // Sticky component can not go beyond its parent's bounds when its parent is not scroller; + stickyY = maxY; + } + + UIView *stickyView = component.view; + CGPoint origin = stickyView.frame.origin; + origin.y = stickyY; + stickyView.frame = (CGRect){origin,stickyView.frame.size}; + } +} + +- (void)addScrollToListener:(WXComponent *)target +{ + BOOL has = NO; + NSMutableArray *listenerArray = [self.listenerArray copy]; + for (WXScrollToTarget *targetData in listenerArray) { + if (targetData.target == target) { + has = YES; + break; + } + } + if (!has) { + WXScrollToTarget *scrollTarget = [[WXScrollToTarget alloc] init]; + scrollTarget.target = target; + scrollTarget.hasAppear = NO; + [self.listenerArray addObject:scrollTarget]; + } +} + +- (void)removeScrollToListener:(WXComponent *)target +{ + if (_shouldRemoveScrollerListener) { + WXScrollToTarget *targetData = nil; + NSMutableArray *listenerArray = [self.listenerArray copy]; + for (WXScrollToTarget *targetDataTemp in listenerArray) { + if (targetDataTemp.target == target) { + targetData = targetDataTemp; + break; + } + } + if(targetData) { + [self.listenerArray removeObject:targetData]; + } + } +} + +- (void)scrollToComponent:(WXComponent *)component withOffset:(CGFloat)offset animated:(BOOL)animated +{ + UIScrollView *scrollView = (UIScrollView *)self.view; + + CGPoint contentOffset = scrollView.contentOffset; + CGFloat scaleFactor = self.weexInstance.pixelScaleFactor; + + if (_scrollDirection == WXScrollDirectionHorizontal) { + CGFloat contentOffetX = [component.supercomponent.view convertPoint:component.view.frame.origin toView:self.view].x; + contentOffetX += offset * scaleFactor; + + if (scrollView.contentSize.width >= scrollView.frame.size.width && contentOffetX > scrollView.contentSize.width - scrollView.frame.size.width) { + contentOffset.x = scrollView.contentSize.width - scrollView.frame.size.width; + } else { + contentOffset.x = contentOffetX; + } + } else { + CGFloat contentOffetY = [component.supercomponent.view convertPoint:component.view.frame.origin toView:self.view].y; + contentOffetY += offset * scaleFactor; + + if (scrollView.contentSize.height >= scrollView.frame.size.height && contentOffetY > scrollView.contentSize.height - scrollView.frame.size.height) { + contentOffset.y = scrollView.contentSize.height - scrollView.frame.size.height; + } else { + contentOffset.y = contentOffetY; + } + } + + [scrollView setContentOffset:contentOffset animated:animated]; +} + +- (BOOL)isNeedLoadMore +{ + if (WXScrollDirectionVertical == _scrollDirection) { + if (_loadMoreOffset >= 0.0 && ((UIScrollView *)self.view).contentOffset.y >= 0) { + return _previousLoadMoreContentHeight != ((UIScrollView *)self.view).contentSize.height && ((UIScrollView *)self.view).contentSize.height - ((UIScrollView *)self.view).contentOffset.y - self.view.frame.size.height <= _loadMoreOffset; + } + } else if (WXScrollDirectionHorizontal == _scrollDirection) { + if (_loadMoreOffset >= 0.0 && ((UIScrollView *)self.view).contentOffset.x >= 0) { + return _previousLoadMoreContentHeight != ((UIScrollView *)self.view).contentSize.width && ((UIScrollView *)self.view).contentSize.width - ((UIScrollView *)self.view).contentOffset.x - self.view.frame.size.width <= _loadMoreOffset; + } + } + + return NO; +} + +- (void)loadMore +{ + [self fireEvent:@"loadmore" params:nil]; + + if (WXScrollDirectionVertical == _scrollDirection) { + _previousLoadMoreContentHeight = ((UIScrollView *)self.view).contentSize.height; + } else if (WXScrollDirectionHorizontal == _scrollDirection) { + _previousLoadMoreContentHeight = ((UIScrollView *)self.view).contentSize.width; + } +} + +- (CGPoint)contentOffset +{ + CGPoint rtv = CGPointZero; + UIScrollView *scrollView = (UIScrollView *)self.view; + if (scrollView) { + rtv = scrollView.contentOffset; + } + return rtv; +} + +- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated +{ + UIScrollView *scrollView = (UIScrollView *)self.view; + [scrollView setContentOffset:contentOffset animated:animated]; +} + +- (CGSize)contentSize +{ + return ((UIScrollView *)self.view).contentSize; +} + +- (void)setContentSize:(CGSize)size +{ + UIScrollView *scrollView = (UIScrollView *)self.view; + scrollView.contentSize = size; +} + +- (UIEdgeInsets)contentInset +{ + UIEdgeInsets rtv = UIEdgeInsetsZero; + UIScrollView *scrollView = (UIScrollView *)self.view; + if (scrollView) { + rtv = scrollView.contentInset; + } + return rtv; +} + +- (void)setContentInset:(UIEdgeInsets)contentInset +{ + UIScrollView *scrollView = (UIScrollView *)self.view; + [scrollView setContentInset:contentInset]; +} + +- (void)addScrollDelegate:(id<UIScrollViewDelegate>)delegate +{ + if (delegate) { + if (!_delegates) { + _delegates = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory]; + } + [_delegates addObject:delegate]; + } +} + +- (void)removeScrollDelegate:(id<UIScrollViewDelegate>)delegate +{ + if (delegate) { + [_delegates removeObject:delegate]; + } +} + +- (WXScrollDirection)scrollDirection +{ + return _scrollDirection; +} + +- (NSString*)refreshType +{ + return _refreshType; +} +- (BOOL)requestGestureShouldStopPropagation:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch +{ + return [self gestureShouldStopPropagation:gestureRecognizer shouldReceiveTouch:touch]; +} + +#pragma mark UIScrollViewDelegate +- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView +{ + if ([_refreshType isEqualToString:@"refreshForAppear"] && _refreshComponent) { + [_refreshComponent setIndicatorHidden:NO]; + } + + if (_scrollStartEvent) { + CGFloat scaleFactor = self.weexInstance.pixelScaleFactor; + NSDictionary *contentSizeData = @{@"width":[NSNumber numberWithFloat:scrollView.contentSize.width / scaleFactor],@"height":[NSNumber numberWithFloat:scrollView.contentSize.height / scaleFactor]}; + NSDictionary *contentOffsetData = @{@"x":[NSNumber numberWithFloat:-scrollView.contentOffset.x / scaleFactor],@"y":[NSNumber numberWithFloat:-scrollView.contentOffset.y / scaleFactor]}; + [self fireEvent:@"scrollstart" params:@{@"contentSize":contentSizeData,@"contentOffset":contentOffsetData} domChanges:nil]; + } +} + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView +{ + //apply block which are registered + WXSDKInstance *instance = self.weexInstance; + if ([self.ref isEqualToString:WX_SDK_ROOT_REF] && + [self isKindOfClass:[WXScrollerComponent class]]) { + if (instance.onScroll) { + instance.onScroll(scrollView.contentOffset); + } + } + + if (_lastContentOffset.x > scrollView.contentOffset.x) { + _direction = @"right"; + } else if (_lastContentOffset.x < scrollView.contentOffset.x) { + _direction = @"left"; + if (WXScrollDirectionHorizontal == _scrollDirection) { + [self handleLoadMore]; + } + } else if(_lastContentOffset.y > scrollView.contentOffset.y) { + _direction = @"down"; + } else if(_lastContentOffset.y < scrollView.contentOffset.y) { + _direction = @"up"; + if (WXScrollDirectionVertical == _scrollDirection) { + [self handleLoadMore]; + } + } + + CGFloat scaleFactor = self.weexInstance.pixelScaleFactor; + [_refreshComponent pullingdown:@{ + REFRESH_DISTANCE_Y: @(fabs((scrollView.contentOffset.y - _lastContentOffset.y)/scaleFactor)), + REFRESH_VIEWHEIGHT: @(_refreshComponent.view.frame.size.height/scaleFactor), + REFRESH_PULLINGDISTANCE: @(scrollView.contentOffset.y/scaleFactor), + @"type":@"pullingdown" + }]; + _lastContentOffset = scrollView.contentOffset; + // check sticky + [self adjustSticky]; + [self handleAppear]; + + if (self.onScroll) { + self.onScroll(scrollView); + } + if (_scrollEvent) { + NSDictionary *contentSizeData = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithFloat:scrollView.contentSize.width / scaleFactor],@"width",[NSNumber numberWithFloat:scrollView.contentSize.height / scaleFactor],@"height", nil]; + //contentOffset values are replaced by (-contentOffset.x,-contentOffset.y) ,in order to be consistent with Android client. + NSDictionary *contentOffsetData = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithFloat:-scrollView.contentOffset.x / scaleFactor],@"x",[NSNumber numberWithFloat:-scrollView.contentOffset.y / scaleFactor],@"y", nil]; + CGFloat distance = 0; + if (_scrollDirection == WXScrollDirectionHorizontal) { + distance = scrollView.contentOffset.x - _lastScrollEventFiredOffset.x; + } else { + distance = scrollView.contentOffset.y - _lastScrollEventFiredOffset.y; + } + if (fabs(distance) >= _offsetAccuracy) { + [self fireEvent:@"scroll" params:@{@"contentSize":contentSizeData,@"contentOffset":contentOffsetData} domChanges:nil]; + _lastScrollEventFiredOffset = scrollView.contentOffset; + } + } + NSHashTable *delegates = [_delegates copy]; + for (id<UIScrollViewDelegate> delegate in delegates) { + if ([delegate respondsToSelector:@selector(scrollViewDidScroll:)]) { + [delegate scrollViewDidScroll:scrollView]; + } + } +} + +- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView +{ + UIEdgeInsets inset = [scrollView contentInset]; + +// currently only set contentInset when loading +// if ([_refreshComponent displayState]) { +// inset.top = _refreshComponent.view.frame.size.height; +// } +// else { +// inset.top = 0; +// } + + if ([_loadingComponent displayState]) { + inset.bottom = _loadingComponent.view.frame.size.height; + } else { + inset.bottom = 0; + } + [scrollView setContentInset:inset]; +} + +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView +{ + if (_scrollEndEvent) { + if (!_isScrolling) { + CGFloat scaleFactor = self.weexInstance.pixelScaleFactor; + NSDictionary *contentSizeData = @{@"width":[NSNumber numberWithFloat:scrollView.contentSize.width / scaleFactor],@"height":[NSNumber numberWithFloat:scrollView.contentSize.height / scaleFactor]}; + NSDictionary *contentOffsetData = @{@"x":[NSNumber numberWithFloat:-scrollView.contentOffset.x / scaleFactor],@"y":[NSNumber numberWithFloat:-scrollView.contentOffset.y / scaleFactor]}; + [self fireEvent:@"scrollend" params:@{@"contentSize":contentSizeData,@"contentOffset":contentOffsetData} domChanges:nil]; + } + } +} + +- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset +{ + if ([_refreshType isEqualToString:@"refreshForAppear"]) { + if(targetContentOffset == nil) + return; + CGPoint offset = *targetContentOffset; + if(velocity.y <= 0) { + // drop down + if( offset.y <= _refreshComponent.calculatedFrame.size.height ) { + [self loadMoreIfNeed]; + } + } else if (velocity.y > 0) { + // drop up + } + } +} + +- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate +{ + [_loadingComponent.view setHidden:NO]; + [_refreshComponent.view setHidden:NO]; + + //refresh + if ([_refreshType isEqualToString:@"refreshForWholeVisible"]) { + if (_refreshComponent && scrollView.contentOffset.y < 0 && scrollView.contentOffset.y + _refreshComponent.calculatedFrame.size.height < _refreshComponent.calculatedFrame.origin.y) { + [_refreshComponent refresh]; + } + } + + //loading + if (_loadingComponent && scrollView.contentOffset.y > 0 && + scrollView.contentOffset.y + scrollView.frame.size.height > _loadingComponent.view.frame.origin.y + _loadingComponent.calculatedFrame.size.height) { + [_loadingComponent loading]; + } + if (!decelerate) { + _isScrolling = NO; + [self performSelector:@selector(scrollViewDidEndDecelerating:) withObject:nil afterDelay:0.1]; + } +} + +- (void)loadMoreIfNeed +{ + WXScrollerComponentView* scrollView = (WXScrollerComponentView *)self.view; + if (scrollView.isDragging || scrollView.isTracking || scrollView.isDecelerating) { + [self performSelector:@selector(loadMoreIfNeed) withObject:nil afterDelay:0.1]; + return; + } + [_refreshComponent refresh]; +} + +- (void)handleAppear +{ + if (![self isViewLoaded]) { + return; + } + UIScrollView *scrollView = (UIScrollView *)self.view; + CGFloat vx = scrollView.contentInset.left + scrollView.contentOffset.x; + CGFloat vy = scrollView.contentInset.top + scrollView.contentOffset.y; + CGFloat vw = scrollView.frame.size.width - scrollView.contentInset.left - scrollView.contentInset.right; + CGFloat vh = scrollView.frame.size.height - scrollView.contentInset.top - scrollView.contentInset.bottom; + CGRect scrollRect = CGRectMake(vx, vy, vw, vh);; + + // notify action for appear + NSArray *listenerArrayCopy = [self.listenerArray copy]; + for(WXScrollToTarget *target in listenerArrayCopy){ + if (_shouldNotifiAppearDescendantView) { + // if target component is descendant of scrollerview, it should notify the appear event handler, or here will skip this appear calculation. + if ([target.target isViewLoaded] && [target.target.view isDescendantOfView:self.view]) { + [self scrollToTarget:target scrollRect:scrollRect]; + } + } else { + [self scrollToTarget:target scrollRect:scrollRect]; + } + } +} + +- (CGPoint)absolutePositionForComponent:(WXComponent *)component +{ + return [component->_view.superview convertPoint:component->_view.frame.origin toView:_view]; +} + +#pragma mark Private Methods + +- (void)scrollToTarget:(WXScrollToTarget *)target scrollRect:(CGRect)rect +{ + WXComponent *component = target.target; + if (![component isViewLoaded]) { + return; + } + + CGFloat ctop; + if (component && component->_view && component->_view.superview) { + ctop = [self absolutePositionForComponent:component].y; + } else { + ctop = 0.0; + } + CGFloat cbottom = ctop + CGRectGetHeight(component.calculatedFrame); + CGFloat cleft; + if (component && component->_view && component->_view.superview) { + cleft = [self absolutePositionForComponent:component].x; + } else { + cleft = 0.0; + } + CGFloat cright = cleft + CGRectGetWidth(component.calculatedFrame); + + CGFloat vtop = CGRectGetMinY(rect), vbottom = CGRectGetMaxY(rect), vleft = CGRectGetMinX(rect), vright = CGRectGetMaxX(rect); + if(cbottom > vtop && ctop <= vbottom && cleft <= vright && cright > vleft){ + if(!target.hasAppear && component){ + target.hasAppear = YES; + if (component->_appearEvent) { +// NSLog(@"appear:%@, %.2f", component, ctop); + [component fireEvent:@"appear" params:_direction ? @{@"direction":_direction} : nil]; + } + } + } else { + if(target.hasAppear && component){ + target.hasAppear = NO; + if(component->_disappearEvent){ +// NSLog(@"disappear:%@", component); + [component fireEvent:@"disappear" params:_direction ? @{@"direction":_direction} : nil]; + } + } + } +} + +- (void)handleLoadMore +{ + if (_listenLoadMore && [self isNeedLoadMore]) { + [self loadMore]; + } +} + +#pragma mark Layout + +- (NSUInteger)_childrenCountForLayout; +{ + return 0; +} + +- (NSUInteger)childrenCountForScrollerLayout +{ + return [super _childrenCountForLayout]; +} + +- (void)_calculateFrameWithSuperAbsolutePosition:(CGPoint)superAbsolutePosition + gatherDirtyComponents:(NSMutableSet<WXComponent *> *)dirtyComponents +{ + /** + * Pretty hacky way + * layout from root to scroller to get scroller's frame, + * layout from children to scroller to get scroller's contentSize + */ + if ([self needsLayout]) { +//#ifndef USE_FLEX + if (![WXComponent isUseFlex]) { + memcpy(_scrollerCSSNode, self.cssNode, sizeof(css_node_t)); + _scrollerCSSNode->children_count = (int)[self childrenCountForScrollerLayout]; + + _scrollerCSSNode->style.position[CSS_LEFT] = 0; + _scrollerCSSNode->style.position[CSS_TOP] = 0; + + if (_scrollDirection == WXScrollDirectionVertical) { + _scrollerCSSNode->style.flex_direction = CSS_FLEX_DIRECTION_COLUMN; + _scrollerCSSNode->style.dimensions[CSS_WIDTH] = _cssNode->layout.dimensions[CSS_WIDTH]; + _scrollerCSSNode->style.dimensions[CSS_HEIGHT] = CSS_UNDEFINED; + } else { + _scrollerCSSNode->style.flex_direction = CSS_FLEX_DIRECTION_ROW; + _scrollerCSSNode->style.dimensions[CSS_HEIGHT] = _cssNode->layout.dimensions[CSS_HEIGHT]; + _scrollerCSSNode->style.dimensions[CSS_WIDTH] = CSS_UNDEFINED; + } + + _scrollerCSSNode->layout.dimensions[CSS_WIDTH] = CSS_UNDEFINED; + _scrollerCSSNode->layout.dimensions[CSS_HEIGHT] = CSS_UNDEFINED; + + layoutNode(_scrollerCSSNode, CSS_UNDEFINED, CSS_UNDEFINED, CSS_DIRECTION_INHERIT); + if ([WXLog logLevel] >= WXLogLevelDebug) { + print_css_node(_scrollerCSSNode, (css_print_options_t)(CSS_PRINT_LAYOUT | CSS_PRINT_STYLE | CSS_PRINT_CHILDREN)); + } + CGSize size = { + WXRoundPixelValue(_scrollerCSSNode->layout.dimensions[CSS_WIDTH]), + WXRoundPixelValue(_scrollerCSSNode->layout.dimensions[CSS_HEIGHT]) + }; + + if (!CGSizeEqualToSize(size, _contentSize)) { + _contentSize = size; + [dirtyComponents addObject:self]; + } + + _scrollerCSSNode->layout.dimensions[CSS_WIDTH] = CSS_UNDEFINED; + _scrollerCSSNode->layout.dimensions[CSS_HEIGHT] = CSS_UNDEFINED; + } + +//#else + else + { + _flexScrollerCSSNode->copyStyle(_flexCssNode); + _flexScrollerCSSNode->copyMeasureFunc(_flexCssNode); + + if (_scrollDirection == WXScrollDirectionVertical) { + _flexScrollerCSSNode->setFlexDirection(WeexCore::kFlexDirectionColumn,NO); + _flexScrollerCSSNode->setStyleWidth(self.flexCssNode->getLayoutWidth(),NO); + _flexScrollerCSSNode->setStyleHeight(FlexUndefined); + } else { + _flexScrollerCSSNode->setFlexDirection(WeexCore::kFlexDirectionRow,NO); + _flexScrollerCSSNode->setStyleHeight(self.flexCssNode->getLayoutHeight()); + _flexScrollerCSSNode->setStyleWidth(FlexUndefined,NO); + } + _flexScrollerCSSNode->markDirty(); + std::pair<float, float> renderPageSize; + renderPageSize.first = self.weexInstance.frame.size.width; + renderPageSize.second = self.weexInstance.frame.size.height; + _flexScrollerCSSNode->calculateLayout(renderPageSize); + CGSize size = { + WXRoundPixelValue(_flexScrollerCSSNode->getLayoutWidth()), + WXRoundPixelValue(_flexScrollerCSSNode->getLayoutHeight()) + }; + + if (!CGSizeEqualToSize(size, _contentSize)) { + // content size + _contentSize = size; + [dirtyComponents addObject:self]; + } + } +//#endif + } + + [super _calculateFrameWithSuperAbsolutePosition:superAbsolutePosition gatherDirtyComponents:dirtyComponents]; +} + +@end