From 506c4dc014771f22a115696b5d7f98d5cae3b284 Mon Sep 17 00:00:00 2001 From: Tpphha Date: Tue, 27 Feb 2018 11:03:14 +0800 Subject: [PATCH 01/30] Changed remove redundant code The `addSubview` can removes the previous superview before making the receiver its new superview. --- LazyScrollView/TMMuiLazyScrollView.m | 3 --- 1 file changed, 3 deletions(-) diff --git a/LazyScrollView/TMMuiLazyScrollView.m b/LazyScrollView/TMMuiLazyScrollView.m index 9570bc1..fdcb735 100644 --- a/LazyScrollView/TMMuiLazyScrollView.m +++ b/LazyScrollView/TMMuiLazyScrollView.m @@ -489,9 +489,6 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa } if (self.autoAddSubview) { if (viewToShow.superview != self) { - if (viewToShow.superview) { - [viewToShow removeFromSuperview]; - } [self addSubview:viewToShow]; } } From bdeb3e33e7559397d0196630a84ef295832473bf Mon Sep 17 00:00:00 2001 From: Tpphha Date: Tue, 27 Feb 2018 11:05:14 +0800 Subject: [PATCH 02/30] Fixed typo Fixed typo --- LazyScrollView/TMMuiLazyScrollView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LazyScrollView/TMMuiLazyScrollView.m b/LazyScrollView/TMMuiLazyScrollView.m index fdcb735..e9f46ff 100644 --- a/LazyScrollView/TMMuiLazyScrollView.m +++ b/LazyScrollView/TMMuiLazyScrollView.m @@ -539,7 +539,7 @@ - (void)reloadData } else{ CGRect visibleBounds = self.bounds; - // 上下增加200像素的缓冲区 + // 上下增加 20point 的缓冲区 CGFloat minY = CGRectGetMinY(visibleBounds) - RenderBufferWindow; CGFloat maxY = CGRectGetMaxY(visibleBounds) + RenderBufferWindow; [self assembleSubviewsForReload:YES minY:minY maxY:maxY]; From 180992a3b5dd02f4baa683aebcb8f45fffcc5d46 Mon Sep 17 00:00:00 2001 From: Tpphha Date: Tue, 27 Feb 2018 14:59:20 +0800 Subject: [PATCH 03/30] Changed improve performance improve performance --- LazyScrollView/TMMuiLazyScrollView.h | 4 +- LazyScrollView/TMMuiLazyScrollView.m | 326 +++++++++++++-------------- 2 files changed, 156 insertions(+), 174 deletions(-) diff --git a/LazyScrollView/TMMuiLazyScrollView.h b/LazyScrollView/TMMuiLazyScrollView.h index dd025f0..b184de1 100644 --- a/LazyScrollView/TMMuiLazyScrollView.h +++ b/LazyScrollView/TMMuiLazyScrollView.h @@ -63,10 +63,10 @@ @property (nonatomic, assign) BOOL autoAddSubview; // Items which has been added to LazyScrollView. -@property (nonatomic, strong, readonly, nonnull) NSSet *visibleItems; +@property (nonatomic, strong, readonly, nonnull) NSSet *visibleItems; // Items which is in the visible screen area. // It is a sub set of "visibleItems". -@property (nonatomic, strong, readonly, nonnull) NSSet *inScreenVisibleItems; +@property (nonatomic, strong, readonly, nonnull) NSSet *inScreenVisibleItems; // Tangram can be footerView for TableView, this outerScrollView is your tableview. @property (nonatomic, weak, nullable) UIScrollView *outerScrollView; diff --git a/LazyScrollView/TMMuiLazyScrollView.m b/LazyScrollView/TMMuiLazyScrollView.m index e9f46ff..dc81c6a 100644 --- a/LazyScrollView/TMMuiLazyScrollView.m +++ b/LazyScrollView/TMMuiLazyScrollView.m @@ -47,100 +47,60 @@ - (void)setMuiID:(NSString *)muiID //**************************************************************** @interface TMMuiLazyScrollView() { - NSMutableSet *_visibleItems; - NSMutableSet *_inScreenVisibleItems; + NSMutableSet *_visibleItems; + NSMutableSet *_inScreenVisibleItems; + + // Store view models (TMMuiRectModel). + NSMutableArray *_itemsFrames; + + // Store reuseable cells by reuseIdentifier. The key is reuseIdentifier + // of views , value is an array that contains reuseable cells. + NSMutableDictionary *> *_recycledIdentifierItemsDic; + // Store reuseable cells by muiID. + NSMutableDictionary *_recycledMuiIDItemsDic; + + // Store view models below contentOffset of ScrollView + NSMutableSet *_firstSet; + // Store view models above contentOffset + height of ScrollView + NSMutableSet *_secondSet; + + // View Model sorted by Top Edge. + NSArray *_modelsSortedByTop; + // View Model sorted by Bottom Edge. + NSArray *_modelsSortedByBottom; + + // It is used to store views need to assign new value after reload. + NSMutableSet *_shouldReloadItems; + + // Store the times of view entered the screen, the key is muiID. + NSMutableDictionary *_enterDic; + + // Record current muiID of visible view for calculate. + // Will be used for dequeueReusableItem methods. + NSString *_currentVisibleItemMuiID; + + // Record muiIDs of visible items. Used for calc enter times. + NSSet *_muiIDOfVisibleViews; + // Store last time visible muiID. Used for calc enter times. + NSSet *_lastVisibleMuiID; + + TMMuiLazyScrollViewObserver *_outerScrollViewObserver; + + BOOL _forwardingDelegateCanPerformScrollViewDidScrollSelector; + + @package + // Record contentOffset of scrollview in previous time that calculate + // views to show + CGPoint _lastScrollOffset; } -// Store reuseable cells by reuseIdentifier. The key is reuseIdentifier -// of views , value is an array that contains reuseable cells. -@property (nonatomic, strong) NSMutableDictionary *recycledIdentifierItemsDic; -// Store reuseable cells by muiID. -@property (nonatomic, strong) NSMutableDictionary *recycledMuiIDItemsDic; - -// Store view models (TMMuiRectModel). -@property (nonatomic, strong) NSMutableArray *itemsFrames; - -// View Model sorted by Top Edge. -@property (nonatomic, strong) NSArray *modelsSortedByTop; -// View Model sorted by Bottom Edge. -@property (nonatomic, strong) NSArray *modelsSortedByBottom; - -// Store view models below contentOffset of ScrollView -@property (nonatomic, strong) NSMutableSet *firstSet; -// Store view models above contentOffset + height of ScrollView -@property (nonatomic, strong) NSMutableSet *secondSet; - -// Record contentOffset of scrollview in previous time that calculate -// views to show -@property (nonatomic, assign) CGPoint lastScrollOffset; - -// Record current muiID of visible view for calculate. -// Will be used for dequeueReusableItem methods. -@property (nonatomic, strong) NSString *currentVisibleItemMuiID; - -// It is used to store views need to assign new value after reload. -@property (nonatomic, strong) NSMutableSet *shouldReloadItems; - -// Record muiIDs of visible items. Used for calc enter times. -@property (nonatomic, strong) NSSet *muiIDOfVisibleViews; -// Store the times of view entered the screen, the key is muiID. -@property (nonatomic, strong) NSMutableDictionary *enterDict; -// Store last time visible muiID. Used for calc enter times. -@property (nonatomic, strong) NSMutableSet *lastVisibleMuiID; - -@property (nonatomic, strong) TMMuiLazyScrollViewObserver *outerScrollViewObserver; - - @end //**************************************************************** @implementation TMMuiLazyScrollView -@dynamic visibleItems, inScreenVisibleItems; - #pragma mark - Getter & Setter - -- (NSMutableSet *)shouldReloadItems -{ - if (nil == _shouldReloadItems) { - _shouldReloadItems = [[NSMutableSet alloc] init]; - } - return _shouldReloadItems; -} - -- (NSArray *)modelsSortedByTop -{ - if (!_modelsSortedByTop){ - _modelsSortedByTop = [[NSArray alloc] init]; - } - return _modelsSortedByTop; -} - -- (NSArray *)modelsSortedByBottom -{ - if (!_modelsSortedByBottom) { - _modelsSortedByBottom = [[NSArray alloc]init]; - } - return _modelsSortedByBottom; -} - -- (NSMutableDictionary *)enterDict -{ - if (nil == _enterDict) { - _enterDict = [[NSMutableDictionary alloc]init]; - } - return _enterDict; -} - -- (NSMutableDictionary *)recycledMuiIDItemsDic -{ - if(nil == _recycledMuiIDItemsDic) { - _recycledMuiIDItemsDic = [[NSMutableDictionary alloc]init]; - } - return _recycledMuiIDItemsDic; -} - - (NSSet *)inScreenVisibleItems { return [_inScreenVisibleItems copy]; @@ -161,16 +121,29 @@ - (void)setFrame:(CGRect)frame -(void)setOuterScrollView:(UIScrollView *)outerScrollView { _outerScrollView = outerScrollView; - if (self.outerScrollViewObserver == nil) { - self.outerScrollViewObserver = [[TMMuiLazyScrollViewObserver alloc]init]; - self.outerScrollViewObserver.lazyScrollView = self; + if (_outerScrollViewObserver == nil) { + _outerScrollViewObserver = [[TMMuiLazyScrollViewObserver alloc]init]; + _outerScrollViewObserver.lazyScrollView = self; } @try { - [outerScrollView removeObserver:self.outerScrollViewObserver forKeyPath:@"contentOffset"]; + [outerScrollView removeObserver:_outerScrollViewObserver forKeyPath:@"contentOffset"]; } @catch (NSException * __unused exception) {} - [outerScrollView addObserver:self.outerScrollViewObserver forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; + [outerScrollView addObserver:_outerScrollViewObserver + forKeyPath:@"contentOffset" + options:NSKeyValueObservingOptionNew + context:nil]; +} + +- (void)setForwardingDelegate:(id)forwardingDelegate { + _forwardingDelegateCanPerformScrollViewDidScrollSelector = NO; + + _forwardingDelegate = forwardingDelegate; + + _forwardingDelegateCanPerformScrollViewDidScrollSelector = + [_forwardingDelegate conformsToProtocol:@protocol(UIScrollViewDelegate)] && + [_forwardingDelegate respondsToSelector:@selector(scrollViewDidScroll:)]; } #pragma mark - Lifecycle @@ -182,12 +155,23 @@ - (id)initWithFrame:(CGRect)frame self.autoresizesSubviews = NO; self.showsHorizontalScrollIndicator = NO; self.showsVerticalScrollIndicator = NO; + + _shouldReloadItems = [[NSMutableSet alloc] init]; + + _modelsSortedByTop = [[NSArray alloc] init]; + _modelsSortedByBottom = [[NSArray alloc]init]; + _recycledIdentifierItemsDic = [[NSMutableDictionary alloc] init]; _visibleItems = [[NSMutableSet alloc] init]; _inScreenVisibleItems = [[NSMutableSet alloc] init]; + _itemsFrames = [[NSMutableArray alloc] init]; + _firstSet = [[NSMutableSet alloc] initWithCapacity:30]; _secondSet = [[NSMutableSet alloc] initWithCapacity:30]; + + _enterDic = [[NSMutableDictionary alloc] init]; + self.delegate = self; } return self; @@ -217,8 +201,8 @@ - (void)didScroll // times of calculating. CGFloat currentY = self.contentOffset.y; CGFloat buffer = RenderBufferWindow / 2; - if (buffer < ABS(currentY - self.lastScrollOffset.y)) { - self.lastScrollOffset = self.contentOffset; + if (buffer < ABS(currentY - _lastScrollOffset.y)) { + _lastScrollOffset = self.contentOffset; [self assembleSubviews]; [self findViewsInVisibleRect]; } @@ -229,19 +213,17 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView { [self didScroll]; - if (self.forwardingDelegate && - [self.forwardingDelegate conformsToProtocol:@protocol(UIScrollViewDelegate)] && - [self.forwardingDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) { - [self.forwardingDelegate scrollViewDidScroll:self]; + if (_forwardingDelegateCanPerformScrollViewDidScrollSelector) { + [_forwardingDelegate scrollViewDidScroll:scrollView]; } } - (id)forwardingTargetForSelector:(SEL)aSelector { - if (self.forwardingDelegate) { + if (_forwardingDelegate) { struct objc_method_description md = protocol_getMethodDescription(@protocol(UIScrollViewDelegate), aSelector, NO, YES); if (NULL != md.name) { - return self.forwardingDelegate; + return _forwardingDelegate; } } return [super forwardingTargetForSelector:aSelector]; @@ -250,10 +232,10 @@ - (id)forwardingTargetForSelector:(SEL)aSelector - (BOOL)respondsToSelector:(SEL)aSelector { BOOL result = [super respondsToSelector:aSelector]; - if (NO == result && self.forwardingDelegate) { + if (NO == result && _forwardingDelegate) { struct objc_method_description md = protocol_getMethodDescription(@protocol(UIScrollViewDelegate), aSelector, NO, YES); if (NULL != md.name) { - result = [self.forwardingDelegate respondsToSelector:aSelector]; + result = [_forwardingDelegate respondsToSelector:aSelector]; } } return result; @@ -264,7 +246,7 @@ - (BOOL)respondsToSelector:(SEL)aSelector // Do Binary search here to find index in view model array. - (NSUInteger)binarySearchForIndex:(NSArray *)frameArray baseLine:(CGFloat)baseLine isFromTop:(BOOL)fromTop { - NSInteger min = 0 ; + NSInteger min = 0; NSInteger max = frameArray.count - 1; NSInteger mid = ceilf((min + max) * 0.5f); while (mid > min && mid < max) { @@ -304,55 +286,55 @@ - (NSUInteger)binarySearchForIndex:(NSArray *)frameArray baseLine:(CGFloat)baseL // Get which views should be shown in LazyScrollView. // The kind of values In NSSet is muiID. -- (NSSet *)showingItemIndexSetFrom:(CGFloat)startY to:(CGFloat)endY +- (NSSet *)showingItemIndexSetFrom:(CGFloat)startY to:(CGFloat)endY { - NSUInteger endBottomIndex = [self binarySearchForIndex:self.modelsSortedByBottom baseLine:startY isFromTop:NO]; - [self.firstSet removeAllObjects]; + NSUInteger endBottomIndex = [self binarySearchForIndex:_modelsSortedByBottom baseLine:startY isFromTop:NO]; + [_firstSet removeAllObjects]; for (NSUInteger i = 0; i <= endBottomIndex; i++) { - TMMuiRectModel *model = [self.modelsSortedByBottom tm_safeObjectAtIndex:i]; + TMMuiRectModel *model = [_modelsSortedByBottom tm_safeObjectAtIndex:i]; if (model != nil) { - [self.firstSet addObject:model.muiID]; + [_firstSet addObject:model.muiID]; } } - NSUInteger endTopIndex = [self binarySearchForIndex:self.modelsSortedByTop baseLine:endY isFromTop:YES]; - [self.secondSet removeAllObjects]; + NSUInteger endTopIndex = [self binarySearchForIndex:_modelsSortedByTop baseLine:endY isFromTop:YES]; + [_secondSet removeAllObjects]; for (NSInteger i = 0; i <= endTopIndex; i++) { - TMMuiRectModel *model = [self.modelsSortedByTop tm_safeObjectAtIndex:i]; + TMMuiRectModel *model = [_modelsSortedByTop tm_safeObjectAtIndex:i]; if (model != nil) { - [self.secondSet addObject:model.muiID]; + [_secondSet addObject:model.muiID]; } } - [self.firstSet intersectSet:self.secondSet]; - return [self.firstSet copy]; + [_firstSet intersectSet:_secondSet]; + return [_firstSet copy]; } // Get view models from delegate. Create to indexes for sorting. - (void)creatScrollViewIndex { NSUInteger count = 0; - if (self.dataSource && - [self.dataSource conformsToProtocol:@protocol(TMMuiLazyScrollViewDataSource)] && - [self.dataSource respondsToSelector:@selector(numberOfItemInScrollView:)]) { - count = [self.dataSource numberOfItemInScrollView:self]; + if (_dataSource && + [_dataSource conformsToProtocol:@protocol(TMMuiLazyScrollViewDataSource)] && + [_dataSource respondsToSelector:@selector(numberOfItemInScrollView:)]) { + count = [_dataSource numberOfItemInScrollView:self]; } - [self.itemsFrames removeAllObjects]; - for (NSUInteger i = 0 ; i< count ; i++) { - TMMuiRectModel *rectmodel; - if (self.dataSource && - [self.dataSource conformsToProtocol:@protocol(TMMuiLazyScrollViewDataSource)] && - [self.dataSource respondsToSelector:@selector(scrollView: rectModelAtIndex:)]) { - rectmodel = [self.dataSource scrollView:self rectModelAtIndex:i]; + [_itemsFrames removeAllObjects]; + for (NSUInteger i = 0 ; i < count ; i++) { + TMMuiRectModel *rectmodel = nil; + if (_dataSource && + [_dataSource conformsToProtocol:@protocol(TMMuiLazyScrollViewDataSource)] && + [_dataSource respondsToSelector:@selector(scrollView:rectModelAtIndex:)]) { + rectmodel = [_dataSource scrollView:self rectModelAtIndex:i]; if (rectmodel.muiID.length == 0) { rectmodel.muiID = [NSString stringWithFormat:@"%lu", (unsigned long)i]; } } - [self.itemsFrames tm_safeAddObject:rectmodel]; + [_itemsFrames tm_safeAddObject:rectmodel]; } - self.modelsSortedByTop = [self.itemsFrames sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { + _modelsSortedByTop = [_itemsFrames sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { CGRect rect1 = [(TMMuiRectModel *) obj1 absRect]; CGRect rect2 = [(TMMuiRectModel *) obj2 absRect]; if (rect1.origin.y < rect2.origin.y) { @@ -364,7 +346,7 @@ - (void)creatScrollViewIndex } }]; - self.modelsSortedByBottom = [self.itemsFrames sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { + _modelsSortedByBottom = [_itemsFrames sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { CGRect rect1 = [(TMMuiRectModel *) obj1 absRect]; CGRect rect2 = [(TMMuiRectModel *) obj2 absRect]; CGFloat bottom1 = CGRectGetMaxY(rect1); @@ -381,33 +363,33 @@ - (void)creatScrollViewIndex - (void)findViewsInVisibleRect { - NSMutableSet *itemViewSet = [self.muiIDOfVisibleViews mutableCopy]; - [itemViewSet minusSet:self.lastVisibleMuiID]; + NSMutableSet *itemViewSet = [_muiIDOfVisibleViews mutableCopy]; + [itemViewSet minusSet:_lastVisibleMuiID]; for (UIView *view in _visibleItems) { if (view && [itemViewSet containsObject:view.muiID]) { if ([view conformsToProtocol:@protocol(TMMuiLazyScrollViewCellProtocol)] && [view respondsToSelector:@selector(mui_didEnterWithTimes:)]) { NSUInteger times = 0; - if ([self.enterDict tm_safeObjectForKey:view.muiID] != nil) { - times = [self.enterDict tm_integerForKey:view.muiID] + 1; + if ([_enterDic tm_safeObjectForKey:view.muiID] != nil) { + times = [_enterDic tm_integerForKey:view.muiID] + 1; } NSNumber *showTimes = [NSNumber numberWithUnsignedInteger:times]; - [self.enterDict tm_safeSetObject:showTimes forKey:view.muiID]; + [_enterDic tm_safeSetObject:showTimes forKey:view.muiID]; [(UIView *)view mui_didEnterWithTimes:times]; } } } - self.lastVisibleMuiID = [self.muiIDOfVisibleViews copy]; + _lastVisibleMuiID = [_muiIDOfVisibleViews copy]; } // A simple method to show view that should be shown in LazyScrollView. - (void)assembleSubviews { - if (self.outerScrollView) { - CGPoint pointInScrollView = [self.superview convertPoint:self.frame.origin toView:self.outerScrollView]; - CGFloat minY = self.outerScrollView.contentOffset.y - pointInScrollView.y - RenderBufferWindow/2 ; + if (_outerScrollView) { + CGPoint pointInScrollView = [self.superview convertPoint:self.frame.origin toView:_outerScrollView]; + CGFloat minY = _outerScrollView.contentOffset.y - pointInScrollView.y - RenderBufferWindow/2 ; //maxY 计算的逻辑,需要修改,增加的height,需要计算的更加明确 - CGFloat maxY = self.outerScrollView.contentOffset.y + self.outerScrollView.frame.size.height - pointInScrollView.y + RenderBufferWindow/2; + CGFloat maxY = _outerScrollView.contentOffset.y + _outerScrollView.frame.size.height - pointInScrollView.y + RenderBufferWindow/2; if (maxY > 0) { [self assembleSubviewsForReload:NO minY:minY maxY:maxY]; } @@ -424,14 +406,14 @@ - (void)assembleSubviews - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloat)maxY { - NSSet *itemShouldShowSet = [self showingItemIndexSetFrom:minY to:maxY]; - if (self.outerScrollView) { - self.muiIDOfVisibleViews = [self showingItemIndexSetFrom:minY to:maxY]; + NSSet *itemShouldShowSet = [self showingItemIndexSetFrom:minY to:maxY]; + if (_outerScrollView) { + _muiIDOfVisibleViews = [self showingItemIndexSetFrom:minY to:maxY]; } else{ - self.muiIDOfVisibleViews = [self showingItemIndexSetFrom:CGRectGetMinY(self.bounds) to:CGRectGetMaxY(self.bounds)]; + _muiIDOfVisibleViews = [self showingItemIndexSetFrom:CGRectGetMinY(self.bounds) to:CGRectGetMaxY(self.bounds)]; } - NSMutableSet *recycledItems = [[NSMutableSet alloc] init]; + NSMutableSet *recycledItems = [[NSMutableSet alloc] init]; // For recycling. Find which views should not in visible area. NSSet *visibles = [_visibleItems copy]; for (UIView *view in visibles) { @@ -444,18 +426,18 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa // If this view should be recycled and the length of its reuseidentifier is over 0. if (view.reuseIdentifier.length > 0) { // Then recycle the view. - NSMutableSet *recycledIdentifierSet = [self recycledIdentifierSet:view.reuseIdentifier]; + NSMutableSet *recycledIdentifierSet = [self recycledIdentifierSet:view.reuseIdentifier]; [recycledIdentifierSet addObject:view]; view.hidden = YES; [recycledItems addObject:view]; // Also add to muiID recycle dict. - [self.recycledMuiIDItemsDic tm_safeSetObject:view forKey:view.muiID]; + [_recycledMuiIDItemsDic tm_safeSetObject:view forKey:view.muiID]; } else if(isReload && view.muiID) { // Need to reload unreusable views. - [self.shouldReloadItems addObject:view.muiID]; + [_shouldReloadItems addObject:view.muiID]; } } else if (isReload && view.muiID) { - [self.shouldReloadItems addObject:view.muiID]; + [_shouldReloadItems addObject:view.muiID]; } } @@ -463,19 +445,19 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa [recycledItems removeAllObjects]; // Creare new view. for (NSString *muiID in itemShouldShowSet) { - BOOL shouldReload = isReload || [self.shouldReloadItems containsObject:muiID]; - if (![self isCellVisible:muiID] || [self.shouldReloadItems containsObject:muiID]) { - if (self.dataSource && - [self.dataSource conformsToProtocol:@protocol(TMMuiLazyScrollViewDataSource)] && - [self.dataSource respondsToSelector:@selector(scrollView:itemByMuiID:)]) { + BOOL shouldReload = isReload || [_shouldReloadItems containsObject:muiID]; + if (![self isCellVisible:muiID] || [_shouldReloadItems containsObject:muiID]) { + if (_dataSource && + [_dataSource conformsToProtocol:@protocol(TMMuiLazyScrollViewDataSource)] && + [_dataSource respondsToSelector:@selector(scrollView:itemByMuiID:)]) { // Create view by dataSource. // If you call dequeue method in your dataSource, the currentVisibleItemMuiID // will be used for searching reusable view. if (shouldReload) { - self.currentVisibleItemMuiID = muiID; + _currentVisibleItemMuiID = muiID; } UIView *viewToShow = [self.dataSource scrollView:self itemByMuiID:muiID]; - self.currentVisibleItemMuiID = nil; + _currentVisibleItemMuiID = nil; // Call afterGetView. if ([viewToShow conformsToProtocol:@protocol(TMMuiLazyScrollViewCellProtocol)] && [viewToShow respondsToSelector:@selector(mui_afterGetView)]) { @@ -494,7 +476,7 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa } } } - [self.shouldReloadItems removeObject:muiID]; + [_shouldReloadItems removeObject:muiID]; } } [_inScreenVisibleItems removeAllObjects]; @@ -510,16 +492,16 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa } // Get NSSet accroding to reuse identifier. -- (NSMutableSet *)recycledIdentifierSet:(NSString *)reuseIdentifier; +- (NSMutableSet *)recycledIdentifierSet:(NSString *)reuseIdentifier; { if (reuseIdentifier.length == 0) { return nil; } - NSMutableSet *result = [self.recycledIdentifierItemsDic tm_safeObjectForKey:reuseIdentifier]; + NSMutableSet *result = [_recycledIdentifierItemsDic tm_safeObjectForKey:reuseIdentifier]; if (result == nil) { result = [[NSMutableSet alloc] init]; - [self.recycledIdentifierItemsDic setObject:result forKey:reuseIdentifier]; + [_recycledIdentifierItemsDic setObject:result forKey:reuseIdentifier]; } return result; } @@ -528,11 +510,11 @@ - (NSMutableSet *)recycledIdentifierSet:(NSString *)reuseIdentifier; - (void)reloadData { [self creatScrollViewIndex]; - if (self.itemsFrames.count > 0) { - if (self.outerScrollView) { - CGRect rectInScrollView = [self convertRect:self.frame toView:self.outerScrollView]; - CGFloat minY = self.outerScrollView.contentOffset.y - rectInScrollView.origin.y - RenderBufferWindow; - CGFloat maxY = self.outerScrollView.contentOffset.y + self.outerScrollView.frame.size.height - rectInScrollView.origin.y + self.outerScrollView.frame.size.height + RenderBufferWindow; + if (_itemsFrames.count > 0) { + if (_outerScrollView) { + CGRect rectInScrollView = [self convertRect:self.frame toView:_outerScrollView]; + CGFloat minY = _outerScrollView.contentOffset.y - rectInScrollView.origin.y - RenderBufferWindow; + CGFloat maxY = _outerScrollView.contentOffset.y + _outerScrollView.frame.size.height - rectInScrollView.origin.y + _outerScrollView.frame.size.height + RenderBufferWindow; if (maxY > 0) { [self assembleSubviewsForReload:YES minY:minY maxY:maxY]; } @@ -575,22 +557,22 @@ - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSSt { UIView *view = nil; - if (self.currentVisibleItemMuiID) { + if (_currentVisibleItemMuiID) { NSSet *visibles = _visibleItems; for (UIView *v in visibles) { - if ([v.muiID isEqualToString:self.currentVisibleItemMuiID] && [v.reuseIdentifier isEqualToString:identifier]) { + if ([v.muiID isEqualToString:_currentVisibleItemMuiID] && [v.reuseIdentifier isEqualToString:identifier]) { view = v; break; } } } else if(muiID && [muiID isKindOfClass:[NSString class]] && muiID.length > 0) { // Try to get reusable view from muiID dict. - view = [self.recycledMuiIDItemsDic tm_safeObjectForKey:muiID class:[UIView class]]; + view = [_recycledMuiIDItemsDic tm_safeObjectForKey:muiID class:[UIView class]]; if (view && view.reuseIdentifier.length > 0 && [view.reuseIdentifier isEqualToString:identifier]) { NSMutableSet *recycledIdentifierSet = [self recycledIdentifierSet:identifier]; if (muiID && [muiID isKindOfClass:[NSString class]] && muiID.length > 0) { - [self.recycledMuiIDItemsDic removeObjectForKey:muiID]; + [_recycledMuiIDItemsDic removeObjectForKey:muiID]; } [recycledIdentifierSet removeObject:view]; view.gestureRecognizers = nil; @@ -605,7 +587,7 @@ - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSSt if (view && view.reuseIdentifier.length > 0) { // If exist reusable view, remove it from recycledSet and recycledMuiIDItemsDic. if (view.muiID && [view.muiID isKindOfClass:[NSString class]] && view.muiID.length > 0) { - [self.recycledMuiIDItemsDic removeObjectForKey:view.muiID]; + [_recycledMuiIDItemsDic removeObjectForKey:view.muiID]; } [recycledIdentifierSet removeObject:view]; // Then remove all gesture recognizers of it. @@ -637,8 +619,8 @@ - (BOOL)isCellVisible:(NSString *)muiID - (void)resetViewEnterTimes { - [self.enterDict removeAllObjects]; - self.lastVisibleMuiID = nil; + [_enterDic removeAllObjects]; + _lastVisibleMuiID = nil; } @end @@ -651,10 +633,10 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N { CGPoint newPoint = [[change objectForKey:NSKeyValueChangeNewKey] CGPointValue]; CGFloat buffer = RenderBufferWindow / 2; - if (buffer < ABS(newPoint.y - self.lazyScrollView.lastScrollOffset.y)) { - self.lazyScrollView.lastScrollOffset = newPoint; - [self.lazyScrollView assembleSubviews]; - [self.lazyScrollView findViewsInVisibleRect]; + if (buffer < ABS(newPoint.y - _lazyScrollView->_lastScrollOffset.y)) { + _lazyScrollView->_lastScrollOffset = newPoint; + [_lazyScrollView assembleSubviews]; + [_lazyScrollView findViewsInVisibleRect]; } } } From 3cf054b9a5df3cceb6ca85491fb2b79672bc7ea5 Mon Sep 17 00:00:00 2001 From: Tpphha Date: Tue, 27 Feb 2018 21:14:25 +0800 Subject: [PATCH 04/30] Fixed missing initialization. Fixed missing initialization. --- LazyScrollView/TMMuiLazyScrollView.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/LazyScrollView/TMMuiLazyScrollView.m b/LazyScrollView/TMMuiLazyScrollView.m index dc81c6a..3cdcfbb 100644 --- a/LazyScrollView/TMMuiLazyScrollView.m +++ b/LazyScrollView/TMMuiLazyScrollView.m @@ -162,6 +162,8 @@ - (id)initWithFrame:(CGRect)frame _modelsSortedByBottom = [[NSArray alloc]init]; _recycledIdentifierItemsDic = [[NSMutableDictionary alloc] init]; + _recycledMuiIDItemsDic = [[NSMutableDictionary alloc] init]; + _visibleItems = [[NSMutableSet alloc] init]; _inScreenVisibleItems = [[NSMutableSet alloc] init]; @@ -456,7 +458,7 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa if (shouldReload) { _currentVisibleItemMuiID = muiID; } - UIView *viewToShow = [self.dataSource scrollView:self itemByMuiID:muiID]; + UIView *viewToShow = [_dataSource scrollView:self itemByMuiID:muiID]; _currentVisibleItemMuiID = nil; // Call afterGetView. if ([viewToShow conformsToProtocol:@protocol(TMMuiLazyScrollViewCellProtocol)] && @@ -469,7 +471,7 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa if (![_visibleItems containsObject:viewToShow]) { [_visibleItems addObject:viewToShow]; } - if (self.autoAddSubview) { + if (_autoAddSubview) { if (viewToShow.superview != self) { [self addSubview:viewToShow]; } From f65f4c1505d5857f28fc3a15c2349be4ef8915ee Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Wed, 28 Feb 2018 17:39:43 +0800 Subject: [PATCH 05/30] update readme & copyright --- LICENSE | 2 +- LazyScroll.podspec | 11 ++----- LazyScrollView/TMMuiLazyScrollView.h | 2 +- LazyScrollView/TMMuiLazyScrollView.m | 2 +- .../TMMuiLazyScrollViewCellProtocol.h | 2 +- LazyScrollView/TMMuiRectModel.h | 2 +- LazyScrollView/TMMuiRectModel.m | 2 +- .../LazyScrollViewDemo/AppDelegate.h | 2 +- .../LazyScrollViewDemo/AppDelegate.m | 2 +- .../LazyScrollViewDemo/ViewController.h | 2 +- .../LazyScrollViewDemo/ViewController.m | 2 +- LazyScrollViewDemo/LazyScrollViewDemo/main.m | 3 +- README.md | 14 +++----- TMUtils.podspec | 2 +- TMUtils/TMUtils.h | 4 +-- TMUtils/TMUtils.m | 4 +-- update_header.py | 32 +++++++++++++++++++ 17 files changed, 56 insertions(+), 34 deletions(-) create mode 100644 update_header.py diff --git a/LICENSE b/LICENSE index 9965f12..3e273a1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2017 Alibaba +Copyright (c) 2015-2018 Alibaba Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/LazyScroll.podspec b/LazyScroll.podspec index 33d58c7..cd0a8bf 100644 --- a/LazyScroll.podspec +++ b/LazyScroll.podspec @@ -2,22 +2,17 @@ Pod::Spec.new do |s| s.name = "LazyScroll" s.version = "1.0.0" - s.summary = "A ScrollView to resolve the problem of reusability of views." - - s.description = <<-DESC - It reply an another way to control reuse in a ScrollView, it depends on give a special reuse identifier to every view controlled in LazyScrollView. - DESC - + s.summary = "A ScrollView to resolve the problem of reusability of views." s.homepage = "https://github.com/alibaba/LazyScrollView" s.license = { :type => 'MIT' } s.author = { "fydx" => "lbgg918@gmail.com", "HarrisonXi" => "gpra8764@gmail.com" } s.platform = :ios - s.ios.deployment_target = "5.0" + s.ios.deployment_target = "7.0" s.source = { :git => "https://github.com/alibaba/LazyScrollView.git", :tag => "1.0.0" } s.source_files = "LazyScrollView/*.{h,m}" s.requires_arc = true - s.dependency 'TMUtils', '~> 1.0' + s.dependency 'TMUtils', '~> 1.0.0' end diff --git a/LazyScrollView/TMMuiLazyScrollView.h b/LazyScrollView/TMMuiLazyScrollView.h index b184de1..8d6eb60 100644 --- a/LazyScrollView/TMMuiLazyScrollView.h +++ b/LazyScrollView/TMMuiLazyScrollView.h @@ -2,7 +2,7 @@ // TMMuiLazyScrollView.h // LazyScrollView // -// Copyright (c) 2015-2017 Alibaba. All rights reserved. +// Copyright (c) 2015-2018 Alibaba. All rights reserved. // #import diff --git a/LazyScrollView/TMMuiLazyScrollView.m b/LazyScrollView/TMMuiLazyScrollView.m index 3cdcfbb..b245b23 100644 --- a/LazyScrollView/TMMuiLazyScrollView.m +++ b/LazyScrollView/TMMuiLazyScrollView.m @@ -2,7 +2,7 @@ // TMMuiLazyScrollView.m // LazyScrollView // -// Copyright (c) 2015-2017 Alibaba. All rights reserved. +// Copyright (c) 2015-2018 Alibaba. All rights reserved. // #import "TMMuiLazyScrollView.h" diff --git a/LazyScrollView/TMMuiLazyScrollViewCellProtocol.h b/LazyScrollView/TMMuiLazyScrollViewCellProtocol.h index 3404304..3baf181 100644 --- a/LazyScrollView/TMMuiLazyScrollViewCellProtocol.h +++ b/LazyScrollView/TMMuiLazyScrollViewCellProtocol.h @@ -2,7 +2,7 @@ // TMMuiLazyScrollViewCellProtocol.h // LazyScrollView // -// Copyright (c) 2017 Alibaba. All rights reserved. +// Copyright (c) 2015-2018 Alibaba. All rights reserved. // /** diff --git a/LazyScrollView/TMMuiRectModel.h b/LazyScrollView/TMMuiRectModel.h index 200e020..4f51e7b 100644 --- a/LazyScrollView/TMMuiRectModel.h +++ b/LazyScrollView/TMMuiRectModel.h @@ -2,7 +2,7 @@ // TMMuiRectModel.h // LazyScrollView // -// Copyright (c) 2017 Alibaba. All rights reserved. +// Copyright (c) 2015-2018 Alibaba. All rights reserved. // #import diff --git a/LazyScrollView/TMMuiRectModel.m b/LazyScrollView/TMMuiRectModel.m index 9023d27..827b21f 100644 --- a/LazyScrollView/TMMuiRectModel.m +++ b/LazyScrollView/TMMuiRectModel.m @@ -2,7 +2,7 @@ // TMMuiRectModel.m // LazyScrollView // -// Copyright (c) 2017 Alibaba. All rights reserved. +// Copyright (c) 2015-2018 Alibaba. All rights reserved. // #import "TMMuiRectModel.h" diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.h b/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.h index 2f24573..e448d2f 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.h +++ b/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.h @@ -2,7 +2,7 @@ // AppDelegate.h // LazyScrollViewDemo // -// Copyright (c) 2017 Alibaba. All rights reserved. +// Copyright (c) 2015-2018 Alibaba. All rights reserved. // #import diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.m b/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.m index 756607b..3a6ccde 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.m +++ b/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.m @@ -2,7 +2,7 @@ // AppDelegate.m // LazyScrollViewDemo // -// Copyright (c) 2017 Alibaba. All rights reserved. +// Copyright (c) 2015-2018 Alibaba. All rights reserved. // #import "AppDelegate.h" diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.h b/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.h index 793ce05..bafcb4e 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.h +++ b/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.h @@ -2,7 +2,7 @@ // ViewController.h // LazyScrollViewDemo // -// Copyright (c) 2017 Alibaba. All rights reserved. +// Copyright (c) 2015-2018 Alibaba. All rights reserved. // #import diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.m index 67db586..98641e4 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.m +++ b/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.m @@ -2,7 +2,7 @@ // ViewController.m // LazyScrollViewDemo // -// Copyright (c) 2017 Alibaba. All rights reserved. +// Copyright (c) 2015-2018 Alibaba. All rights reserved. // #import "ViewController.h" diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/main.m b/LazyScrollViewDemo/LazyScrollViewDemo/main.m index c531ed8..498ab46 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/main.m +++ b/LazyScrollViewDemo/LazyScrollViewDemo/main.m @@ -2,8 +2,7 @@ // main.m // LazyScrollViewDemo // -// Created by xiaoxia on 2017/2/27. -// Copyright © 2017年 taobao. All rights reserved. +// Copyright (c) 2015-2018 Alibaba. All rights reserved. // #import diff --git a/README.md b/README.md index 01e595e..c6ba53e 100644 --- a/README.md +++ b/README.md @@ -2,21 +2,17 @@ # LazyScrollView -[中文说明](http://pingguohe.net/2016/01/31/lazyscroll.html) [中文Demo说明](http://pingguohe.net/2017/03/02/lazyScrollView-demo.html) +[中文说明](http://pingguohe.net/2016/01/31/lazyscroll.html) -> 依赖 LazyScrollView,我们创建了一个模块化页面UI解决方案,详情可见 [Tangram-iOS](https://github.com/alibaba/tangram-ios)。 +> 基于 LazyScrollView,我们创造了一个动态创建模块化 UI 页面的解决方案,详情可见 [Tangram-iOS](https://github.com/alibaba/tangram-ios)。 LazyScrollView is an iOS ScrollView, to resolve the problem of reusability of views. -We reply another way to control reuse in a ScrollView, it depends on give a special reuse identifier to every view controlled in LazyScrollView. - Comparing to UITableView, LazyScrollView can easily create different layout, instead of the single row flow layout. Comparing to UICollectionView, LazyScrollView can create views without Grid layout, and provides a easier way to create different kinds of layous in a ScrollView. -The system requirement is iOS 5+. - -> We create a modular UI solution for building native page dynamically based on `LazyScrollView`, you can see more info from this repo: [Tangram-iOS](https://github.com/alibaba/tangram-ios) +> We create a modular UI solution for building UI page dynamically based on `LazyScrollView`, you can see more info from this repo: [Tangram-iOS](https://github.com/alibaba/tangram-ios) # Installation @@ -24,7 +20,7 @@ LazyScroll is available as `LazyScroll` in CocoaPods. pod 'LazyScroll' -If you don't want to use cocoapods, you can also manually add the files in `LazyScrollView` folder into your Xcode project. +You can also download the source files from [release page](https://github.com/alibaba/LazyScrollView/releases) and add them into your project manually. # Usage @@ -70,4 +66,4 @@ Finally, do reload: [scrollview reloadData]; ``` -To view detailed usage, please clone the repo and open the demo project. +For more details, please clone the repo and open the demo project. diff --git a/TMUtils.podspec b/TMUtils.podspec index 4fc4595..947a51f 100644 --- a/TMUtils.podspec +++ b/TMUtils.podspec @@ -7,7 +7,7 @@ Pod::Spec.new do |s| s.license = {:type => 'MIT'} s.author = { "HarrisonXi" => "gpra8764@gmail.com" } s.platform = :ios - s.ios.deployment_target = "5.0" + s.ios.deployment_target = "7.0" s.source = { :git => "https://github.com/alibaba/LazyScrollView.git", :tag => "1.0.0" } s.source_files = "TMUtils/*.{h,m}" s.requires_arc = true diff --git a/TMUtils/TMUtils.h b/TMUtils/TMUtils.h index d28f688..ea5083e 100644 --- a/TMUtils/TMUtils.h +++ b/TMUtils/TMUtils.h @@ -1,8 +1,8 @@ // // TMUtils.h -// LazyScrollView +// TMUtils // -// Copyright (c) 2017 Alibaba. All rights reserved. +// Copyright (c) 2015-2018 Alibaba. All rights reserved. // #import diff --git a/TMUtils/TMUtils.m b/TMUtils/TMUtils.m index e1a3d48..8c52ea7 100644 --- a/TMUtils/TMUtils.m +++ b/TMUtils/TMUtils.m @@ -1,8 +1,8 @@ // // TMUtils.m -// LazyScrollView +// TMUtils // -// Copyright (c) 2017 Alibaba. All rights reserved. +// Copyright (c) 2015-2018 Alibaba. All rights reserved. // #import "TMUtils.h" diff --git a/update_header.py b/update_header.py new file mode 100644 index 0000000..49fdef0 --- /dev/null +++ b/update_header.py @@ -0,0 +1,32 @@ +# -*- coding:utf-8 -*- +import re +import sys,os + +def updateHeader(DIR, PROJ): + for path in os.listdir(DIR): + fullPath = os.path.join(DIR, path) + if os.path.isdir(fullPath): + if path != "Pods": + updateHeader(fullPath, PROJ) + elif os.path.isfile(fullPath): + if path.lower().endswith('.m') or path.lower().endswith('.h'): + print('Updating: %s' % (path)) + codeFile = open(fullPath, 'r+') + content = codeFile.read() + content = re.sub('^(//[^\n]*\n)+//(?P[^\n]*)\n', + '//\n' + + '// ' + path + '\n' + + '// ' + PROJ + '\n' + + '//\n' + + '// Copyright (c) 2015-2018 Alibaba. All rights reserved.\n' + + '//' + '\g' + '\n', + content) + codeFile.seek(0) + codeFile.write(content) + codeFile.truncate() + codeFile.close() + +updateHeader(os.path.join(sys.path[0], 'LazyScrollView'), 'LazyScrollView') +updateHeader(os.path.join(sys.path[0], 'LazyScrollViewDemo'), 'LazyScrollViewDemo') +updateHeader(os.path.join(sys.path[0], 'TMUtils'), 'TMUtils') +print('Header updating is done.') From c73d022eb1b8e18785fd96ec6a77a7e4da7258c3 Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Wed, 28 Feb 2018 19:20:32 +0800 Subject: [PATCH 06/30] move Podffile & renew project --- .../project.pbxproj | 234 ++++++++---------- .../LazyScrollViewDemo/AppDelegate.m | 42 +--- .../AppIcon.appiconset/Contents.json | 60 +++++ .../Base.lproj/LaunchScreen.storyboard | 14 +- .../Base.lproj/Main.storyboard | 26 -- .../LazyScrollViewDemo/Info.plist | 11 +- LazyScrollViewDemo/Podfile | 8 - Podfile | 12 + 8 files changed, 205 insertions(+), 202 deletions(-) delete mode 100644 LazyScrollViewDemo/LazyScrollViewDemo/Base.lproj/Main.storyboard delete mode 100644 LazyScrollViewDemo/Podfile create mode 100644 Podfile diff --git a/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj b/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj index d098f34..85ae07e 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj +++ b/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj @@ -3,102 +3,91 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 48; objects = { /* Begin PBXBuildFile section */ - 70D7C6381E6427CD008B76C6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 70D7C6371E6427CD008B76C6 /* main.m */; }; - 70D7C63B1E6427CD008B76C6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 70D7C63A1E6427CD008B76C6 /* AppDelegate.m */; }; - 70D7C63E1E6427CD008B76C6 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 70D7C63D1E6427CD008B76C6 /* ViewController.m */; }; - 70D7C6411E6427CD008B76C6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 70D7C63F1E6427CD008B76C6 /* Main.storyboard */; }; - 70D7C6431E6427CD008B76C6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 70D7C6421E6427CD008B76C6 /* Assets.xcassets */; }; - 70D7C6461E6427CD008B76C6 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 70D7C6441E6427CD008B76C6 /* LaunchScreen.storyboard */; }; - F0517B48C8AEA67EB7DAF979 /* libPods-LazyScrollViewDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ECD1FF81519CA31F379C5611 /* libPods-LazyScrollViewDemo.a */; }; + 927CAE3B2046B37700BD3B19 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 927CAE3A2046B37700BD3B19 /* AppDelegate.m */; }; + 927CAE3E2046B37700BD3B19 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 927CAE3D2046B37700BD3B19 /* ViewController.m */; }; + 927CAE432046B37700BD3B19 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 927CAE422046B37700BD3B19 /* Assets.xcassets */; }; + 927CAE462046B37700BD3B19 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 927CAE442046B37700BD3B19 /* LaunchScreen.storyboard */; }; + 927CAE492046B37700BD3B19 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 927CAE482046B37700BD3B19 /* main.m */; }; + D1AB44F5D8E0B6975BF3CDF3 /* libPods-LazyScrollViewDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CA97F266258FD7B207134C76 /* libPods-LazyScrollViewDemo.a */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 70D7C6331E6427CD008B76C6 /* LazyScrollViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LazyScrollViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 70D7C6371E6427CD008B76C6 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - 70D7C6391E6427CD008B76C6 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 70D7C63A1E6427CD008B76C6 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 70D7C63C1E6427CD008B76C6 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; - 70D7C63D1E6427CD008B76C6 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; - 70D7C6401E6427CD008B76C6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 70D7C6421E6427CD008B76C6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 70D7C6451E6427CD008B76C6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 70D7C6471E6427CD008B76C6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 8584A0928BAA1136A3EA481D /* Pods-LazyScrollViewDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LazyScrollViewDemo.release.xcconfig"; path = "Pods/Target Support Files/Pods-LazyScrollViewDemo/Pods-LazyScrollViewDemo.release.xcconfig"; sourceTree = ""; }; - A5C4C9124EC9215291495727 /* Pods-LazyScrollViewDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LazyScrollViewDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LazyScrollViewDemo/Pods-LazyScrollViewDemo.debug.xcconfig"; sourceTree = ""; }; - ECD1FF81519CA31F379C5611 /* libPods-LazyScrollViewDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-LazyScrollViewDemo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 80196663B6BB20F868052CE9 /* Pods-LazyScrollViewDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LazyScrollViewDemo.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-LazyScrollViewDemo/Pods-LazyScrollViewDemo.debug.xcconfig"; sourceTree = ""; }; + 927CAE362046B37700BD3B19 /* LazyScrollViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LazyScrollViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 927CAE392046B37700BD3B19 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 927CAE3A2046B37700BD3B19 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 927CAE3C2046B37700BD3B19 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 927CAE3D2046B37700BD3B19 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 927CAE422046B37700BD3B19 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 927CAE452046B37700BD3B19 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 927CAE472046B37700BD3B19 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 927CAE482046B37700BD3B19 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + AA72B4C430D44D71DA7D6BD2 /* Pods-LazyScrollViewDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LazyScrollViewDemo.release.xcconfig"; path = "../Pods/Target Support Files/Pods-LazyScrollViewDemo/Pods-LazyScrollViewDemo.release.xcconfig"; sourceTree = ""; }; + CA97F266258FD7B207134C76 /* libPods-LazyScrollViewDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-LazyScrollViewDemo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 70D7C6301E6427CD008B76C6 /* Frameworks */ = { + 927CAE332046B37700BD3B19 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F0517B48C8AEA67EB7DAF979 /* libPods-LazyScrollViewDemo.a in Frameworks */, + D1AB44F5D8E0B6975BF3CDF3 /* libPods-LazyScrollViewDemo.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 03B4679B891B589AF7A68BBB /* Pods */ = { + 54053CC9EA1F39E5076BC69F /* Pods */ = { isa = PBXGroup; children = ( - A5C4C9124EC9215291495727 /* Pods-LazyScrollViewDemo.debug.xcconfig */, - 8584A0928BAA1136A3EA481D /* Pods-LazyScrollViewDemo.release.xcconfig */, + 80196663B6BB20F868052CE9 /* Pods-LazyScrollViewDemo.debug.xcconfig */, + AA72B4C430D44D71DA7D6BD2 /* Pods-LazyScrollViewDemo.release.xcconfig */, ); name = Pods; sourceTree = ""; }; - 70D7C62A1E6427CD008B76C6 = { + 927CAE2D2046B37700BD3B19 = { isa = PBXGroup; children = ( - 70D7C6351E6427CD008B76C6 /* LazyScrollViewDemo */, - 70D7C6341E6427CD008B76C6 /* Products */, - 03B4679B891B589AF7A68BBB /* Pods */, - A48E16FBDA7CA19039D2164B /* Frameworks */, + 927CAE382046B37700BD3B19 /* LazyScrollViewDemo */, + 927CAE372046B37700BD3B19 /* Products */, + 54053CC9EA1F39E5076BC69F /* Pods */, + A9F9DA53D9F091F220CF1FF4 /* Frameworks */, ); sourceTree = ""; }; - 70D7C6341E6427CD008B76C6 /* Products */ = { + 927CAE372046B37700BD3B19 /* Products */ = { isa = PBXGroup; children = ( - 70D7C6331E6427CD008B76C6 /* LazyScrollViewDemo.app */, + 927CAE362046B37700BD3B19 /* LazyScrollViewDemo.app */, ); name = Products; sourceTree = ""; }; - 70D7C6351E6427CD008B76C6 /* LazyScrollViewDemo */ = { + 927CAE382046B37700BD3B19 /* LazyScrollViewDemo */ = { isa = PBXGroup; children = ( - 70D7C6391E6427CD008B76C6 /* AppDelegate.h */, - 70D7C63A1E6427CD008B76C6 /* AppDelegate.m */, - 70D7C63C1E6427CD008B76C6 /* ViewController.h */, - 70D7C63D1E6427CD008B76C6 /* ViewController.m */, - 70D7C63F1E6427CD008B76C6 /* Main.storyboard */, - 70D7C6421E6427CD008B76C6 /* Assets.xcassets */, - 70D7C6441E6427CD008B76C6 /* LaunchScreen.storyboard */, - 70D7C6471E6427CD008B76C6 /* Info.plist */, - 70D7C6361E6427CD008B76C6 /* Supporting Files */, + 927CAE392046B37700BD3B19 /* AppDelegate.h */, + 927CAE3A2046B37700BD3B19 /* AppDelegate.m */, + 927CAE3C2046B37700BD3B19 /* ViewController.h */, + 927CAE3D2046B37700BD3B19 /* ViewController.m */, + 927CAE422046B37700BD3B19 /* Assets.xcassets */, + 927CAE442046B37700BD3B19 /* LaunchScreen.storyboard */, + 927CAE472046B37700BD3B19 /* Info.plist */, + 927CAE482046B37700BD3B19 /* main.m */, ); path = LazyScrollViewDemo; sourceTree = ""; }; - 70D7C6361E6427CD008B76C6 /* Supporting Files */ = { + A9F9DA53D9F091F220CF1FF4 /* Frameworks */ = { isa = PBXGroup; children = ( - 70D7C6371E6427CD008B76C6 /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - A48E16FBDA7CA19039D2164B /* Frameworks */ = { - isa = PBXGroup; - children = ( - ECD1FF81519CA31F379C5611 /* libPods-LazyScrollViewDemo.a */, + CA97F266258FD7B207134C76 /* libPods-LazyScrollViewDemo.a */, ); name = Frameworks; sourceTree = ""; @@ -106,16 +95,16 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 70D7C6321E6427CD008B76C6 /* LazyScrollViewDemo */ = { + 927CAE352046B37700BD3B19 /* LazyScrollViewDemo */ = { isa = PBXNativeTarget; - buildConfigurationList = 70D7C64A1E6427CD008B76C6 /* Build configuration list for PBXNativeTarget "LazyScrollViewDemo" */; + buildConfigurationList = 927CAE4C2046B37700BD3B19 /* Build configuration list for PBXNativeTarget "LazyScrollViewDemo" */; buildPhases = ( - 598A8EE8D51846140BDD239E /* [CP] Check Pods Manifest.lock */, - 70D7C62F1E6427CD008B76C6 /* Sources */, - 70D7C6301E6427CD008B76C6 /* Frameworks */, - 70D7C6311E6427CD008B76C6 /* Resources */, - 3A6A0E9BEC57FAC92138C123 /* [CP] Embed Pods Frameworks */, - FBE925B995B81EED4A638B2E /* [CP] Copy Pods Resources */, + A810AF92003E824DBE229A21 /* [CP] Check Pods Manifest.lock */, + 927CAE322046B37700BD3B19 /* Sources */, + 927CAE332046B37700BD3B19 /* Frameworks */, + 927CAE342046B37700BD3B19 /* Resources */, + 2C0A0C8EE3C249225702BEF4 /* [CP] Embed Pods Frameworks */, + 3B42DAA9E89AC5058947BA15 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -123,57 +112,56 @@ ); name = LazyScrollViewDemo; productName = LazyScrollViewDemo; - productReference = 70D7C6331E6427CD008B76C6 /* LazyScrollViewDemo.app */; + productReference = 927CAE362046B37700BD3B19 /* LazyScrollViewDemo.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - 70D7C62B1E6427CD008B76C6 /* Project object */ = { + 927CAE2E2046B37700BD3B19 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0910; - ORGANIZATIONNAME = taobao; + LastUpgradeCheck = 0920; + ORGANIZATIONNAME = tmall; TargetAttributes = { - 70D7C6321E6427CD008B76C6 = { - CreatedOnToolsVersion = 8.2.1; + 927CAE352046B37700BD3B19 = { + CreatedOnToolsVersion = 9.2; ProvisioningStyle = Automatic; }; }; }; - buildConfigurationList = 70D7C62E1E6427CD008B76C6 /* Build configuration list for PBXProject "LazyScrollViewDemo" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + buildConfigurationList = 927CAE312046B37700BD3B19 /* Build configuration list for PBXProject "LazyScrollViewDemo" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); - mainGroup = 70D7C62A1E6427CD008B76C6; - productRefGroup = 70D7C6341E6427CD008B76C6 /* Products */; + mainGroup = 927CAE2D2046B37700BD3B19; + productRefGroup = 927CAE372046B37700BD3B19 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - 70D7C6321E6427CD008B76C6 /* LazyScrollViewDemo */, + 927CAE352046B37700BD3B19 /* LazyScrollViewDemo */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 70D7C6311E6427CD008B76C6 /* Resources */ = { + 927CAE342046B37700BD3B19 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 70D7C6461E6427CD008B76C6 /* LaunchScreen.storyboard in Resources */, - 70D7C6431E6427CD008B76C6 /* Assets.xcassets in Resources */, - 70D7C6411E6427CD008B76C6 /* Main.storyboard in Resources */, + 927CAE462046B37700BD3B19 /* LaunchScreen.storyboard in Resources */, + 927CAE432046B37700BD3B19 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 3A6A0E9BEC57FAC92138C123 /* [CP] Embed Pods Frameworks */ = { + 2C0A0C8EE3C249225702BEF4 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -185,70 +173,62 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LazyScrollViewDemo/Pods-LazyScrollViewDemo-frameworks.sh\"\n"; + shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-LazyScrollViewDemo/Pods-LazyScrollViewDemo-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 598A8EE8D51846140BDD239E /* [CP] Check Pods Manifest.lock */ = { + 3B42DAA9E89AC5058947BA15 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Copy Pods Resources"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-LazyScrollViewDemo-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-LazyScrollViewDemo/Pods-LazyScrollViewDemo-resources.sh\"\n"; showEnvVarsInLog = 0; }; - FBE925B995B81EED4A638B2E /* [CP] Copy Pods Resources */ = { + A810AF92003E824DBE229A21 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "[CP] Copy Pods Resources"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-LazyScrollViewDemo-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LazyScrollViewDemo/Pods-LazyScrollViewDemo-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 70D7C62F1E6427CD008B76C6 /* Sources */ = { + 927CAE322046B37700BD3B19 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 70D7C63E1E6427CD008B76C6 /* ViewController.m in Sources */, - 70D7C63B1E6427CD008B76C6 /* AppDelegate.m in Sources */, - 70D7C6381E6427CD008B76C6 /* main.m in Sources */, + 927CAE3E2046B37700BD3B19 /* ViewController.m in Sources */, + 927CAE492046B37700BD3B19 /* main.m in Sources */, + 927CAE3B2046B37700BD3B19 /* AppDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ - 70D7C63F1E6427CD008B76C6 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 70D7C6401E6427CD008B76C6 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 70D7C6441E6427CD008B76C6 /* LaunchScreen.storyboard */ = { + 927CAE442046B37700BD3B19 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( - 70D7C6451E6427CD008B76C6 /* Base */, + 927CAE452046B37700BD3B19 /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; @@ -256,12 +236,13 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ - 70D7C6481E6427CD008B76C6 /* Debug */ = { + 927CAE4A2046B37700BD3B19 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -281,14 +262,15 @@ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; @@ -309,12 +291,13 @@ }; name = Debug; }; - 70D7C6491E6427CD008B76C6 /* Release */ = { + 927CAE4B2046B37700BD3B19 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; @@ -334,14 +317,15 @@ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; @@ -356,56 +340,56 @@ }; name = Release; }; - 70D7C64B1E6427CD008B76C6 /* Debug */ = { + 927CAE4D2046B37700BD3B19 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = A5C4C9124EC9215291495727 /* Pods-LazyScrollViewDemo.debug.xcconfig */; + baseConfigurationReference = 80196663B6BB20F868052CE9 /* Pods-LazyScrollViewDemo.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = LazyScrollViewDemo/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = tmall.LazyScrollViewDemo; PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = 1; }; name = Debug; }; - 70D7C64C1E6427CD008B76C6 /* Release */ = { + 927CAE4E2046B37700BD3B19 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 8584A0928BAA1136A3EA481D /* Pods-LazyScrollViewDemo.release.xcconfig */; + baseConfigurationReference = AA72B4C430D44D71DA7D6BD2 /* Pods-LazyScrollViewDemo.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = ""; + CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = LazyScrollViewDemo/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = tmall.LazyScrollViewDemo; PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = 1; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 70D7C62E1E6427CD008B76C6 /* Build configuration list for PBXProject "LazyScrollViewDemo" */ = { + 927CAE312046B37700BD3B19 /* Build configuration list for PBXProject "LazyScrollViewDemo" */ = { isa = XCConfigurationList; buildConfigurations = ( - 70D7C6481E6427CD008B76C6 /* Debug */, - 70D7C6491E6427CD008B76C6 /* Release */, + 927CAE4A2046B37700BD3B19 /* Debug */, + 927CAE4B2046B37700BD3B19 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 70D7C64A1E6427CD008B76C6 /* Build configuration list for PBXNativeTarget "LazyScrollViewDemo" */ = { + 927CAE4C2046B37700BD3B19 /* Build configuration list for PBXNativeTarget "LazyScrollViewDemo" */ = { isa = XCConfigurationList; buildConfigurations = ( - 70D7C64B1E6427CD008B76C6 /* Debug */, - 70D7C64C1E6427CD008B76C6 /* Release */, + 927CAE4D2046B37700BD3B19 /* Debug */, + 927CAE4E2046B37700BD3B19 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; - rootObject = 70D7C62B1E6427CD008B76C6 /* Project object */; + rootObject = 927CAE2E2046B37700BD3B19 /* Project object */; } diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.m b/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.m index 3a6ccde..f559b12 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.m +++ b/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.m @@ -6,45 +6,17 @@ // #import "AppDelegate.h" - -@interface AppDelegate () - -@end +#import "ViewController.h" @implementation AppDelegate - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[ViewController new]]; + self.window.backgroundColor = [UIColor whiteColor]; + [self.window makeKeyAndVisible]; return YES; } - -- (void)applicationWillResignActive:(UIApplication *)application { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. -} - - -- (void)applicationDidEnterBackground:(UIApplication *)application { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. -} - - -- (void)applicationWillEnterForeground:(UIApplication *)application { - // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. -} - - -- (void)applicationDidBecomeActive:(UIApplication *)application { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. -} - - -- (void)applicationWillTerminate:(UIApplication *)application { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. -} - - @end diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/Assets.xcassets/AppIcon.appiconset/Contents.json b/LazyScrollViewDemo/LazyScrollViewDemo/Assets.xcassets/AppIcon.appiconset/Contents.json index 118c98f..d8db8d6 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/LazyScrollViewDemo/LazyScrollViewDemo/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,5 +1,15 @@ { "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, { "idiom" : "iphone", "size" : "29x29", @@ -29,6 +39,56 @@ "idiom" : "iphone", "size" : "60x60", "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" } ], "info" : { diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/Base.lproj/LaunchScreen.storyboard b/LazyScrollViewDemo/LazyScrollViewDemo/Base.lproj/LaunchScreen.storyboard index fdf3f97..c34e854 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/Base.lproj/LaunchScreen.storyboard +++ b/LazyScrollViewDemo/LazyScrollViewDemo/Base.lproj/LaunchScreen.storyboard @@ -1,7 +1,11 @@ - - + + + + + - + + @@ -10,8 +14,8 @@ - - + + diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/Base.lproj/Main.storyboard b/LazyScrollViewDemo/LazyScrollViewDemo/Base.lproj/Main.storyboard deleted file mode 100644 index 4529698..0000000 --- a/LazyScrollViewDemo/LazyScrollViewDemo/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/Info.plist b/LazyScrollViewDemo/LazyScrollViewDemo/Info.plist index 38e98af..4222ac2 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/Info.plist +++ b/LazyScrollViewDemo/LazyScrollViewDemo/Info.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - en + $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -22,8 +22,6 @@ UILaunchStoryboardName LaunchScreen - UIMainStoryboardFile - Main UIRequiredDeviceCapabilities armv7 @@ -34,5 +32,12 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + diff --git a/LazyScrollViewDemo/Podfile b/LazyScrollViewDemo/Podfile deleted file mode 100644 index 4be8020..0000000 --- a/LazyScrollViewDemo/Podfile +++ /dev/null @@ -1,8 +0,0 @@ -source "https://github.com/CocoaPods/Specs.git" - -platform :ios, '8.0' - -target 'LazyScrollViewDemo' do - pod 'LazyScroll', :path => '../' - # pod 'TMUtils', :path => '../' -end diff --git a/Podfile b/Podfile new file mode 100644 index 0000000..95e9a44 --- /dev/null +++ b/Podfile @@ -0,0 +1,12 @@ +source "https://github.com/CocoaPods/Specs.git" +# source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git' + +platform :ios, '7.0' + +target 'LazyScrollViewDemo' do + project 'LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj' + pod 'LazyScroll', :path => './' + pod 'TMUtils', :path => './' +end + +workspace 'LazyScrollView' From c5ff77d5252983aee58e18525f686b61a99842be Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Wed, 28 Feb 2018 19:21:04 +0800 Subject: [PATCH 07/30] rename TMUtils categories --- TMUtils/TMUtils.h | 8 ++++---- TMUtils/TMUtils.m | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/TMUtils/TMUtils.h b/TMUtils/TMUtils.h index ea5083e..5689ec7 100644 --- a/TMUtils/TMUtils.h +++ b/TMUtils/TMUtils.h @@ -8,7 +8,7 @@ #import #import -@interface NSArray (TMUtil) +@interface NSArray (TMSafeUtils) - (id)tm_safeObjectAtIndex:(NSUInteger)index; - (id)tm_safeObjectAtIndex:(NSUInteger)index class:(Class)aClass; @@ -22,14 +22,14 @@ @end -@interface NSMutableArray (TMUtil) +@interface NSMutableArray (TMSafeUtils) - (void)tm_safeAddObject:(id)anObject; - (void)tm_safeInsertObject:(id)anObject atIndex:(NSUInteger)index; @end -@interface NSDictionary (TMUtil) +@interface NSDictionary (TMSafeUtils) - (id)tm_safeObjectForKey:(id)key; - (id)tm_safeObjectForKey:(id)key class:(Class)aClass; @@ -46,7 +46,7 @@ @end -@interface NSMutableDictionary (TMUtil) +@interface NSMutableDictionary (TMSafeUtils) - (void)tm_safeSetObject:(id)anObject forKey:(id)key; diff --git a/TMUtils/TMUtils.m b/TMUtils/TMUtils.m index 8c52ea7..36bd31e 100644 --- a/TMUtils/TMUtils.m +++ b/TMUtils/TMUtils.m @@ -7,7 +7,7 @@ #import "TMUtils.h" -@implementation NSArray (TMUtil) +@implementation NSArray (TMSafeUtils) - (id)tm_safeObjectAtIndex:(NSUInteger)index { @@ -74,7 +74,7 @@ - (NSArray *)tm_arrayAtIndex:(NSUInteger)index @end -@implementation NSMutableArray (TMUtil) +@implementation NSMutableArray (TMSafeUtils) - (void)tm_safeAddObject:(id)anObject { @@ -92,7 +92,7 @@ - (void)tm_safeInsertObject:(id)anObject atIndex:(NSUInteger)index @end -@implementation NSDictionary (TMUtil) +@implementation NSDictionary (TMSafeUtils) - (id)tm_safeObjectForKey:(id)key { @@ -171,7 +171,7 @@ - (void)tm_safeSetValue:(id)value forKey:(NSString *)key @end -@implementation NSMutableDictionary (TMUtil) +@implementation NSMutableDictionary (TMSafeUtils) - (void)tm_safeSetObject:(id)anObject forKey:(id)key { From f25d4ab4197c543ef68f1ac85a0e1d46eae04a08 Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Wed, 28 Feb 2018 20:55:56 +0800 Subject: [PATCH 08/30] upgrade TMUtils: add some new helper methods --- TMUtils/NSArray+TMSafeUtils.h | 37 +++++ TMUtils/{TMUtils.m => NSArray+TMSafeUtils.m} | 131 +++++++---------- TMUtils/NSDictionary+TMSafeUtils.h | 35 +++++ TMUtils/NSDictionary+TMSafeUtils.m | 142 +++++++++++++++++++ TMUtils/NSString+TMSafeUtils.h | 15 ++ TMUtils/NSString+TMSafeUtils.m | 23 +++ TMUtils/TMUtils.h | 49 +------ 7 files changed, 307 insertions(+), 125 deletions(-) create mode 100644 TMUtils/NSArray+TMSafeUtils.h rename TMUtils/{TMUtils.m => NSArray+TMSafeUtils.m} (57%) create mode 100644 TMUtils/NSDictionary+TMSafeUtils.h create mode 100644 TMUtils/NSDictionary+TMSafeUtils.m create mode 100644 TMUtils/NSString+TMSafeUtils.h create mode 100644 TMUtils/NSString+TMSafeUtils.m diff --git a/TMUtils/NSArray+TMSafeUtils.h b/TMUtils/NSArray+TMSafeUtils.h new file mode 100644 index 0000000..643711a --- /dev/null +++ b/TMUtils/NSArray+TMSafeUtils.h @@ -0,0 +1,37 @@ +// +// NSArray+TMSafeUtils.h +// TMUtils +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import +#import + +@interface NSArray (TMSafeUtils) + +- (id)tm_safeObjectAtIndex:(NSUInteger)index; +- (id)tm_safeObjectAtIndex:(NSUInteger)index class:(Class)aClass; + +- (bool)tm_boolAtIndex:(NSUInteger)index; +- (CGFloat)tm_floatAtIndex:(NSUInteger)index; +- (NSInteger)tm_integerAtIndex:(NSUInteger)index; +- (int)tm_intAtIndex:(NSUInteger)index; +- (long)tm_longAtIndex:(NSUInteger)index; +- (NSNumber *)tm_numberAtIndex:(NSUInteger)index; +- (NSString *)tm_stringAtIndex:(NSUInteger)index; +- (NSDictionary *)tm_dictionaryAtIndex:(NSUInteger)index; +- (NSArray *)tm_arrayAtIndex:(NSUInteger)index; +- (NSMutableDictionary *)tm_mutableDictionaryAtIndex:(NSUInteger)index; +- (NSMutableArray *)tm_mutableArrayAtIndex:(NSUInteger)index; + +@end + +@interface NSMutableArray (TMSafeUtils) + +- (void)tm_safeAddObject:(id)anObject; +- (void)tm_safeInsertObject:(id)anObject atIndex:(NSUInteger)index; +- (void)tm_safeReplaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject; +- (void)tm_safeRemoveObjectAtIndex:(NSUInteger)index; + +@end diff --git a/TMUtils/TMUtils.m b/TMUtils/NSArray+TMSafeUtils.m similarity index 57% rename from TMUtils/TMUtils.m rename to TMUtils/NSArray+TMSafeUtils.m index 36bd31e..cd13f8f 100644 --- a/TMUtils/TMUtils.m +++ b/TMUtils/NSArray+TMSafeUtils.m @@ -1,11 +1,12 @@ // -// TMUtils.m +// NSArray+TMSafeUtils.m // TMUtils // // Copyright (c) 2015-2018 Alibaba. All rights reserved. // -#import "TMUtils.h" +#import "NSArray+TMSafeUtils.h" +#import "NSString+TMSafeUtils.h" @implementation NSArray (TMSafeUtils) @@ -57,127 +58,99 @@ - (NSInteger)tm_integerAtIndex:(NSUInteger)index return 0; } -- (NSString *)tm_stringAtIndex:(NSUInteger)index -{ - return [self tm_safeObjectAtIndex:index class:[NSString class]]; -} - -- (NSDictionary *)tm_dictionaryAtIndex:(NSUInteger)index -{ - return [self tm_safeObjectAtIndex:index class:[NSDictionary class]]; -} - -- (NSArray *)tm_arrayAtIndex:(NSUInteger)index +- (int)tm_intAtIndex:(NSUInteger)index { - return [self tm_safeObjectAtIndex:index class:[NSArray class]]; -} - -@end - -@implementation NSMutableArray (TMSafeUtils) - -- (void)tm_safeAddObject:(id)anObject -{ - if (anObject) { - [self addObject:anObject]; + id value = [self tm_safeObjectAtIndex:index]; + if ([value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSString class]]) { + return [value intValue]; } + return 0; } -- (void)tm_safeInsertObject:(id)anObject atIndex:(NSUInteger)index +- (long)tm_longAtIndex:(NSUInteger)index { - if (anObject && index <= self.count) { - [self insertObject:anObject atIndex:index]; + id value = [self tm_safeObjectAtIndex:index]; + if ([value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSString class]]) { + return [value longValue]; } + return 0; } -@end - -@implementation NSDictionary (TMSafeUtils) - -- (id)tm_safeObjectForKey:(id)key +- (NSNumber *)tm_numberAtIndex:(NSUInteger)index { - if (key == nil) { - return nil; + id value = [self tm_safeObjectAtIndex:index]; + if ([value isKindOfClass:[NSNumber class]]) { + return value; } - id value = [self objectForKey:key]; - if (value == [NSNull null]) { - return nil; + if ([value respondsToSelector:@selector(numberValue)]) { + return [value numberValue]; } - return value; + return nil; } -- (id)tm_safeObjectForKey:(id)key class:(Class)aClass +- (NSString *)tm_stringAtIndex:(NSUInteger)index { - id value = [self tm_safeObjectForKey:key]; - if ([value isKindOfClass:aClass]) { + id value = [self tm_safeObjectAtIndex:index]; + if ([value isKindOfClass:[NSString class]]) { return value; } + if ([value respondsToSelector:@selector(stringValue)]) { + return [value stringValue]; + } return nil; } -- (bool)tm_boolForKey:(id)key +- (NSArray *)tm_arrayAtIndex:(NSUInteger)index { - id value = [self tm_safeObjectForKey:key]; - if ([value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSString class]]) { - return [value boolValue]; - } - return NO; + return [self tm_safeObjectAtIndex:index class:[NSArray class]]; } -- (CGFloat)tm_floatForKey:(id)key +- (NSDictionary *)tm_dictionaryAtIndex:(NSUInteger)index { - id value = [self tm_safeObjectForKey:key]; - if ([value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSString class]]) { - return [value floatValue]; - } - return 0; + return [self tm_safeObjectAtIndex:index class:[NSDictionary class]]; } -- (NSInteger)tm_integerForKey:(id)key +- (NSMutableArray *)tm_mutableArrayAtIndex:(NSUInteger)index { - id value = [self tm_safeObjectForKey:key]; - if ([value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSString class]]) { - return [value integerValue]; - } - return 0; + return [self tm_safeObjectAtIndex:index class:[NSMutableArray class]]; } -- (NSString *)tm_stringForKey:(id)key +- (NSMutableDictionary *)tm_mutableDictionaryAtIndex:(NSUInteger)index { - return [self tm_safeObjectForKey:key class:[NSString class]]; + return [self tm_safeObjectAtIndex:index class:[NSMutableDictionary class]]; } -- (NSDictionary *)tm_dictionaryForKey:(id)key -{ - return [self tm_safeObjectForKey:key class:[NSDictionary class]]; -} +@end -- (NSArray *)tm_arrayForKey:(id)key +@implementation NSMutableArray (TMSafeUtils) + +- (void)tm_safeAddObject:(id)anObject { - return [self tm_safeObjectForKey:key class:[NSArray class]]; + if (anObject) { + [self addObject:anObject]; + } } -- (id)tm_safeValueForKey:(NSString *)key +- (void)tm_safeInsertObject:(id)anObject atIndex:(NSUInteger)index { - return [self tm_safeObjectForKey:key]; + if (anObject && index <= self.count) { + [self insertObject:anObject atIndex:index]; + } } -- (void)tm_safeSetValue:(id)value forKey:(NSString *)key +- (void)tm_safeReplaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject { - if (key && [key isKindOfClass:[NSString class]]) { - [self setValue:value forKey:key]; + if (anObject && index < self.count) { + [self replaceObjectAtIndex:index withObject:anObject]; } } -@end - -@implementation NSMutableDictionary (TMSafeUtils) - -- (void)tm_safeSetObject:(id)anObject forKey:(id)key +- (void)tm_safeRemoveObjectAtIndex:(NSUInteger)index { - if (key && anObject) { - [self setObject:anObject forKey:key]; + if (index < self.count) { + [self removeObjectAtIndex:index]; } } @end + diff --git a/TMUtils/NSDictionary+TMSafeUtils.h b/TMUtils/NSDictionary+TMSafeUtils.h new file mode 100644 index 0000000..b8d2fa9 --- /dev/null +++ b/TMUtils/NSDictionary+TMSafeUtils.h @@ -0,0 +1,35 @@ +// +// NSDictionary+TMSafeUtils.h +// TMUtils +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import +#import + +@interface NSDictionary (TMSafeUtils) + +- (id)tm_safeObjectForKey:(id)key; +- (id)tm_safeObjectForKey:(id)key class:(Class)aClass; + +- (bool)tm_boolForKey:(id)key; +- (CGFloat)tm_floatForKey:(id)key; +- (NSInteger)tm_integerForKey:(id)key; +- (int)tm_intForKey:(id)key; +- (long)tm_longForKey:(id)key; +- (NSNumber *)tm_numberForKey:(id)key; +- (NSString *)tm_stringForKey:(id)key; +- (NSDictionary *)tm_dictionaryForKey:(id)key; +- (NSArray *)tm_arrayForKey:(id)key; +- (NSMutableDictionary *)tm_mutableDictionaryForKey:(id)key; +- (NSMutableArray *)tm_mutableArrayForKey:(id)key; + +@end + +@interface NSMutableDictionary (TMSafeUtils) + +- (void)tm_safeSetObject:(id)anObject forKey:(id)key; +- (void)tm_safeRemoveObjectForKey:(id)key; + +@end diff --git a/TMUtils/NSDictionary+TMSafeUtils.m b/TMUtils/NSDictionary+TMSafeUtils.m new file mode 100644 index 0000000..e2b3e5e --- /dev/null +++ b/TMUtils/NSDictionary+TMSafeUtils.m @@ -0,0 +1,142 @@ +// +// NSDictionary+TMSafeUtils.m +// TMUtils +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import "NSDictionary+TMSafeUtils.h" +#import "NSString+TMSafeUtils.h" + +@implementation NSDictionary (TMSafeUtils) + +- (id)tm_safeObjectForKey:(id)key +{ + if (key == nil) { + return nil; + } + id value = [self objectForKey:key]; + if (value == [NSNull null]) { + return nil; + } + return value; +} + +- (id)tm_safeObjectForKey:(id)key class:(Class)aClass +{ + id value = [self tm_safeObjectForKey:key]; + if ([value isKindOfClass:aClass]) { + return value; + } + return nil; +} + +- (bool)tm_boolForKey:(id)key +{ + id value = [self tm_safeObjectForKey:key]; + if ([value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSString class]]) { + return [value boolValue]; + } + return NO; +} + +- (CGFloat)tm_floatForKey:(id)key +{ + id value = [self tm_safeObjectForKey:key]; + if ([value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSString class]]) { + return [value floatValue]; + } + return 0; +} + +- (NSInteger)tm_integerForKey:(id)key +{ + id value = [self tm_safeObjectForKey:key]; + if ([value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSString class]]) { + return [value integerValue]; + } + return 0; +} + +- (int)tm_intForKey:(id)key +{ + id value = [self tm_safeObjectForKey:key]; + if ([value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSString class]]) { + return [value intValue]; + } + return 0; +} + +- (long)tm_longForKey:(id)key +{ + id value = [self tm_safeObjectForKey:key]; + if ([value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSString class]]) { + return [value longValue]; + } + return 0; +} + +- (NSNumber *)tm_numberForKey:(id)key +{ + id value = [self tm_safeObjectForKey:key]; + if ([value isKindOfClass:[NSNumber class]]) { + return value; + } + if ([value respondsToSelector:@selector(numberValue)]) { + return [value numberValue]; + } + return nil; +} + +- (NSString *)tm_stringForKey:(id)key +{ + id value = [self tm_safeObjectForKey:key]; + if ([value isKindOfClass:[NSString class]]) { + return value; + } + if ([value respondsToSelector:@selector(stringValue)]) { + return [value stringValue]; + } + return nil; +} + +- (NSArray *)tm_arrayForKey:(id)key +{ + return [self tm_safeObjectForKey:key class:[NSArray class]]; +} + +- (NSDictionary *)tm_dictionaryForKey:(id)key +{ + return [self tm_safeObjectForKey:key class:[NSDictionary class]]; +} + +- (NSMutableArray *)tm_mutableArrayForKey:(id)key +{ + return [self tm_safeObjectForKey:key class:[NSMutableArray class]]; +} + +- (NSMutableDictionary *)tm_mutableDictionaryForKey:(id)key +{ + return [self tm_safeObjectForKey:key class:[NSMutableDictionary class]]; +} + +@end + +@implementation NSMutableDictionary (TMSafeUtils) + +- (void)tm_safeSetObject:(id)anObject forKey:(id)key +{ + if (key && anObject) { + [self setObject:anObject forKey:key]; + } +} + +-(void)tm_safeRemoveObjectForKey:(id)key +{ + if (key) { + [self removeObjectForKey:key]; + } +} + +@end + diff --git a/TMUtils/NSString+TMSafeUtils.h b/TMUtils/NSString+TMSafeUtils.h new file mode 100644 index 0000000..e188f87 --- /dev/null +++ b/TMUtils/NSString+TMSafeUtils.h @@ -0,0 +1,15 @@ +// +// NSString+TMSafeUtils.h +// TMUtils +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import + +@interface NSString (TMSafeUtils) + +- (long)longValue; +- (NSNumber *)numberValue; + +@end diff --git a/TMUtils/NSString+TMSafeUtils.m b/TMUtils/NSString+TMSafeUtils.m new file mode 100644 index 0000000..16ccc93 --- /dev/null +++ b/TMUtils/NSString+TMSafeUtils.m @@ -0,0 +1,23 @@ +// +// NSString+TMSafeUtils.m +// TMUtils +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import "NSString+TMSafeUtils.h" + +@implementation NSString (TMSafeUtils) + +- (long)longValue +{ + return (long)[self integerValue]; +} + +- (NSNumber *)numberValue +{ + NSNumberFormatter *formatter = [NSNumberFormatter new]; + return [formatter numberFromString:self]; +} + +@end diff --git a/TMUtils/TMUtils.h b/TMUtils/TMUtils.h index 5689ec7..bf4aa66 100644 --- a/TMUtils/TMUtils.h +++ b/TMUtils/TMUtils.h @@ -5,49 +5,6 @@ // Copyright (c) 2015-2018 Alibaba. All rights reserved. // -#import -#import - -@interface NSArray (TMSafeUtils) - -- (id)tm_safeObjectAtIndex:(NSUInteger)index; -- (id)tm_safeObjectAtIndex:(NSUInteger)index class:(Class)aClass; - -- (bool)tm_boolAtIndex:(NSUInteger)index; -- (CGFloat)tm_floatAtIndex:(NSUInteger)index; -- (NSInteger)tm_integerAtIndex:(NSUInteger)index; -- (NSString *)tm_stringAtIndex:(NSUInteger)index; -- (NSDictionary *)tm_dictionaryAtIndex:(NSUInteger)index; -- (NSArray *)tm_arrayAtIndex:(NSUInteger)index; - -@end - -@interface NSMutableArray (TMSafeUtils) - -- (void)tm_safeAddObject:(id)anObject; -- (void)tm_safeInsertObject:(id)anObject atIndex:(NSUInteger)index; - -@end - -@interface NSDictionary (TMSafeUtils) - -- (id)tm_safeObjectForKey:(id)key; -- (id)tm_safeObjectForKey:(id)key class:(Class)aClass; - -- (bool)tm_boolForKey:(id)key; -- (CGFloat)tm_floatForKey:(id)key; -- (NSInteger)tm_integerForKey:(id)key; -- (NSString *)tm_stringForKey:(id)key; -- (NSDictionary *)tm_dictionaryForKey:(id)key; -- (NSArray *)tm_arrayForKey:(id)key; - -- (id)tm_safeValueForKey:(NSString *)key; -- (void)tm_safeSetValue:(id)value forKey:(NSString *)key; - -@end - -@interface NSMutableDictionary (TMSafeUtils) - -- (void)tm_safeSetObject:(id)anObject forKey:(id)key; - -@end +#import "NSString+TMSafeUtils.h" +#import "NSArray+TMSafeUtils.h" +#import "NSDictionary+TMSafeUtils.h" From fc032bf1ac12c09e4ceaa4bdb26f4b98a8730902 Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Thu, 1 Mar 2018 10:34:17 +0800 Subject: [PATCH 09/30] rename classes --- LazyScroll.podspec | 1 + ...ellProtocol.h => TMLazyItemViewProtocol.h} | 4 +- .../{TMMuiRectModel.h => TMLazyRectModel.h} | 4 +- .../{TMMuiRectModel.m => TMLazyRectModel.m} | 6 +- ...MuiLazyScrollView.h => TMLazyScrollView.h} | 26 ++++---- ...MuiLazyScrollView.m => TMLazyScrollView.m} | 64 +++++++++---------- .../LazyScrollViewDemo/ViewController.m | 16 ++--- 7 files changed, 61 insertions(+), 60 deletions(-) rename LazyScrollView/{TMMuiLazyScrollViewCellProtocol.h => TMLazyItemViewProtocol.h} (90%) rename LazyScrollView/{TMMuiRectModel.h => TMLazyRectModel.h} (86%) rename LazyScrollView/{TMMuiRectModel.m => TMLazyRectModel.m} (53%) rename LazyScrollView/{TMMuiLazyScrollView.h => TMLazyScrollView.h} (78%) rename LazyScrollView/{TMMuiLazyScrollView.m => TMLazyScrollView.m} (90%) diff --git a/LazyScroll.podspec b/LazyScroll.podspec index cd0a8bf..c857bb1 100644 --- a/LazyScroll.podspec +++ b/LazyScroll.podspec @@ -12,6 +12,7 @@ Pod::Spec.new do |s| s.source = { :git => "https://github.com/alibaba/LazyScrollView.git", :tag => "1.0.0" } s.source_files = "LazyScrollView/*.{h,m}" s.requires_arc = true + s.prefix_header_contents = '#import ' s.dependency 'TMUtils', '~> 1.0.0' diff --git a/LazyScrollView/TMMuiLazyScrollViewCellProtocol.h b/LazyScrollView/TMLazyItemViewProtocol.h similarity index 90% rename from LazyScrollView/TMMuiLazyScrollViewCellProtocol.h rename to LazyScrollView/TMLazyItemViewProtocol.h index 3baf181..6849f30 100644 --- a/LazyScrollView/TMMuiLazyScrollViewCellProtocol.h +++ b/LazyScrollView/TMLazyItemViewProtocol.h @@ -1,5 +1,5 @@ // -// TMMuiLazyScrollViewCellProtocol.h +// TMLazyItemViewProtocol.h // LazyScrollView // // Copyright (c) 2015-2018 Alibaba. All rights reserved. @@ -9,7 +9,7 @@ If the view in LazyScrollView implement this protocol, view can do something in its lifecycle. */ -@protocol TMMuiLazyScrollViewCellProtocol +@protocol TMLazyItemViewProtocol @optional // Will be called if call dequeueReusableItemWithIdentifier diff --git a/LazyScrollView/TMMuiRectModel.h b/LazyScrollView/TMLazyRectModel.h similarity index 86% rename from LazyScrollView/TMMuiRectModel.h rename to LazyScrollView/TMLazyRectModel.h index 4f51e7b..cd80682 100644 --- a/LazyScrollView/TMMuiRectModel.h +++ b/LazyScrollView/TMLazyRectModel.h @@ -1,5 +1,5 @@ // -// TMMuiRectModel.h +// TMLazyRectModel.h // LazyScrollView // // Copyright (c) 2015-2018 Alibaba. All rights reserved. @@ -11,7 +11,7 @@ It is a view model that holding information of view. At least holding absRect and muiID. */ -@interface TMMuiRectModel : NSObject +@interface TMLazyRectModel : NSObject // A rect that relative to the scroll view. @property (nonatomic,assign) CGRect absRect; diff --git a/LazyScrollView/TMMuiRectModel.m b/LazyScrollView/TMLazyRectModel.m similarity index 53% rename from LazyScrollView/TMMuiRectModel.m rename to LazyScrollView/TMLazyRectModel.m index 827b21f..2c9b7b7 100644 --- a/LazyScrollView/TMMuiRectModel.m +++ b/LazyScrollView/TMLazyRectModel.m @@ -1,12 +1,12 @@ // -// TMMuiRectModel.m +// TMLazyRectModel.m // LazyScrollView // // Copyright (c) 2015-2018 Alibaba. All rights reserved. // -#import "TMMuiRectModel.h" +#import "TMLazyRectModel.h" -@implementation TMMuiRectModel +@implementation TMLazyRectModel @end diff --git a/LazyScrollView/TMMuiLazyScrollView.h b/LazyScrollView/TMLazyScrollView.h similarity index 78% rename from LazyScrollView/TMMuiLazyScrollView.h rename to LazyScrollView/TMLazyScrollView.h index 8d6eb60..d3ee30d 100644 --- a/LazyScrollView/TMMuiLazyScrollView.h +++ b/LazyScrollView/TMLazyScrollView.h @@ -1,21 +1,21 @@ // -// TMMuiLazyScrollView.h +// TMLazyScrollView.h // LazyScrollView // // Copyright (c) 2015-2018 Alibaba. All rights reserved. // #import -#import "TMMuiLazyScrollViewCellProtocol.h" -#import "TMMuiRectModel.h" +#import "TMLazyItemViewProtocol.h" +#import "TMLazyRectModel.h" -@class TMMuiLazyScrollView; +@class TMLazyScrollView; /** A UIView category required by LazyScrollView. */ -@interface UIView (TMMuiLazyScrollView) +@interface UIView (TMLazyScrollView) // A uniq string that identify a view, require to // be same as muiID of the model. @@ -33,29 +33,29 @@ /** This protocol represents the data model object. */ -@protocol TMMuiLazyScrollViewDataSource +@protocol TMLazyScrollViewDataSource @required // 0 by default. -- (NSUInteger)numberOfItemInScrollView:(nonnull TMMuiLazyScrollView *)scrollView; +- (NSUInteger)numberOfItemInScrollView:(nonnull TMLazyScrollView *)scrollView; // Return the view model by spcial index. -- (nonnull TMMuiRectModel *)scrollView:(nonnull TMMuiLazyScrollView *)scrollView +- (nonnull TMLazyRectModel *)scrollView:(nonnull TMLazyScrollView *)scrollView rectModelAtIndex:(NSUInteger)index; // You should render the item view here. // You should ALWAYS try to reuse views by setting each view's reuseIdentifier. -- (nonnull UIView *)scrollView:(nonnull TMMuiLazyScrollView *)scrollView +- (nonnull UIView *)scrollView:(nonnull TMLazyScrollView *)scrollView itemByMuiID:(nonnull NSString *)muiID; @end //**************************************************************** -@interface TMMuiLazyScrollView : UIScrollView +@interface TMLazyScrollView : UIScrollView // 注意,修改 delegate 属性后需要将 scrollViewDidScroll: 事件转发回给 TangramView -@property (nonatomic, weak, nullable) id dataSource; +@property (nonatomic, weak, nullable) id dataSource; @property (nonatomic, weak, nullable) id forwardingDelegate; @@ -91,6 +91,6 @@ //**************************************************************** -@interface TMMuiLazyScrollViewObserver: NSObject -@property (nonatomic, weak, nullable) TMMuiLazyScrollView *lazyScrollView; +@interface TMLazyScrollViewObserver: NSObject +@property (nonatomic, weak, nullable) TMLazyScrollView *lazyScrollView; @end diff --git a/LazyScrollView/TMMuiLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m similarity index 90% rename from LazyScrollView/TMMuiLazyScrollView.m rename to LazyScrollView/TMLazyScrollView.m index b245b23..868890e 100644 --- a/LazyScrollView/TMMuiLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -1,18 +1,18 @@ // -// TMMuiLazyScrollView.m +// TMLazyScrollView.m // LazyScrollView // // Copyright (c) 2015-2018 Alibaba. All rights reserved. // -#import "TMMuiLazyScrollView.h" +#import "TMLazyScrollView.h" #import #import #define RenderBufferWindow 20.f -@implementation UIView(TMMuiLazyScrollView) +@implementation UIView(TMLazyScrollView) - (instancetype)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier { @@ -46,12 +46,12 @@ - (void)setMuiID:(NSString *)muiID //**************************************************************** -@interface TMMuiLazyScrollView() { +@interface TMLazyScrollView() { NSMutableSet *_visibleItems; NSMutableSet *_inScreenVisibleItems; - // Store view models (TMMuiRectModel). - NSMutableArray *_itemsFrames; + // Store view models (TMLazyRectModel). + NSMutableArray *_itemsFrames; // Store reuseable cells by reuseIdentifier. The key is reuseIdentifier // of views , value is an array that contains reuseable cells. @@ -65,9 +65,9 @@ @interface TMMuiLazyScrollView() { NSMutableSet *_secondSet; // View Model sorted by Top Edge. - NSArray *_modelsSortedByTop; + NSArray *_modelsSortedByTop; // View Model sorted by Bottom Edge. - NSArray *_modelsSortedByBottom; + NSArray *_modelsSortedByBottom; // It is used to store views need to assign new value after reload. NSMutableSet *_shouldReloadItems; @@ -84,7 +84,7 @@ @interface TMMuiLazyScrollView() { // Store last time visible muiID. Used for calc enter times. NSSet *_lastVisibleMuiID; - TMMuiLazyScrollViewObserver *_outerScrollViewObserver; + TMLazyScrollViewObserver *_outerScrollViewObserver; BOOL _forwardingDelegateCanPerformScrollViewDidScrollSelector; @@ -98,7 +98,7 @@ @interface TMMuiLazyScrollView() { //**************************************************************** -@implementation TMMuiLazyScrollView +@implementation TMLazyScrollView #pragma mark - Getter & Setter - (NSSet *)inScreenVisibleItems @@ -122,7 +122,7 @@ -(void)setOuterScrollView:(UIScrollView *)outerScrollView { _outerScrollView = outerScrollView; if (_outerScrollViewObserver == nil) { - _outerScrollViewObserver = [[TMMuiLazyScrollViewObserver alloc]init]; + _outerScrollViewObserver = [[TMLazyScrollViewObserver alloc]init]; _outerScrollViewObserver.lazyScrollView = self; } @@ -252,12 +252,12 @@ - (NSUInteger)binarySearchForIndex:(NSArray *)frameArray baseLine:(CGFloat)baseL NSInteger max = frameArray.count - 1; NSInteger mid = ceilf((min + max) * 0.5f); while (mid > min && mid < max) { - CGRect rect = [(TMMuiRectModel *)[frameArray tm_safeObjectAtIndex:mid] absRect]; + CGRect rect = [(TMLazyRectModel *)[frameArray tm_safeObjectAtIndex:mid] absRect]; // For top if(fromTop) { CGFloat itemTop = CGRectGetMinY(rect); if (itemTop <= baseLine) { - CGRect nextItemRect = [(TMMuiRectModel *)[frameArray tm_safeObjectAtIndex:mid + 1] absRect]; + CGRect nextItemRect = [(TMLazyRectModel *)[frameArray tm_safeObjectAtIndex:mid + 1] absRect]; CGFloat nextTop = CGRectGetMinY(nextItemRect); if (nextTop > baseLine) { break; @@ -271,7 +271,7 @@ - (NSUInteger)binarySearchForIndex:(NSArray *)frameArray baseLine:(CGFloat)baseL else { CGFloat itemBottom = CGRectGetMaxY(rect); if (itemBottom >= baseLine) { - CGRect nextItemRect = [(TMMuiRectModel *)[frameArray tm_safeObjectAtIndex:mid + 1] absRect]; + CGRect nextItemRect = [(TMLazyRectModel *)[frameArray tm_safeObjectAtIndex:mid + 1] absRect]; CGFloat nextBottom = CGRectGetMaxY(nextItemRect); if (nextBottom < baseLine) { break; @@ -293,7 +293,7 @@ - (NSUInteger)binarySearchForIndex:(NSArray *)frameArray baseLine:(CGFloat)baseL NSUInteger endBottomIndex = [self binarySearchForIndex:_modelsSortedByBottom baseLine:startY isFromTop:NO]; [_firstSet removeAllObjects]; for (NSUInteger i = 0; i <= endBottomIndex; i++) { - TMMuiRectModel *model = [_modelsSortedByBottom tm_safeObjectAtIndex:i]; + TMLazyRectModel *model = [_modelsSortedByBottom tm_safeObjectAtIndex:i]; if (model != nil) { [_firstSet addObject:model.muiID]; } @@ -302,7 +302,7 @@ - (NSUInteger)binarySearchForIndex:(NSArray *)frameArray baseLine:(CGFloat)baseL NSUInteger endTopIndex = [self binarySearchForIndex:_modelsSortedByTop baseLine:endY isFromTop:YES]; [_secondSet removeAllObjects]; for (NSInteger i = 0; i <= endTopIndex; i++) { - TMMuiRectModel *model = [_modelsSortedByTop tm_safeObjectAtIndex:i]; + TMLazyRectModel *model = [_modelsSortedByTop tm_safeObjectAtIndex:i]; if (model != nil) { [_secondSet addObject:model.muiID]; } @@ -317,16 +317,16 @@ - (void)creatScrollViewIndex { NSUInteger count = 0; if (_dataSource && - [_dataSource conformsToProtocol:@protocol(TMMuiLazyScrollViewDataSource)] && + [_dataSource conformsToProtocol:@protocol(TMLazyScrollViewDataSource)] && [_dataSource respondsToSelector:@selector(numberOfItemInScrollView:)]) { count = [_dataSource numberOfItemInScrollView:self]; } [_itemsFrames removeAllObjects]; for (NSUInteger i = 0 ; i < count ; i++) { - TMMuiRectModel *rectmodel = nil; + TMLazyRectModel *rectmodel = nil; if (_dataSource && - [_dataSource conformsToProtocol:@protocol(TMMuiLazyScrollViewDataSource)] && + [_dataSource conformsToProtocol:@protocol(TMLazyScrollViewDataSource)] && [_dataSource respondsToSelector:@selector(scrollView:rectModelAtIndex:)]) { rectmodel = [_dataSource scrollView:self rectModelAtIndex:i]; if (rectmodel.muiID.length == 0) { @@ -337,8 +337,8 @@ - (void)creatScrollViewIndex } _modelsSortedByTop = [_itemsFrames sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { - CGRect rect1 = [(TMMuiRectModel *) obj1 absRect]; - CGRect rect2 = [(TMMuiRectModel *) obj2 absRect]; + CGRect rect1 = [(TMLazyRectModel *) obj1 absRect]; + CGRect rect2 = [(TMLazyRectModel *) obj2 absRect]; if (rect1.origin.y < rect2.origin.y) { return NSOrderedAscending; } else if (rect1.origin.y > rect2.origin.y) { @@ -349,8 +349,8 @@ - (void)creatScrollViewIndex }]; _modelsSortedByBottom = [_itemsFrames sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { - CGRect rect1 = [(TMMuiRectModel *) obj1 absRect]; - CGRect rect2 = [(TMMuiRectModel *) obj2 absRect]; + CGRect rect1 = [(TMLazyRectModel *) obj1 absRect]; + CGRect rect2 = [(TMLazyRectModel *) obj2 absRect]; CGFloat bottom1 = CGRectGetMaxY(rect1); CGFloat bottom2 = CGRectGetMaxY(rect2); if (bottom1 > bottom2) { @@ -369,7 +369,7 @@ - (void)findViewsInVisibleRect [itemViewSet minusSet:_lastVisibleMuiID]; for (UIView *view in _visibleItems) { if (view && [itemViewSet containsObject:view.muiID]) { - if ([view conformsToProtocol:@protocol(TMMuiLazyScrollViewCellProtocol)] && + if ([view conformsToProtocol:@protocol(TMLazyItemViewProtocol)] && [view respondsToSelector:@selector(mui_didEnterWithTimes:)]) { NSUInteger times = 0; if ([_enterDic tm_safeObjectForKey:view.muiID] != nil) { @@ -377,7 +377,7 @@ - (void)findViewsInVisibleRect } NSNumber *showTimes = [NSNumber numberWithUnsignedInteger:times]; [_enterDic tm_safeSetObject:showTimes forKey:view.muiID]; - [(UIView *)view mui_didEnterWithTimes:times]; + [(UIView *)view mui_didEnterWithTimes:times]; } } } @@ -423,7 +423,7 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa BOOL isToShow = [itemShouldShowSet containsObject:view.muiID]; if (!isToShow) { if ([view respondsToSelector:@selector(mui_didLeave)]){ - [(UIView *)view mui_didLeave]; + [(UIView *)view mui_didLeave]; } // If this view should be recycled and the length of its reuseidentifier is over 0. if (view.reuseIdentifier.length > 0) { @@ -450,7 +450,7 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa BOOL shouldReload = isReload || [_shouldReloadItems containsObject:muiID]; if (![self isCellVisible:muiID] || [_shouldReloadItems containsObject:muiID]) { if (_dataSource && - [_dataSource conformsToProtocol:@protocol(TMMuiLazyScrollViewDataSource)] && + [_dataSource conformsToProtocol:@protocol(TMLazyScrollViewDataSource)] && [_dataSource respondsToSelector:@selector(scrollView:itemByMuiID:)]) { // Create view by dataSource. // If you call dequeue method in your dataSource, the currentVisibleItemMuiID @@ -461,9 +461,9 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa UIView *viewToShow = [_dataSource scrollView:self itemByMuiID:muiID]; _currentVisibleItemMuiID = nil; // Call afterGetView. - if ([viewToShow conformsToProtocol:@protocol(TMMuiLazyScrollViewCellProtocol)] && + if ([viewToShow conformsToProtocol:@protocol(TMLazyItemViewProtocol)] && [viewToShow respondsToSelector:@selector(mui_afterGetView)]) { - [(UIView *)viewToShow mui_afterGetView]; + [(UIView *)viewToShow mui_afterGetView]; } if (viewToShow) { viewToShow.muiID = muiID; @@ -599,8 +599,8 @@ - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSSt } } - if ([view conformsToProtocol:@protocol(TMMuiLazyScrollViewCellProtocol)] && [view respondsToSelector:@selector(mui_prepareForReuse)]) { - [(UIView *)view mui_prepareForReuse]; + if ([view conformsToProtocol:@protocol(TMLazyItemViewProtocol)] && [view respondsToSelector:@selector(mui_prepareForReuse)]) { + [(UIView *)view mui_prepareForReuse]; } return view; } @@ -627,7 +627,7 @@ - (void)resetViewEnterTimes @end -@implementation TMMuiLazyScrollViewObserver +@implementation TMLazyScrollViewObserver - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.m index 98641e4..2ee8865 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.m +++ b/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.m @@ -6,10 +6,10 @@ // #import "ViewController.h" -#import +#import -@interface LazyScrollViewCustomView : UILabel +@interface LazyScrollViewCustomView : UILabel @property (nonatomic, assign) NSUInteger reuseTimes; @@ -25,7 +25,7 @@ - (void)mui_prepareForReuse @end -@interface ViewController () { +@interface ViewController () { NSMutableArray * rectArray; } @@ -37,7 +37,7 @@ - (void)viewDidLoad { [super viewDidLoad]; // STEP 1 . Create LazyScrollView - TMMuiLazyScrollView *scrollview = [[TMMuiLazyScrollView alloc] init]; + TMLazyScrollView *scrollview = [[TMLazyScrollView alloc] init]; scrollview.frame = self.view.bounds; scrollview.dataSource = self; scrollview.autoAddSubview = YES; @@ -69,21 +69,21 @@ - (void)viewDidLoad { } // STEP 2 implement datasource delegate. -- (NSUInteger)numberOfItemInScrollView:(TMMuiLazyScrollView *)scrollView +- (NSUInteger)numberOfItemInScrollView:(TMLazyScrollView *)scrollView { return rectArray.count; } -- (TMMuiRectModel *)scrollView:(TMMuiLazyScrollView *)scrollView rectModelAtIndex:(NSUInteger)index +- (TMLazyRectModel *)scrollView:(TMLazyScrollView *)scrollView rectModelAtIndex:(NSUInteger)index { CGRect rect = [(NSValue *)[rectArray objectAtIndex:index] CGRectValue]; - TMMuiRectModel *rectModel = [[TMMuiRectModel alloc] init]; + TMLazyRectModel *rectModel = [[TMLazyRectModel alloc] init]; rectModel.absRect = rect; rectModel.muiID = [NSString stringWithFormat:@"%zd", index]; return rectModel; } -- (UIView *)scrollView:(TMMuiLazyScrollView *)scrollView itemByMuiID:(NSString *)muiID +- (UIView *)scrollView:(TMLazyScrollView *)scrollView itemByMuiID:(NSString *)muiID { // Find view that is reuseable first. LazyScrollViewCustomView *label = (LazyScrollViewCustomView *)[scrollView dequeueReusableItemWithIdentifier:@"testView"]; From 29804588e64b39890a8647f1072446945064d8cb Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Thu, 1 Mar 2018 15:02:05 +0800 Subject: [PATCH 10/30] tidy codes and comments --- LazyScrollView/LazyScroll.h | 16 ++++ LazyScrollView/TMLazyItemModel.h | 29 +++++++ LazyScrollView/TMLazyItemModel.m | 22 ++++++ LazyScrollView/TMLazyItemViewProtocol.h | 42 ++++++---- LazyScrollView/TMLazyRectModel.h | 21 ----- LazyScrollView/TMLazyRectModel.m | 12 --- LazyScrollView/TMLazyScrollView.h | 44 ++++------- LazyScrollView/TMLazyScrollView.m | 76 +++++-------------- LazyScrollView/UIView+TMLazyScrollView.h | 18 +++++ LazyScrollView/UIView+TMLazyScrollView.m | 52 +++++++++++++ .../LazyScrollViewDemo/ViewController.m | 9 +-- TMUtils/TMUtils.h | 5 ++ 12 files changed, 207 insertions(+), 139 deletions(-) create mode 100644 LazyScrollView/LazyScroll.h create mode 100644 LazyScrollView/TMLazyItemModel.h create mode 100644 LazyScrollView/TMLazyItemModel.m delete mode 100644 LazyScrollView/TMLazyRectModel.h delete mode 100644 LazyScrollView/TMLazyRectModel.m create mode 100644 LazyScrollView/UIView+TMLazyScrollView.h create mode 100644 LazyScrollView/UIView+TMLazyScrollView.m diff --git a/LazyScrollView/LazyScroll.h b/LazyScrollView/LazyScroll.h new file mode 100644 index 0000000..0b1c1ed --- /dev/null +++ b/LazyScrollView/LazyScroll.h @@ -0,0 +1,16 @@ +// +// LazyScroll.h +// LazyScrollView +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#ifndef LazyScroll_h +#define LazyScroll_h + +#import "TMLazyItemViewProtocol.h" +#import "TMLazyItemModel.h" +#import "UIView+TMLazyScrollView.h" +#import "TMLazyScrollView.h" + +#endif /* LazyScroll_h */ diff --git a/LazyScrollView/TMLazyItemModel.h b/LazyScrollView/TMLazyItemModel.h new file mode 100644 index 0000000..fc74a61 --- /dev/null +++ b/LazyScrollView/TMLazyItemModel.h @@ -0,0 +1,29 @@ +// +// TMLazyItemModel.h +// LazyScrollView +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import + +/** + It is a model to store data of item view. + */ +@interface TMLazyItemModel : NSObject + +/** + Item view's frame in LazyScrollView. + */ +@property (nonatomic, assign) CGRect absRect; +@property (nonatomic, readonly) CGFloat top; +@property (nonatomic, readonly) CGFloat bottom; + +/** + Item view's unique ID in LazyScrollView. + Will be set to string value of index if it's nil. + The ID MUST BE unique. + */ +@property (nonatomic, copy) NSString *muiID; + +@end diff --git a/LazyScrollView/TMLazyItemModel.m b/LazyScrollView/TMLazyItemModel.m new file mode 100644 index 0000000..1a26650 --- /dev/null +++ b/LazyScrollView/TMLazyItemModel.m @@ -0,0 +1,22 @@ +// +// TMLazyItemModel.m +// LazyScrollView +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import "TMLazyItemModel.h" + +@implementation TMLazyItemModel + +- (CGFloat)top +{ + return CGRectGetMinY(_absRect); +} + +- (CGFloat)bottom +{ + return CGRectGetMaxY(_absRect); +} + +@end diff --git a/LazyScrollView/TMLazyItemViewProtocol.h b/LazyScrollView/TMLazyItemViewProtocol.h index 6849f30..9a169c4 100644 --- a/LazyScrollView/TMLazyItemViewProtocol.h +++ b/LazyScrollView/TMLazyItemViewProtocol.h @@ -6,27 +6,37 @@ // /** - If the view in LazyScrollView implement this protocol, - view can do something in its lifecycle. + If the item view in LazyScrollView implements this protocol, it + can receive specified event callback in LazyScrollView's lifecycle. */ -@protocol TMLazyItemViewProtocol +@protocol TMLazyItemViewProtocol @optional -// Will be called if call dequeueReusableItemWithIdentifier -// to get a reuseable view, the same as "prepareForReuse" -// in UITableViewCell. +/** + Will be called if the item view is dequeued in + 'dequeueReusableItemWithIdentifier:' method. + It is similar with 'prepareForReuse' method of UITableViewCell. + */ - (void)mui_prepareForReuse; -// When view enter the visible area of LazyScrollView, -// call this method. -// First 'times' is 0. -- (void)mui_didEnterWithTimes:(NSUInteger)times; -// When we need render the view, call this method. -// The difference between this method and -// 'mui_didEnterWithTimes' is there is a buffer area -// in LazyScrollView(RenderBufferWindow). +/** + Will be called if the item view is loaded into buffer area. + This callback always is used for setup item view. + It is similar with 'viewDidLoad' method of UIViewController. + */ - (void)mui_afterGetView; -// When the view is out of screen, this method will be -// called. +/** + Will be called if the item view enters the visible area. + The times starts from 0. + If the item view is in the visible area and the LazyScrollView + is reloaded, this callback will not be called. + This callback always is used for user action tracking. Sometimes, + it is also used for starting timer event. + */ +- (void)mui_didEnterWithTimes:(NSUInteger)times; +/** + Will be called if the item view leaves the visiable area. + This callback always is used for stopping timer event. + */ - (void)mui_didLeave; @end diff --git a/LazyScrollView/TMLazyRectModel.h b/LazyScrollView/TMLazyRectModel.h deleted file mode 100644 index cd80682..0000000 --- a/LazyScrollView/TMLazyRectModel.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// TMLazyRectModel.h -// LazyScrollView -// -// Copyright (c) 2015-2018 Alibaba. All rights reserved. -// - -#import - -/** - It is a view model that holding information of view. - At least holding absRect and muiID. - */ -@interface TMLazyRectModel : NSObject - -// A rect that relative to the scroll view. -@property (nonatomic,assign) CGRect absRect; -// A uniq string that identify a model. -@property (nonatomic,copy) NSString *muiID; - -@end diff --git a/LazyScrollView/TMLazyRectModel.m b/LazyScrollView/TMLazyRectModel.m deleted file mode 100644 index 2c9b7b7..0000000 --- a/LazyScrollView/TMLazyRectModel.m +++ /dev/null @@ -1,12 +0,0 @@ -// -// TMLazyRectModel.m -// LazyScrollView -// -// Copyright (c) 2015-2018 Alibaba. All rights reserved. -// - -#import "TMLazyRectModel.h" - -@implementation TMLazyRectModel - -@end diff --git a/LazyScrollView/TMLazyScrollView.h b/LazyScrollView/TMLazyScrollView.h index d3ee30d..784a77e 100644 --- a/LazyScrollView/TMLazyScrollView.h +++ b/LazyScrollView/TMLazyScrollView.h @@ -6,50 +6,36 @@ // #import -#import "TMLazyItemViewProtocol.h" -#import "TMLazyRectModel.h" +#import "TMLazyItemModel.h" @class TMLazyScrollView; +@protocol TMLazyScrollViewDataSource + +@required /** - A UIView category required by LazyScrollView. + Similar with 'tableView:numberOfRowsInSection:' of UITableView. */ -@interface UIView (TMLazyScrollView) - -// A uniq string that identify a view, require to -// be same as muiID of the model. -@property (nonatomic, copy, nullable) NSString *muiID; -// A string used to identify a view that is reusable. -@property (nonatomic, copy, nullable) NSString *reuseIdentifier; - -- (nonnull instancetype)initWithFrame:(CGRect)frame - reuseIdentifier:(nullable NSString *)reuseIdentifier; - -@end - -//**************************************************************** +- (NSUInteger)numberOfItemsInScrollView:(nonnull TMLazyScrollView *)scrollView; /** - This protocol represents the data model object. + Similar with 'tableView:heightForRowAtIndexPath:' of UITableView. + Manager the correct muiID of item views will bring a higher performance. */ -@protocol TMLazyScrollViewDataSource - -@required +- (nonnull TMLazyItemModel *)scrollView:(nonnull TMLazyScrollView *)scrollView + itemModelAtIndex:(NSUInteger)index; -// 0 by default. -- (NSUInteger)numberOfItemInScrollView:(nonnull TMLazyScrollView *)scrollView; -// Return the view model by spcial index. -- (nonnull TMLazyRectModel *)scrollView:(nonnull TMLazyScrollView *)scrollView - rectModelAtIndex:(NSUInteger)index; -// You should render the item view here. -// You should ALWAYS try to reuse views by setting each view's reuseIdentifier. +/** + Similar with 'tableView:cellForRowAtIndexPath:' of UITableView. + It will use muiID in item model instead of index. + */ - (nonnull UIView *)scrollView:(nonnull TMLazyScrollView *)scrollView itemByMuiID:(nonnull NSString *)muiID; @end -//**************************************************************** +//**************************************************************** @interface TMLazyScrollView : UIScrollView diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index 868890e..83f409c 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -7,51 +7,17 @@ #import "TMLazyScrollView.h" #import -#import +#import "TMLazyItemViewProtocol.h" +#import "UIView+TMLazyScrollView.h" #define RenderBufferWindow 20.f - -@implementation UIView(TMLazyScrollView) - -- (instancetype)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier -{ - if (self = [self initWithFrame:frame]) { - self.reuseIdentifier = reuseIdentifier; - } - return self; -} - -- (NSString *)reuseIdentifier -{ - return objc_getAssociatedObject(self, @"tm_reuseIdentifier"); -} - -- (void)setReuseIdentifier:(NSString *)reuseIdentifier -{ - objc_setAssociatedObject(self, @"tm_reuseIdentifier", reuseIdentifier, OBJC_ASSOCIATION_COPY_NONATOMIC); -} - -- (NSString *)muiID -{ - return objc_getAssociatedObject(self, @"tm_muiID"); -} - -- (void)setMuiID:(NSString *)muiID -{ - objc_setAssociatedObject(self, @"tm_muiID", muiID, OBJC_ASSOCIATION_COPY_NONATOMIC); -} - -@end - -//**************************************************************** - @interface TMLazyScrollView() { NSMutableSet *_visibleItems; NSMutableSet *_inScreenVisibleItems; - // Store view models (TMLazyRectModel). - NSMutableArray *_itemsFrames; + // Store view models (TMLazyItemModel). + NSMutableArray *_itemsFrames; // Store reuseable cells by reuseIdentifier. The key is reuseIdentifier // of views , value is an array that contains reuseable cells. @@ -65,9 +31,9 @@ @interface TMLazyScrollView() { NSMutableSet *_secondSet; // View Model sorted by Top Edge. - NSArray *_modelsSortedByTop; + NSArray *_modelsSortedByTop; // View Model sorted by Bottom Edge. - NSArray *_modelsSortedByBottom; + NSArray *_modelsSortedByBottom; // It is used to store views need to assign new value after reload. NSMutableSet *_shouldReloadItems; @@ -96,8 +62,6 @@ @interface TMLazyScrollView() { @end -//**************************************************************** - @implementation TMLazyScrollView #pragma mark - Getter & Setter @@ -252,12 +216,12 @@ - (NSUInteger)binarySearchForIndex:(NSArray *)frameArray baseLine:(CGFloat)baseL NSInteger max = frameArray.count - 1; NSInteger mid = ceilf((min + max) * 0.5f); while (mid > min && mid < max) { - CGRect rect = [(TMLazyRectModel *)[frameArray tm_safeObjectAtIndex:mid] absRect]; + CGRect rect = [(TMLazyItemModel *)[frameArray tm_safeObjectAtIndex:mid] absRect]; // For top if(fromTop) { CGFloat itemTop = CGRectGetMinY(rect); if (itemTop <= baseLine) { - CGRect nextItemRect = [(TMLazyRectModel *)[frameArray tm_safeObjectAtIndex:mid + 1] absRect]; + CGRect nextItemRect = [(TMLazyItemModel *)[frameArray tm_safeObjectAtIndex:mid + 1] absRect]; CGFloat nextTop = CGRectGetMinY(nextItemRect); if (nextTop > baseLine) { break; @@ -271,7 +235,7 @@ - (NSUInteger)binarySearchForIndex:(NSArray *)frameArray baseLine:(CGFloat)baseL else { CGFloat itemBottom = CGRectGetMaxY(rect); if (itemBottom >= baseLine) { - CGRect nextItemRect = [(TMLazyRectModel *)[frameArray tm_safeObjectAtIndex:mid + 1] absRect]; + CGRect nextItemRect = [(TMLazyItemModel *)[frameArray tm_safeObjectAtIndex:mid + 1] absRect]; CGFloat nextBottom = CGRectGetMaxY(nextItemRect); if (nextBottom < baseLine) { break; @@ -293,7 +257,7 @@ - (NSUInteger)binarySearchForIndex:(NSArray *)frameArray baseLine:(CGFloat)baseL NSUInteger endBottomIndex = [self binarySearchForIndex:_modelsSortedByBottom baseLine:startY isFromTop:NO]; [_firstSet removeAllObjects]; for (NSUInteger i = 0; i <= endBottomIndex; i++) { - TMLazyRectModel *model = [_modelsSortedByBottom tm_safeObjectAtIndex:i]; + TMLazyItemModel *model = [_modelsSortedByBottom tm_safeObjectAtIndex:i]; if (model != nil) { [_firstSet addObject:model.muiID]; } @@ -302,7 +266,7 @@ - (NSUInteger)binarySearchForIndex:(NSArray *)frameArray baseLine:(CGFloat)baseL NSUInteger endTopIndex = [self binarySearchForIndex:_modelsSortedByTop baseLine:endY isFromTop:YES]; [_secondSet removeAllObjects]; for (NSInteger i = 0; i <= endTopIndex; i++) { - TMLazyRectModel *model = [_modelsSortedByTop tm_safeObjectAtIndex:i]; + TMLazyItemModel *model = [_modelsSortedByTop tm_safeObjectAtIndex:i]; if (model != nil) { [_secondSet addObject:model.muiID]; } @@ -318,17 +282,17 @@ - (void)creatScrollViewIndex NSUInteger count = 0; if (_dataSource && [_dataSource conformsToProtocol:@protocol(TMLazyScrollViewDataSource)] && - [_dataSource respondsToSelector:@selector(numberOfItemInScrollView:)]) { - count = [_dataSource numberOfItemInScrollView:self]; + [_dataSource respondsToSelector:@selector(numberOfItemsInScrollView:)]) { + count = [_dataSource numberOfItemsInScrollView:self]; } [_itemsFrames removeAllObjects]; for (NSUInteger i = 0 ; i < count ; i++) { - TMLazyRectModel *rectmodel = nil; + TMLazyItemModel *rectmodel = nil; if (_dataSource && [_dataSource conformsToProtocol:@protocol(TMLazyScrollViewDataSource)] && - [_dataSource respondsToSelector:@selector(scrollView:rectModelAtIndex:)]) { - rectmodel = [_dataSource scrollView:self rectModelAtIndex:i]; + [_dataSource respondsToSelector:@selector(scrollView:itemModelAtIndex:)]) { + rectmodel = [_dataSource scrollView:self itemModelAtIndex:i]; if (rectmodel.muiID.length == 0) { rectmodel.muiID = [NSString stringWithFormat:@"%lu", (unsigned long)i]; } @@ -337,8 +301,8 @@ - (void)creatScrollViewIndex } _modelsSortedByTop = [_itemsFrames sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { - CGRect rect1 = [(TMLazyRectModel *) obj1 absRect]; - CGRect rect2 = [(TMLazyRectModel *) obj2 absRect]; + CGRect rect1 = [(TMLazyItemModel *) obj1 absRect]; + CGRect rect2 = [(TMLazyItemModel *) obj2 absRect]; if (rect1.origin.y < rect2.origin.y) { return NSOrderedAscending; } else if (rect1.origin.y > rect2.origin.y) { @@ -349,8 +313,8 @@ - (void)creatScrollViewIndex }]; _modelsSortedByBottom = [_itemsFrames sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { - CGRect rect1 = [(TMLazyRectModel *) obj1 absRect]; - CGRect rect2 = [(TMLazyRectModel *) obj2 absRect]; + CGRect rect1 = [(TMLazyItemModel *) obj1 absRect]; + CGRect rect2 = [(TMLazyItemModel *) obj2 absRect]; CGFloat bottom1 = CGRectGetMaxY(rect1); CGFloat bottom2 = CGRectGetMaxY(rect2); if (bottom1 > bottom2) { diff --git a/LazyScrollView/UIView+TMLazyScrollView.h b/LazyScrollView/UIView+TMLazyScrollView.h new file mode 100644 index 0000000..92f3364 --- /dev/null +++ b/LazyScrollView/UIView+TMLazyScrollView.h @@ -0,0 +1,18 @@ +// +// UIView+TMLazyScrollView.h +// LazyScrollView +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import + +@interface UIView (TMLazyScrollView) + +@property (nonatomic, copy) NSString *muiID; +@property (nonatomic, copy) NSString *reuseIdentifier; + +- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier; +- (instancetype)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier; + +@end diff --git a/LazyScrollView/UIView+TMLazyScrollView.m b/LazyScrollView/UIView+TMLazyScrollView.m new file mode 100644 index 0000000..7e6f061 --- /dev/null +++ b/LazyScrollView/UIView+TMLazyScrollView.m @@ -0,0 +1,52 @@ +// +// UIView+TMLazyScrollView.m +// LazyScrollView +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import "UIView+TMLazyScrollView.h" +#import + +#define ReuseIdentifierKey @"ReuseIdentifierKey" +#define MuiIdKey @"MuiIdKey" + +@implementation UIView (TMLazyScrollView) + +- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier +{ + if (self = [self init]) { + self.reuseIdentifier = reuseIdentifier; + } + return self; +} + +- (instancetype)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier +{ + if (self = [self initWithFrame:frame]) { + self.reuseIdentifier = reuseIdentifier; + } + return self; +} + +- (NSString *)reuseIdentifier +{ + return objc_getAssociatedObject(self, ReuseIdentifierKey); +} + +- (void)setReuseIdentifier:(NSString *)reuseIdentifier +{ + objc_setAssociatedObject(self, ReuseIdentifierKey, reuseIdentifier, OBJC_ASSOCIATION_COPY_NONATOMIC); +} + +- (NSString *)muiID +{ + return objc_getAssociatedObject(self, MuiIdKey); +} + +- (void)setMuiID:(NSString *)muiID +{ + objc_setAssociatedObject(self, MuiIdKey, muiID, OBJC_ASSOCIATION_COPY_NONATOMIC); +} + +@end diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.m index 2ee8865..b954eef 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.m +++ b/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.m @@ -6,8 +6,7 @@ // #import "ViewController.h" -#import - +#import @interface LazyScrollViewCustomView : UILabel @@ -69,15 +68,15 @@ - (void)viewDidLoad { } // STEP 2 implement datasource delegate. -- (NSUInteger)numberOfItemInScrollView:(TMLazyScrollView *)scrollView +- (NSUInteger)numberOfItemsInScrollView:(TMLazyScrollView *)scrollView { return rectArray.count; } -- (TMLazyRectModel *)scrollView:(TMLazyScrollView *)scrollView rectModelAtIndex:(NSUInteger)index +- (TMLazyItemModel *)scrollView:(TMLazyScrollView *)scrollView itemModelAtIndex:(NSUInteger)index { CGRect rect = [(NSValue *)[rectArray objectAtIndex:index] CGRectValue]; - TMLazyRectModel *rectModel = [[TMLazyRectModel alloc] init]; + TMLazyItemModel *rectModel = [[TMLazyItemModel alloc] init]; rectModel.absRect = rect; rectModel.muiID = [NSString stringWithFormat:@"%zd", index]; return rectModel; diff --git a/TMUtils/TMUtils.h b/TMUtils/TMUtils.h index bf4aa66..0ae7f75 100644 --- a/TMUtils/TMUtils.h +++ b/TMUtils/TMUtils.h @@ -5,6 +5,11 @@ // Copyright (c) 2015-2018 Alibaba. All rights reserved. // +#ifndef TMUtils_h +#define TMUtils_h + #import "NSString+TMSafeUtils.h" #import "NSArray+TMSafeUtils.h" #import "NSDictionary+TMSafeUtils.h" + +#endif /* TMUtils_h */ From 32e46583382557ab89fa363083a43e4856f895a4 Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Thu, 1 Mar 2018 17:42:09 +0800 Subject: [PATCH 11/30] 1. tidy codes 2. remove the delegate forwarding logic and hack setContentOffset directly --- LazyScrollView/TMLazyScrollView.h | 57 ++++++++----- LazyScrollView/TMLazyScrollView.m | 130 ++++++++++-------------------- 2 files changed, 82 insertions(+), 105 deletions(-) diff --git a/LazyScrollView/TMLazyScrollView.h b/LazyScrollView/TMLazyScrollView.h index 784a77e..68aad46 100644 --- a/LazyScrollView/TMLazyScrollView.h +++ b/LazyScrollView/TMLazyScrollView.h @@ -39,44 +39,63 @@ @interface TMLazyScrollView : UIScrollView -// 注意,修改 delegate 属性后需要将 scrollViewDidScroll: 事件转发回给 TangramView - @property (nonatomic, weak, nullable) id dataSource; -@property (nonatomic, weak, nullable) id forwardingDelegate; +/** + LazyScrollView can be used as a subview of another ScrollView. + For example: + You can use LazyScrollView as footerView of TableView. + Then the outerScrollView should be that TableView. + */ +@property (nonatomic, weak, nullable) UIScrollView *outerScrollView; -// Default value is NO. +/** + If it is YES, LazyScrollView will add created item view into + its subviews automatically. + Default value is NO. + */ @property (nonatomic, assign) BOOL autoAddSubview; -// Items which has been added to LazyScrollView. +/** + Item views which is in the buffer area. + They will be shown soon. + */ @property (nonatomic, strong, readonly, nonnull) NSSet *visibleItems; -// Items which is in the visible screen area. -// It is a sub set of "visibleItems". -@property (nonatomic, strong, readonly, nonnull) NSSet *inScreenVisibleItems; -// Tangram can be footerView for TableView, this outerScrollView is your tableview. -@property (nonatomic, weak, nullable) UIScrollView *outerScrollView; +/** + Item views which is in the screen visible area. + It is a sub set of "visibleItems". + */ +@property (nonatomic, strong, readonly, nonnull) NSSet *inScreenVisibleItems; -// reloads everything from scratch and redisplays visible views. - (void)reloadData; -// Remove all subviews and reuseable views. -- (void)removeAllLayouts; -// Get reuseable view by reuseIdentifier. If cannot find reuseable -// view by reuseIdentifier, here will return nil. +/** + Get reuseable view by reuseIdentifier. + */ - (nullable UIView *)dequeueReusableItemWithIdentifier:(nonnull NSString *)identifier; -// Get reuseable view by reuseIdentifier and muiID. -// MuiID has higher priority. +/** + Get reuseable view by reuseIdentifier and muiID. + MuiID has higher priority. + */ - (nullable UIView *)dequeueReusableItemWithIdentifier:(nonnull NSString *)identifier muiID:(nullable NSString *)muiID; -// After call this method, the times of mui_didEnterWithTimes will start from 0 -- (void)resetViewEnterTimes; +- (void)clearItemViewsAndReusePool; +- (void)removeAllLayouts __deprecated_msg("use clearItemViewsAndReusePool"); + +/** + After call this method, the times of 'mui_didEnterWithTimes:' will start from 0. + */ +- (void)resetItemViewsEnterTimes; +- (void)resetViewEnterTimes __deprecated_msg("use resetItemViewsEnterTimes"); @end //**************************************************************** @interface TMLazyScrollViewObserver: NSObject + @property (nonatomic, weak, nullable) TMLazyScrollView *lazyScrollView; + @end diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index 83f409c..a5bcd4f 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -10,17 +10,17 @@ #import "TMLazyItemViewProtocol.h" #import "UIView+TMLazyScrollView.h" -#define RenderBufferWindow 20.f +#define LazyBufferHeight 20.0 +#define LazyHalfBufferHeight (LazyBufferHeight / 2.0) -@interface TMLazyScrollView() { +@interface TMLazyScrollView () { NSMutableSet *_visibleItems; NSMutableSet *_inScreenVisibleItems; - // Store view models (TMLazyItemModel). + // Store item models. NSMutableArray *_itemsFrames; - // Store reuseable cells by reuseIdentifier. The key is reuseIdentifier - // of views , value is an array that contains reuseable cells. + // Store reuseable cells by reuseIdentifier. NSMutableDictionary *> *_recycledIdentifierItemsDic; // Store reuseable cells by muiID. NSMutableDictionary *_recycledMuiIDItemsDic; @@ -65,6 +65,7 @@ @interface TMLazyScrollView() { @implementation TMLazyScrollView #pragma mark - Getter & Setter + - (NSSet *)inScreenVisibleItems { return [_inScreenVisibleItems copy]; @@ -75,13 +76,6 @@ - (NSSet *)visibleItems return [_visibleItems copy]; } -- (void)setFrame:(CGRect)frame -{ - if (!CGRectEqualToRect(frame, self.frame)) { - [super setFrame:frame]; - } -} - -(void)setOuterScrollView:(UIScrollView *)outerScrollView { _outerScrollView = outerScrollView; @@ -100,16 +94,6 @@ -(void)setOuterScrollView:(UIScrollView *)outerScrollView context:nil]; } -- (void)setForwardingDelegate:(id)forwardingDelegate { - _forwardingDelegateCanPerformScrollViewDidScrollSelector = NO; - - _forwardingDelegate = forwardingDelegate; - - _forwardingDelegateCanPerformScrollViewDidScrollSelector = - [_forwardingDelegate conformsToProtocol:@protocol(UIScrollViewDelegate)] && - [_forwardingDelegate respondsToSelector:@selector(scrollViewDidScroll:)]; -} - #pragma mark - Lifecycle - (id)initWithFrame:(CGRect)frame @@ -137,8 +121,6 @@ - (id)initWithFrame:(CGRect)frame _secondSet = [[NSMutableSet alloc] initWithCapacity:30]; _enterDic = [[NSMutableDictionary alloc] init]; - - self.delegate = self; } return self; } @@ -147,7 +129,6 @@ - (void)dealloc { _dataSource = nil; self.delegate = nil; - _forwardingDelegate = nil; if (_outerScrollView) { @try { [_outerScrollView removeObserver:_outerScrollViewObserver forKeyPath:@"contentOffset"]; @@ -158,53 +139,21 @@ - (void)dealloc } } -#pragma mark - ScrollViewDelegate +#pragma mark - ScrollEvent -- (void)didScroll +- (void)setContentOffset:(CGPoint)contentOffset { + [super setContentOffset:contentOffset]; // Calculate which views should be shown. // Calcuting will cost some time, so here is a buffer for reducing // times of calculating. - CGFloat currentY = self.contentOffset.y; - CGFloat buffer = RenderBufferWindow / 2; + CGFloat currentY = contentOffset.y; + CGFloat buffer = LazyHalfBufferHeight; if (buffer < ABS(currentY - _lastScrollOffset.y)) { _lastScrollOffset = self.contentOffset; [self assembleSubviews]; [self findViewsInVisibleRect]; } - -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - [self didScroll]; - - if (_forwardingDelegateCanPerformScrollViewDidScrollSelector) { - [_forwardingDelegate scrollViewDidScroll:scrollView]; - } -} - -- (id)forwardingTargetForSelector:(SEL)aSelector -{ - if (_forwardingDelegate) { - struct objc_method_description md = protocol_getMethodDescription(@protocol(UIScrollViewDelegate), aSelector, NO, YES); - if (NULL != md.name) { - return _forwardingDelegate; - } - } - return [super forwardingTargetForSelector:aSelector]; -} - -- (BOOL)respondsToSelector:(SEL)aSelector -{ - BOOL result = [super respondsToSelector:aSelector]; - if (NO == result && _forwardingDelegate) { - struct objc_method_description md = protocol_getMethodDescription(@protocol(UIScrollViewDelegate), aSelector, NO, YES); - if (NULL != md.name) { - result = [_forwardingDelegate respondsToSelector:aSelector]; - } - } - return result; } #pragma mark - Core Logic @@ -353,9 +302,9 @@ - (void)assembleSubviews { if (_outerScrollView) { CGPoint pointInScrollView = [self.superview convertPoint:self.frame.origin toView:_outerScrollView]; - CGFloat minY = _outerScrollView.contentOffset.y - pointInScrollView.y - RenderBufferWindow/2 ; + CGFloat minY = _outerScrollView.contentOffset.y - pointInScrollView.y - LazyHalfBufferHeight; //maxY 计算的逻辑,需要修改,增加的height,需要计算的更加明确 - CGFloat maxY = _outerScrollView.contentOffset.y + _outerScrollView.frame.size.height - pointInScrollView.y + RenderBufferWindow/2; + CGFloat maxY = _outerScrollView.contentOffset.y + _outerScrollView.frame.size.height - pointInScrollView.y + LazyHalfBufferHeight; if (maxY > 0) { [self assembleSubviewsForReload:NO minY:minY maxY:maxY]; } @@ -364,8 +313,8 @@ - (void)assembleSubviews else { CGRect visibleBounds = self.bounds; - CGFloat minY = CGRectGetMinY(visibleBounds) - RenderBufferWindow; - CGFloat maxY = CGRectGetMaxY(visibleBounds) + RenderBufferWindow; + CGFloat minY = CGRectGetMinY(visibleBounds) - LazyBufferHeight; + CGFloat maxY = CGRectGetMaxY(visibleBounds) + LazyBufferHeight; [self assembleSubviewsForReload:NO minY:minY maxY:maxY]; } } @@ -479,8 +428,8 @@ - (void)reloadData if (_itemsFrames.count > 0) { if (_outerScrollView) { CGRect rectInScrollView = [self convertRect:self.frame toView:_outerScrollView]; - CGFloat minY = _outerScrollView.contentOffset.y - rectInScrollView.origin.y - RenderBufferWindow; - CGFloat maxY = _outerScrollView.contentOffset.y + _outerScrollView.frame.size.height - rectInScrollView.origin.y + _outerScrollView.frame.size.height + RenderBufferWindow; + CGFloat minY = _outerScrollView.contentOffset.y - rectInScrollView.origin.y - LazyBufferHeight; + CGFloat maxY = _outerScrollView.contentOffset.y + _outerScrollView.frame.size.height - rectInScrollView.origin.y + _outerScrollView.frame.size.height + LazyBufferHeight; if (maxY > 0) { [self assembleSubviewsForReload:YES minY:minY maxY:maxY]; } @@ -488,8 +437,8 @@ - (void)reloadData else{ CGRect visibleBounds = self.bounds; // 上下增加 20point 的缓冲区 - CGFloat minY = CGRectGetMinY(visibleBounds) - RenderBufferWindow; - CGFloat maxY = CGRectGetMaxY(visibleBounds) + RenderBufferWindow; + CGFloat minY = CGRectGetMinY(visibleBounds) - LazyBufferHeight; + CGFloat maxY = CGRectGetMaxY(visibleBounds) + LazyBufferHeight; [self assembleSubviewsForReload:YES minY:minY maxY:maxY]; } [self findViewsInVisibleRect]; @@ -497,20 +446,6 @@ - (void)reloadData } -// Remove all subviews and reuseable views. -- (void)removeAllLayouts -{ - NSSet *visibles = _visibleItems; - for (UIView *view in visibles) { - NSMutableSet *recycledIdentifierSet = [self recycledIdentifierSet:view.reuseIdentifier]; - [recycledIdentifierSet addObject:view]; - view.hidden = YES; - } - [_visibleItems removeAllObjects]; - [_recycledIdentifierItemsDic removeAllObjects]; - [_recycledMuiIDItemsDic removeAllObjects]; -} - // To acquire an already allocated view that can be reused by reuse identifier. - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier { @@ -583,12 +518,35 @@ - (BOOL)isCellVisible:(NSString *)muiID return result; } -- (void)resetViewEnterTimes +- (void)clearItemViewsAndReusePool +{ + NSSet *visibles = _visibleItems; + for (UIView *view in visibles) { + NSMutableSet *recycledIdentifierSet = [self recycledIdentifierSet:view.reuseIdentifier]; + [recycledIdentifierSet addObject:view]; + view.hidden = YES; + } + [_visibleItems removeAllObjects]; + [_recycledIdentifierItemsDic removeAllObjects]; + [_recycledMuiIDItemsDic removeAllObjects]; +} + +- (void)removeAllLayouts +{ + [self clearItemViewsAndReusePool]; +} + +- (void)resetItemViewsEnterTimes { [_enterDic removeAllObjects]; _lastVisibleMuiID = nil; } +- (void)resetViewEnterTimes +{ + [self resetItemViewsEnterTimes]; +} + @end @implementation TMLazyScrollViewObserver @@ -598,7 +556,7 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N if([keyPath isEqualToString:@"contentOffset"]) { CGPoint newPoint = [[change objectForKey:NSKeyValueChangeNewKey] CGPointValue]; - CGFloat buffer = RenderBufferWindow / 2; + CGFloat buffer = LazyHalfBufferHeight; if (buffer < ABS(newPoint.y - _lazyScrollView->_lastScrollOffset.y)) { _lazyScrollView->_lastScrollOffset = newPoint; [_lazyScrollView assembleSubviews]; From 0458034bcc0468ced062517e539d251cd3794e90 Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Thu, 1 Mar 2018 19:49:25 +0800 Subject: [PATCH 12/30] add reuse pool class --- LazyScrollView/LazyScroll.h | 1 + LazyScrollView/TMLazyReusePool.h | 17 +++++ LazyScrollView/TMLazyReusePool.m | 76 ++++++++++++++++++++++ LazyScrollView/TMLazyScrollView.h | 18 ++++-- LazyScrollView/TMLazyScrollView.m | 103 ++++++++---------------------- TMUtils/NSArray+TMSafeUtils.h | 1 + TMUtils/NSArray+TMSafeUtils.m | 7 ++ 7 files changed, 141 insertions(+), 82 deletions(-) create mode 100644 LazyScrollView/TMLazyReusePool.h create mode 100644 LazyScrollView/TMLazyReusePool.m diff --git a/LazyScrollView/LazyScroll.h b/LazyScrollView/LazyScroll.h index 0b1c1ed..fc33e3d 100644 --- a/LazyScrollView/LazyScroll.h +++ b/LazyScrollView/LazyScroll.h @@ -10,6 +10,7 @@ #import "TMLazyItemViewProtocol.h" #import "TMLazyItemModel.h" +#import "TMLazyReusePool.h" #import "UIView+TMLazyScrollView.h" #import "TMLazyScrollView.h" diff --git a/LazyScrollView/TMLazyReusePool.h b/LazyScrollView/TMLazyReusePool.h new file mode 100644 index 0000000..760ab48 --- /dev/null +++ b/LazyScrollView/TMLazyReusePool.h @@ -0,0 +1,17 @@ +// +// TMLazyReusePool.h +// LazyScrollView +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import + +@interface TMLazyReusePool : NSObject + +- (void)addItemView:(UIView *)itemView forReuseIdentifier:(NSString *)reuseIdentifier; +- (UIView *)dequeueItemViewForReuseIdentifier:(NSString *)reuseIdentifier; +- (UIView *)dequeueItemViewForReuseIdentifier:(NSString *)reuseIdentifier andMuiID:(NSString *)muiID; +- (void)clear; + +@end diff --git a/LazyScrollView/TMLazyReusePool.m b/LazyScrollView/TMLazyReusePool.m new file mode 100644 index 0000000..f7e390e --- /dev/null +++ b/LazyScrollView/TMLazyReusePool.m @@ -0,0 +1,76 @@ +// +// TMLazyReusePool.m +// LazyScrollView +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import "TMLazyReusePool.h" +#import "UIView+TMLazyScrollView.h" + +@interface TMLazyReusePool () { + NSMutableDictionary *_reuseDict; +} + +@end + +@implementation TMLazyReusePool + +- (instancetype)init +{ + if (self = [super init]) { + _reuseDict = [NSMutableDictionary dictionary]; + } + return self; +} + +- (void)addItemView:(UIView *)itemView forReuseIdentifier:(NSString *)reuseIdentifier +{ + if (reuseIdentifier == nil || itemView == nil) { + return; + } + NSMutableSet *reuseSet = [_reuseDict tm_safeObjectForKey:reuseIdentifier]; + if (!reuseSet) { + reuseSet = [NSMutableSet set]; + [_reuseDict setObject:reuseSet forKey:reuseIdentifier]; + } + [reuseSet addObject:itemView]; +} + +- (UIView *)dequeueItemViewForReuseIdentifier:(NSString *)reuseIdentifier +{ + return [self dequeueItemViewForReuseIdentifier:reuseIdentifier andMuiID:nil]; +} + +- (UIView *)dequeueItemViewForReuseIdentifier:(NSString *)reuseIdentifier andMuiID:(NSString *)muiID +{ + if (reuseIdentifier == nil) { + return nil; + } + UIView *result = nil; + NSMutableSet *reuseSet = [_reuseDict tm_safeObjectForKey:reuseIdentifier]; + if (reuseSet && reuseSet.count > 0) { + if (!muiID) { + result = [reuseSet anyObject]; + [reuseSet removeObject:result]; + } else { + for (UIView *itemView in reuseSet) { + if ([itemView.muiID isEqualToString:muiID]) { + result = itemView; + break; + } + } + if (result) { + [reuseSet removeObject:result]; + } + } + } + return result; +} + +- (void)clear +{ + [_reuseDict removeAllObjects]; +} + +@end diff --git a/LazyScrollView/TMLazyScrollView.h b/LazyScrollView/TMLazyScrollView.h index 68aad46..8ecfa04 100644 --- a/LazyScrollView/TMLazyScrollView.h +++ b/LazyScrollView/TMLazyScrollView.h @@ -8,6 +8,7 @@ #import #import "TMLazyItemModel.h" +@class TMLazyReusePool; @class TMLazyScrollView; @protocol TMLazyScrollViewDataSource @@ -41,6 +42,11 @@ @property (nonatomic, weak, nullable) id dataSource; +/** + Used for managing reuseable item views. + */ +@property (nonatomic, strong, nonnull) TMLazyReusePool *reusePool; + /** LazyScrollView can be used as a subview of another ScrollView. For example: @@ -71,24 +77,24 @@ - (void)reloadData; /** - Get reuseable view by reuseIdentifier. + Get reuseable item view by reuseIdentifier. */ - (nullable UIView *)dequeueReusableItemWithIdentifier:(nonnull NSString *)identifier; /** - Get reuseable view by reuseIdentifier and muiID. + Get reuseable item view by reuseIdentifier and muiID. MuiID has higher priority. */ - (nullable UIView *)dequeueReusableItemWithIdentifier:(nonnull NSString *)identifier muiID:(nullable NSString *)muiID; -- (void)clearItemViewsAndReusePool; -- (void)removeAllLayouts __deprecated_msg("use clearItemViewsAndReusePool"); +- (void)clearItemsAndReusePool; +- (void)removeAllLayouts __deprecated_msg("use clearItemsAndReusePool"); /** After call this method, the times of 'mui_didEnterWithTimes:' will start from 0. */ -- (void)resetItemViewsEnterTimes; -- (void)resetViewEnterTimes __deprecated_msg("use resetItemViewsEnterTimes"); +- (void)resetItemsEnterTimes; +- (void)resetViewEnterTimes __deprecated_msg("use resetItemsEnterTimes"); @end diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index a5bcd4f..7d19d57 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -9,6 +9,7 @@ #import #import "TMLazyItemViewProtocol.h" #import "UIView+TMLazyScrollView.h" +#import "TMLazyReusePool.h" #define LazyBufferHeight 20.0 #define LazyHalfBufferHeight (LazyBufferHeight / 2.0) @@ -20,11 +21,6 @@ @interface TMLazyScrollView () { // Store item models. NSMutableArray *_itemsFrames; - // Store reuseable cells by reuseIdentifier. - NSMutableDictionary *> *_recycledIdentifierItemsDic; - // Store reuseable cells by muiID. - NSMutableDictionary *_recycledMuiIDItemsDic; - // Store view models below contentOffset of ScrollView NSMutableSet *_firstSet; // Store view models above contentOffset + height of ScrollView @@ -109,8 +105,7 @@ - (id)initWithFrame:(CGRect)frame _modelsSortedByTop = [[NSArray alloc] init]; _modelsSortedByBottom = [[NSArray alloc]init]; - _recycledIdentifierItemsDic = [[NSMutableDictionary alloc] init]; - _recycledMuiIDItemsDic = [[NSMutableDictionary alloc] init]; + _reusePool = [TMLazyReusePool new]; _visibleItems = [[NSMutableSet alloc] init]; _inScreenVisibleItems = [[NSMutableSet alloc] init]; @@ -341,12 +336,9 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa // If this view should be recycled and the length of its reuseidentifier is over 0. if (view.reuseIdentifier.length > 0) { // Then recycle the view. - NSMutableSet *recycledIdentifierSet = [self recycledIdentifierSet:view.reuseIdentifier]; - [recycledIdentifierSet addObject:view]; + [self.reusePool addItemView:view forReuseIdentifier:view.reuseIdentifier]; view.hidden = YES; [recycledItems addObject:view]; - // Also add to muiID recycle dict. - [_recycledMuiIDItemsDic tm_safeSetObject:view forKey:view.muiID]; } else if(isReload && view.muiID) { // Need to reload unreusable views. [_shouldReloadItems addObject:view.muiID]; @@ -406,21 +398,6 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa } } -// Get NSSet accroding to reuse identifier. -- (NSMutableSet *)recycledIdentifierSet:(NSString *)reuseIdentifier; -{ - if (reuseIdentifier.length == 0) { - return nil; - } - - NSMutableSet *result = [_recycledIdentifierItemsDic tm_safeObjectForKey:reuseIdentifier]; - if (result == nil) { - result = [[NSMutableSet alloc] init]; - [_recycledIdentifierItemsDic setObject:result forKey:reuseIdentifier]; - } - return result; -} - // Reloads everything and redisplays visible views. - (void)reloadData { @@ -456,52 +433,30 @@ - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier // Use muiID for higher priority. - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSString *)muiID { - UIView *view = nil; - - if (_currentVisibleItemMuiID) { - NSSet *visibles = _visibleItems; - for (UIView *v in visibles) { - if ([v.muiID isEqualToString:_currentVisibleItemMuiID] && [v.reuseIdentifier isEqualToString:identifier]) { - view = v; - break; + UIView *result = nil; + if (identifier && identifier.length > 0) { + if (_currentVisibleItemMuiID) { + for (UIView *item in _visibleItems) { + if ([item.muiID isEqualToString:_currentVisibleItemMuiID] && [item.reuseIdentifier isEqualToString:identifier]) { + result = item; + break; + } } } - } else if(muiID && [muiID isKindOfClass:[NSString class]] && muiID.length > 0) { - // Try to get reusable view from muiID dict. - view = [_recycledMuiIDItemsDic tm_safeObjectForKey:muiID class:[UIView class]]; - if (view && view.reuseIdentifier.length > 0 && [view.reuseIdentifier isEqualToString:identifier]) - { - NSMutableSet *recycledIdentifierSet = [self recycledIdentifierSet:identifier]; - if (muiID && [muiID isKindOfClass:[NSString class]] && muiID.length > 0) { - [_recycledMuiIDItemsDic removeObjectForKey:muiID]; - } - [recycledIdentifierSet removeObject:view]; - view.gestureRecognizers = nil; - } else { - view = nil; + if (result == nil && muiID && muiID.length > 0) { + result = [self.reusePool dequeueItemViewForReuseIdentifier:identifier andMuiID:muiID]; } - } - - if (nil == view) { - NSMutableSet *recycledIdentifierSet = [self recycledIdentifierSet:identifier]; - view = [recycledIdentifierSet anyObject]; - if (view && view.reuseIdentifier.length > 0) { - // If exist reusable view, remove it from recycledSet and recycledMuiIDItemsDic. - if (view.muiID && [view.muiID isKindOfClass:[NSString class]] && view.muiID.length > 0) { - [_recycledMuiIDItemsDic removeObjectForKey:view.muiID]; + if (result == nil) { + result = [self.reusePool dequeueItemViewForReuseIdentifier:identifier]; + } + if (result) { + result.gestureRecognizers = nil; + if ([result respondsToSelector:@selector(mui_prepareForReuse)]) { + [(id)result mui_prepareForReuse]; } - [recycledIdentifierSet removeObject:view]; - // Then remove all gesture recognizers of it. - view.gestureRecognizers = nil; - } else { - view = nil; } } - - if ([view conformsToProtocol:@protocol(TMLazyItemViewProtocol)] && [view respondsToSelector:@selector(mui_prepareForReuse)]) { - [(UIView *)view mui_prepareForReuse]; - } - return view; + return result; } //Make sure whether the view is visible accroding to muiID. @@ -518,25 +473,21 @@ - (BOOL)isCellVisible:(NSString *)muiID return result; } -- (void)clearItemViewsAndReusePool +- (void)clearItemsAndReusePool { - NSSet *visibles = _visibleItems; - for (UIView *view in visibles) { - NSMutableSet *recycledIdentifierSet = [self recycledIdentifierSet:view.reuseIdentifier]; - [recycledIdentifierSet addObject:view]; + for (UIView *view in _visibleItems) { view.hidden = YES; } [_visibleItems removeAllObjects]; - [_recycledIdentifierItemsDic removeAllObjects]; - [_recycledMuiIDItemsDic removeAllObjects]; + [self.reusePool clear]; } - (void)removeAllLayouts { - [self clearItemViewsAndReusePool]; + [self clearItemsAndReusePool]; } -- (void)resetItemViewsEnterTimes +- (void)resetItemsEnterTimes { [_enterDic removeAllObjects]; _lastVisibleMuiID = nil; @@ -544,7 +495,7 @@ - (void)resetItemViewsEnterTimes - (void)resetViewEnterTimes { - [self resetItemViewsEnterTimes]; + [self resetItemsEnterTimes]; } @end diff --git a/TMUtils/NSArray+TMSafeUtils.h b/TMUtils/NSArray+TMSafeUtils.h index 643711a..217d167 100644 --- a/TMUtils/NSArray+TMSafeUtils.h +++ b/TMUtils/NSArray+TMSafeUtils.h @@ -33,5 +33,6 @@ - (void)tm_safeInsertObject:(id)anObject atIndex:(NSUInteger)index; - (void)tm_safeReplaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject; - (void)tm_safeRemoveObjectAtIndex:(NSUInteger)index; +- (void)tm_safeRemoveObject:(id)anObject; @end diff --git a/TMUtils/NSArray+TMSafeUtils.m b/TMUtils/NSArray+TMSafeUtils.m index cd13f8f..5030804 100644 --- a/TMUtils/NSArray+TMSafeUtils.m +++ b/TMUtils/NSArray+TMSafeUtils.m @@ -152,5 +152,12 @@ - (void)tm_safeRemoveObjectAtIndex:(NSUInteger)index } } +- (void)tm_safeRemoveObject:(id)anObject +{ + if (anObject != nil) { + [self removeObject:anObject]; + } +} + @end From 30df8a50c1fcec871d1326f6a5ef469941ba9485 Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Fri, 2 Mar 2018 16:24:20 +0800 Subject: [PATCH 13/30] update outScrollView feature --- LazyScrollView/TMLazyScrollView.h | 8 --- LazyScrollView/TMLazyScrollView.m | 93 ++++++++++++++++--------------- 2 files changed, 48 insertions(+), 53 deletions(-) diff --git a/LazyScrollView/TMLazyScrollView.h b/LazyScrollView/TMLazyScrollView.h index 8ecfa04..0046bf1 100644 --- a/LazyScrollView/TMLazyScrollView.h +++ b/LazyScrollView/TMLazyScrollView.h @@ -97,11 +97,3 @@ - (void)resetViewEnterTimes __deprecated_msg("use resetItemsEnterTimes"); @end - -//**************************************************************** - -@interface TMLazyScrollViewObserver: NSObject - -@property (nonatomic, weak, nullable) TMLazyScrollView *lazyScrollView; - -@end diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index 7d19d57..5929615 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -13,13 +13,16 @@ #define LazyBufferHeight 20.0 #define LazyHalfBufferHeight (LazyBufferHeight / 2.0) +void * const LazyObserverContext = "LazyObserverContext"; + +@class TMLazyOuterScrollViewObserver; @interface TMLazyScrollView () { NSMutableSet *_visibleItems; NSMutableSet *_inScreenVisibleItems; // Store item models. - NSMutableArray *_itemsFrames; + NSMutableArray *_itemsModels; // Store view models below contentOffset of ScrollView NSMutableSet *_firstSet; @@ -46,7 +49,7 @@ @interface TMLazyScrollView () { // Store last time visible muiID. Used for calc enter times. NSSet *_lastVisibleMuiID; - TMLazyScrollViewObserver *_outerScrollViewObserver; + TMLazyOuterScrollViewObserver *_outerScrollViewObserver; BOOL _forwardingDelegateCanPerformScrollViewDidScrollSelector; @@ -56,6 +59,8 @@ @interface TMLazyScrollView () { CGPoint _lastScrollOffset; } +- (void)outerScrollViewDidScroll; + @end @implementation TMLazyScrollView @@ -74,27 +79,22 @@ - (NSSet *)visibleItems -(void)setOuterScrollView:(UIScrollView *)outerScrollView { - _outerScrollView = outerScrollView; - if (_outerScrollViewObserver == nil) { - _outerScrollViewObserver = [[TMLazyScrollViewObserver alloc]init]; - _outerScrollViewObserver.lazyScrollView = self; - } - - @try { - [outerScrollView removeObserver:_outerScrollViewObserver forKeyPath:@"contentOffset"]; + if (_outerScrollView != outerScrollView) { + if (_outerScrollView) { + [_outerScrollView removeObserver:self forKeyPath:@"contentOffset" context:LazyObserverContext]; + } + if (outerScrollView) { + [outerScrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:LazyObserverContext]; + } + _outerScrollView = outerScrollView; } - @catch (NSException * __unused exception) {} - [outerScrollView addObserver:_outerScrollViewObserver - forKeyPath:@"contentOffset" - options:NSKeyValueObservingOptionNew - context:nil]; } #pragma mark - Lifecycle -- (id)initWithFrame:(CGRect)frame +- (id)init { - if (self = [super initWithFrame:frame]) { + if (self = [super init]) { self.clipsToBounds = YES; self.autoresizesSubviews = NO; self.showsHorizontalScrollIndicator = NO; @@ -110,7 +110,7 @@ - (id)initWithFrame:(CGRect)frame _visibleItems = [[NSMutableSet alloc] init]; _inScreenVisibleItems = [[NSMutableSet alloc] init]; - _itemsFrames = [[NSMutableArray alloc] init]; + _itemsModels = [[NSMutableArray alloc] init]; _firstSet = [[NSMutableSet alloc] initWithCapacity:30]; _secondSet = [[NSMutableSet alloc] initWithCapacity:30]; @@ -124,14 +124,7 @@ - (void)dealloc { _dataSource = nil; self.delegate = nil; - if (_outerScrollView) { - @try { - [_outerScrollView removeObserver:_outerScrollViewObserver forKeyPath:@"contentOffset"]; - } - @catch (NSException *exception) { - - } - } + self.outerScrollView = nil; } #pragma mark - ScrollEvent @@ -139,8 +132,8 @@ - (void)dealloc - (void)setContentOffset:(CGPoint)contentOffset { [super setContentOffset:contentOffset]; - // Calculate which views should be shown. - // Calcuting will cost some time, so here is a buffer for reducing + // Calculate which item views should be shown. + // Calculating will cost some time, so here is a buffer for reducing // times of calculating. CGFloat currentY = contentOffset.y; CGFloat buffer = LazyHalfBufferHeight; @@ -151,6 +144,17 @@ - (void)setContentOffset:(CGPoint)contentOffset } } +- (void)outerScrollViewDidScroll +{ + CGFloat currentY = _outerScrollView.contentOffset.y; + CGFloat buffer = LazyHalfBufferHeight; + if (buffer < ABS(currentY - _lastScrollOffset.y)) { + _lastScrollOffset = _outerScrollView.contentOffset; + [self assembleSubviews]; + [self findViewsInVisibleRect]; + } +} + #pragma mark - Core Logic // Do Binary search here to find index in view model array. @@ -230,7 +234,7 @@ - (void)creatScrollViewIndex count = [_dataSource numberOfItemsInScrollView:self]; } - [_itemsFrames removeAllObjects]; + [_itemsModels removeAllObjects]; for (NSUInteger i = 0 ; i < count ; i++) { TMLazyItemModel *rectmodel = nil; if (_dataSource && @@ -241,10 +245,10 @@ - (void)creatScrollViewIndex rectmodel.muiID = [NSString stringWithFormat:@"%lu", (unsigned long)i]; } } - [_itemsFrames tm_safeAddObject:rectmodel]; + [_itemsModels tm_safeAddObject:rectmodel]; } - _modelsSortedByTop = [_itemsFrames sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { + _modelsSortedByTop = [_itemsModels sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { CGRect rect1 = [(TMLazyItemModel *) obj1 absRect]; CGRect rect2 = [(TMLazyItemModel *) obj2 absRect]; if (rect1.origin.y < rect2.origin.y) { @@ -256,7 +260,7 @@ - (void)creatScrollViewIndex } }]; - _modelsSortedByBottom = [_itemsFrames sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { + _modelsSortedByBottom = [_itemsModels sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { CGRect rect1 = [(TMLazyItemModel *) obj1 absRect]; CGRect rect2 = [(TMLazyItemModel *) obj2 absRect]; CGFloat bottom1 = CGRectGetMaxY(rect1); @@ -402,7 +406,7 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa - (void)reloadData { [self creatScrollViewIndex]; - if (_itemsFrames.count > 0) { + if (_itemsModels.count > 0) { if (_outerScrollView) { CGRect rectInScrollView = [self convertRect:self.frame toView:_outerScrollView]; CGFloat minY = _outerScrollView.contentOffset.y - rectInScrollView.origin.y - LazyBufferHeight; @@ -500,22 +504,21 @@ - (void)resetViewEnterTimes @end -@implementation TMLazyScrollViewObserver +//**************************************************************** + +@interface TMLazyOuterScrollViewObserver: NSObject + +@property (nonatomic, weak) TMLazyScrollView *lazyScrollView; + +@end + +@implementation TMLazyOuterScrollViewObserver - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if([keyPath isEqualToString:@"contentOffset"]) - { - CGPoint newPoint = [[change objectForKey:NSKeyValueChangeNewKey] CGPointValue]; - CGFloat buffer = LazyHalfBufferHeight; - if (buffer < ABS(newPoint.y - _lazyScrollView->_lastScrollOffset.y)) { - _lazyScrollView->_lastScrollOffset = newPoint; - [_lazyScrollView assembleSubviews]; - [_lazyScrollView findViewsInVisibleRect]; - } + if (context == LazyObserverContext && [keyPath isEqualToString:@"contentOffset"] && _lazyScrollView) { + [_lazyScrollView outerScrollViewDidScroll]; } } @end - - From c2be3b4a9fd2d739ee9fc610a681b97880944ab9 Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Fri, 2 Mar 2018 17:10:11 +0800 Subject: [PATCH 14/30] update demo --- LazyScrollView/TMLazyScrollView.m | 4 +- .../project.pbxproj | 24 +++-- .../LazyScrollViewDemo/AppDelegate.m | 4 +- .../LazyScrollViewDemo/MainViewController.h | 12 +++ .../LazyScrollViewDemo/MainViewController.m | 56 ++++++++++++ ...ViewController.h => OuterViewController.h} | 6 +- .../LazyScrollViewDemo/OuterViewController.m | 36 ++++++++ .../LazyScrollViewDemo/ReuseViewController.h | 12 +++ ...ViewController.m => ReuseViewController.m} | 91 ++++++++++++------- 9 files changed, 196 insertions(+), 49 deletions(-) create mode 100644 LazyScrollViewDemo/LazyScrollViewDemo/MainViewController.h create mode 100644 LazyScrollViewDemo/LazyScrollViewDemo/MainViewController.m rename LazyScrollViewDemo/LazyScrollViewDemo/{ViewController.h => OuterViewController.h} (61%) create mode 100644 LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m create mode 100644 LazyScrollViewDemo/LazyScrollViewDemo/ReuseViewController.h rename LazyScrollViewDemo/LazyScrollViewDemo/{ViewController.m => ReuseViewController.m} (52%) diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index 5929615..48442b0 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -92,9 +92,9 @@ -(void)setOuterScrollView:(UIScrollView *)outerScrollView #pragma mark - Lifecycle -- (id)init +- (id)initWithFrame:(CGRect)frame { - if (self = [super init]) { + if (self = [super initWithFrame:frame]) { self.clipsToBounds = YES; self.autoresizesSubviews = NO; self.showsHorizontalScrollIndicator = NO; diff --git a/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj b/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj index 85ae07e..0505582 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj +++ b/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj @@ -8,10 +8,12 @@ /* Begin PBXBuildFile section */ 927CAE3B2046B37700BD3B19 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 927CAE3A2046B37700BD3B19 /* AppDelegate.m */; }; - 927CAE3E2046B37700BD3B19 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 927CAE3D2046B37700BD3B19 /* ViewController.m */; }; + 927CAE3E2046B37700BD3B19 /* ReuseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 927CAE3D2046B37700BD3B19 /* ReuseViewController.m */; }; 927CAE432046B37700BD3B19 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 927CAE422046B37700BD3B19 /* Assets.xcassets */; }; 927CAE462046B37700BD3B19 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 927CAE442046B37700BD3B19 /* LaunchScreen.storyboard */; }; 927CAE492046B37700BD3B19 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 927CAE482046B37700BD3B19 /* main.m */; }; + 92F01C4720493C36000983CA /* MainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 92F01C4620493C36000983CA /* MainViewController.m */; }; + 92F01C4A20493D9C000983CA /* OuterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 92F01C4920493D9C000983CA /* OuterViewController.m */; }; D1AB44F5D8E0B6975BF3CDF3 /* libPods-LazyScrollViewDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CA97F266258FD7B207134C76 /* libPods-LazyScrollViewDemo.a */; }; /* End PBXBuildFile section */ @@ -20,12 +22,16 @@ 927CAE362046B37700BD3B19 /* LazyScrollViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LazyScrollViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 927CAE392046B37700BD3B19 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 927CAE3A2046B37700BD3B19 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 927CAE3C2046B37700BD3B19 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; - 927CAE3D2046B37700BD3B19 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 927CAE3C2046B37700BD3B19 /* ReuseViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReuseViewController.h; sourceTree = ""; }; + 927CAE3D2046B37700BD3B19 /* ReuseViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ReuseViewController.m; sourceTree = ""; }; 927CAE422046B37700BD3B19 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 927CAE452046B37700BD3B19 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 927CAE472046B37700BD3B19 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 927CAE482046B37700BD3B19 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 92F01C4520493C36000983CA /* MainViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainViewController.h; sourceTree = ""; }; + 92F01C4620493C36000983CA /* MainViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MainViewController.m; sourceTree = ""; }; + 92F01C4820493D9C000983CA /* OuterViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OuterViewController.h; sourceTree = ""; }; + 92F01C4920493D9C000983CA /* OuterViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OuterViewController.m; sourceTree = ""; }; AA72B4C430D44D71DA7D6BD2 /* Pods-LazyScrollViewDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LazyScrollViewDemo.release.xcconfig"; path = "../Pods/Target Support Files/Pods-LazyScrollViewDemo/Pods-LazyScrollViewDemo.release.xcconfig"; sourceTree = ""; }; CA97F266258FD7B207134C76 /* libPods-LazyScrollViewDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-LazyScrollViewDemo.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -74,8 +80,12 @@ children = ( 927CAE392046B37700BD3B19 /* AppDelegate.h */, 927CAE3A2046B37700BD3B19 /* AppDelegate.m */, - 927CAE3C2046B37700BD3B19 /* ViewController.h */, - 927CAE3D2046B37700BD3B19 /* ViewController.m */, + 92F01C4520493C36000983CA /* MainViewController.h */, + 92F01C4620493C36000983CA /* MainViewController.m */, + 927CAE3C2046B37700BD3B19 /* ReuseViewController.h */, + 927CAE3D2046B37700BD3B19 /* ReuseViewController.m */, + 92F01C4820493D9C000983CA /* OuterViewController.h */, + 92F01C4920493D9C000983CA /* OuterViewController.m */, 927CAE422046B37700BD3B19 /* Assets.xcassets */, 927CAE442046B37700BD3B19 /* LaunchScreen.storyboard */, 927CAE472046B37700BD3B19 /* Info.plist */, @@ -216,9 +226,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 927CAE3E2046B37700BD3B19 /* ViewController.m in Sources */, + 927CAE3E2046B37700BD3B19 /* ReuseViewController.m in Sources */, 927CAE492046B37700BD3B19 /* main.m in Sources */, + 92F01C4A20493D9C000983CA /* OuterViewController.m in Sources */, 927CAE3B2046B37700BD3B19 /* AppDelegate.m in Sources */, + 92F01C4720493C36000983CA /* MainViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.m b/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.m index f559b12..b70811f 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.m +++ b/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.m @@ -6,14 +6,14 @@ // #import "AppDelegate.h" -#import "ViewController.h" +#import "MainViewController.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[ViewController new]]; + self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[MainViewController new]]; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; return YES; diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/MainViewController.h b/LazyScrollViewDemo/LazyScrollViewDemo/MainViewController.h new file mode 100644 index 0000000..e4fe59e --- /dev/null +++ b/LazyScrollViewDemo/LazyScrollViewDemo/MainViewController.h @@ -0,0 +1,12 @@ +// +// MainViewController.h +// LazyScrollViewDemo +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import + +@interface MainViewController : UITableViewController + +@end diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/MainViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/MainViewController.m new file mode 100644 index 0000000..b5be26c --- /dev/null +++ b/LazyScrollViewDemo/LazyScrollViewDemo/MainViewController.m @@ -0,0 +1,56 @@ +// +// MainViewController.m +// LazyScrollViewDemo +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import "MainViewController.h" +#import "OuterViewController.h" + +@interface MainViewController () + +@property (nonatomic, strong) NSArray *demoArray; + +@end + +@implementation MainViewController + +- (instancetype)init +{ + if (self = [super init]) { + self.title = @"LazyScrollDemo"; + self.demoArray = @[@"Reuse", @"OuterScrollView"]; + } + return self; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return self.demoArray.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; + if (!cell) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; + } + cell.textLabel.text = self.demoArray[indexPath.row]; + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSString *demoName = self.demoArray[indexPath.row]; + UIViewController *vc; + if ([demoName isEqualToString:@"OuterScrollView"]) { + vc = [OuterViewController new]; + } else { + Class demoVcClass = NSClassFromString([demoName stringByAppendingString:@"ViewController"]); + vc = [demoVcClass new]; + } + [self.navigationController pushViewController:vc animated:YES]; +} + +@end diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.h b/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.h similarity index 61% rename from LazyScrollViewDemo/LazyScrollViewDemo/ViewController.h rename to LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.h index bafcb4e..8fd0bd8 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.h +++ b/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.h @@ -1,5 +1,5 @@ // -// ViewController.h +// OuterViewController.h // LazyScrollViewDemo // // Copyright (c) 2015-2018 Alibaba. All rights reserved. @@ -7,8 +7,6 @@ #import -@interface ViewController : UIViewController - +@interface OuterViewController : UIViewController @end - diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m new file mode 100644 index 0000000..4e0cb18 --- /dev/null +++ b/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m @@ -0,0 +1,36 @@ +// +// OuterViewController.m +// LazyScrollViewDemo +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import "OuterViewController.h" + +@interface OuterViewController () + +@end + +@implementation OuterViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. +} + +- (void)didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +/* +#pragma mark - Navigation + +// In a storyboard-based application, you will often want to do a little preparation before navigation +- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { + // Get the new view controller using [segue destinationViewController]. + // Pass the selected object to the new view controller. +} +*/ + +@end diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/ReuseViewController.h b/LazyScrollViewDemo/LazyScrollViewDemo/ReuseViewController.h new file mode 100644 index 0000000..2c9b34d --- /dev/null +++ b/LazyScrollViewDemo/LazyScrollViewDemo/ReuseViewController.h @@ -0,0 +1,12 @@ +// +// ReuseViewController.h +// LazyScrollViewDemo +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import + +@interface ReuseViewController : UIViewController + +@end diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/ReuseViewController.m similarity index 52% rename from LazyScrollViewDemo/LazyScrollViewDemo/ViewController.m rename to LazyScrollViewDemo/LazyScrollViewDemo/ReuseViewController.m index b954eef..d899462 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.m +++ b/LazyScrollViewDemo/LazyScrollViewDemo/ReuseViewController.m @@ -1,12 +1,13 @@ // -// ViewController.m +// ReuseViewController.m // LazyScrollViewDemo // // Copyright (c) 2015-2018 Alibaba. All rights reserved. // -#import "ViewController.h" +#import "ReuseViewController.h" #import +#import @interface LazyScrollViewCustomView : UILabel @@ -23,59 +24,87 @@ - (void)mui_prepareForReuse @end +//**************************************************************** -@interface ViewController () { - NSMutableArray * rectArray; +@interface ReuseViewController () { + NSMutableArray * _rectArray; + NSMutableArray * _colorArray; } @end -@implementation ViewController +@implementation ReuseViewController + +- (instancetype)init +{ + if (self = [super init]) { + self.title = @"Reuse"; + } + return self; +} - (void)viewDidLoad { [super viewDidLoad]; - - // STEP 1 . Create LazyScrollView - TMLazyScrollView *scrollview = [[TMLazyScrollView alloc] init]; - scrollview.frame = self.view.bounds; + + // STEP 1: Create LazyScrollView + TMLazyScrollView *scrollview = [[TMLazyScrollView alloc] initWithFrame:self.view.bounds]; scrollview.dataSource = self; scrollview.autoAddSubview = YES; [self.view addSubview:scrollview]; // Here is frame array for test. - // LazyScrollView must know every rect before rending. - rectArray = [[NSMutableArray alloc] init]; - CGFloat maxY = 0, currentY = 10; + // LazyScrollView must know item view's frame before rending. + _rectArray = [[NSMutableArray alloc] init]; + CGFloat maxY = 0, currentY = 50; CGFloat viewWidth = CGRectGetWidth(self.view.bounds); - // Create a single column layout with 5 elements; - for (int i = 0; i < 5; i++) { + // Create a double column layout with 10 elements. + for (int i = 0; i < 10; i++) { + [self addRect:CGRectMake((i % 2) * (viewWidth - 20 + 3) / 2 + 10, i / 2 * 80 + currentY, (viewWidth - 20 - 3) / 2, 80 - 3) andUpdateMaxY:&maxY]; + } + // Create a single column layout with 10 elements. + currentY = maxY + 10; + for (int i = 0; i < 10; i++) { [self addRect:CGRectMake(10, i * 80 + currentY, viewWidth - 20, 80 - 3) andUpdateMaxY:&maxY]; } - // Create a double column layout with 10 elements; + // Create a double column layout with 10 elements. currentY = maxY + 10; for (int i = 0; i < 10; i++) { [self addRect:CGRectMake((i % 2) * (viewWidth - 20 + 3) / 2 + 10, i / 2 * 80 + currentY, (viewWidth - 20 - 3) / 2, 80 - 3) andUpdateMaxY:&maxY]; } - // Create a trible column layout with 15 elements; - currentY = maxY + 10; - for (int i = 0; i < 15; i++) { - [self addRect:CGRectMake((i % 3) * (viewWidth - 20 + 6) / 3 + 10, i / 3 * 80 + currentY, (viewWidth - 20 - 6) / 3, 80 - 3) andUpdateMaxY:&maxY]; + + // Create color array. + // The color order is like rainbow. + _colorArray = [NSMutableArray arrayWithCapacity:_rectArray.count]; + CGFloat hue = 0; + for (int i = 0; i < _rectArray.count; i++) { + [_colorArray addObject:[UIColor colorWithHue:hue saturation:1 brightness:1 alpha:1]]; + hue += 0.04; + if (hue >= 1) { + hue = 0; + } } - // STEP 3 reload LazyScrollView - scrollview.contentSize = CGSizeMake(CGRectGetWidth(self.view.bounds), maxY + 10); + // STEP 3: reload LazyScrollView + scrollview.contentSize = CGSizeMake(viewWidth, maxY + 10); [scrollview reloadData]; + + // A tip. + UILabel *tipLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, viewWidth - 20, 30)]; + tipLabel.font = [UIFont systemFontOfSize:12]; + tipLabel.numberOfLines = 0; + tipLabel.text = @"Item views's color should be from red to blue. They are reused. Magenta should not be appeared."; + [scrollview addSubview:tipLabel]; } -// STEP 2 implement datasource delegate. +// STEP 2: implement datasource. - (NSUInteger)numberOfItemsInScrollView:(TMLazyScrollView *)scrollView { - return rectArray.count; + return _rectArray.count; } - (TMLazyItemModel *)scrollView:(TMLazyScrollView *)scrollView itemModelAtIndex:(NSUInteger)index { - CGRect rect = [(NSValue *)[rectArray objectAtIndex:index] CGRectValue]; + CGRect rect = [(NSValue *)[_rectArray objectAtIndex:index] CGRectValue]; TMLazyItemModel *rectModel = [[TMLazyItemModel alloc] init]; rectModel.absRect = rect; rectModel.muiID = [NSString stringWithFormat:@"%zd", index]; @@ -93,9 +122,9 @@ - (UIView *)scrollView:(TMLazyScrollView *)scrollView itemByMuiID:(NSString *)mu label.textAlignment = NSTextAlignmentCenter; label.numberOfLines = 0; label.reuseIdentifier = @"testView"; - label.backgroundColor = [self randomColor]; + label.backgroundColor = [_colorArray tm_safeObjectAtIndex:index]; } - label.frame = [(NSValue *)[rectArray objectAtIndex:index] CGRectValue]; + label.frame = [(NSValue *)[_rectArray objectAtIndex:index] CGRectValue]; if (label.reuseTimes > 0) { label.text = [NSString stringWithFormat:@"%zd\nlast index: %@\nreuse times: %zd", index, label.muiID, label.reuseTimes]; } else { @@ -111,15 +140,7 @@ - (void)addRect:(CGRect)newRect andUpdateMaxY:(CGFloat *)maxY if (CGRectGetMaxY(newRect) > *maxY) { *maxY = CGRectGetMaxY(newRect); } - [rectArray addObject:[NSValue valueWithCGRect:newRect]]; -} - -- (UIColor *)randomColor -{ - CGFloat hue = ( arc4random() % 256 / 256.0 ); // 0.0 to 1.0 - CGFloat saturation = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from white - CGFloat brightness = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from black - return [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:1]; + [_rectArray addObject:[NSValue valueWithCGRect:newRect]]; } @end From 8dfca9463dcb3cd1fa00ab88a336ffe91b95f7a0 Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Fri, 2 Mar 2018 17:52:51 +0800 Subject: [PATCH 15/30] add a demo for outerScrollView --- LazyScrollView/TMLazyScrollView.h | 1 + LazyScrollView/TMLazyScrollView.m | 39 +++--- .../LazyScrollViewDemo/OuterViewController.h | 2 +- .../LazyScrollViewDemo/OuterViewController.m | 119 ++++++++++++++++-- .../LazyScrollViewDemo/ReuseViewController.m | 4 +- 5 files changed, 135 insertions(+), 30 deletions(-) diff --git a/LazyScrollView/TMLazyScrollView.h b/LazyScrollView/TMLazyScrollView.h index 0046bf1..56c5755 100644 --- a/LazyScrollView/TMLazyScrollView.h +++ b/LazyScrollView/TMLazyScrollView.h @@ -52,6 +52,7 @@ For example: You can use LazyScrollView as footerView of TableView. Then the outerScrollView should be that TableView. + You MUST set this property to nil before the outerScrollView's dealloc. */ @property (nonatomic, weak, nullable) UIScrollView *outerScrollView; diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index 48442b0..11cb956 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -15,7 +15,13 @@ #define LazyHalfBufferHeight (LazyBufferHeight / 2.0) void * const LazyObserverContext = "LazyObserverContext"; -@class TMLazyOuterScrollViewObserver; +@interface TMLazyOuterScrollViewObserver: NSObject + +@property (nonatomic, weak) TMLazyScrollView *lazyScrollView; + +@end + +//**************************************************************** @interface TMLazyScrollView () { NSMutableSet *_visibleItems; @@ -49,8 +55,6 @@ @interface TMLazyScrollView () { // Store last time visible muiID. Used for calc enter times. NSSet *_lastVisibleMuiID; - TMLazyOuterScrollViewObserver *_outerScrollViewObserver; - BOOL _forwardingDelegateCanPerformScrollViewDidScrollSelector; @package @@ -59,13 +63,15 @@ @interface TMLazyScrollView () { CGPoint _lastScrollOffset; } +@property (nonatomic, strong) TMLazyOuterScrollViewObserver *outerScrollViewObserver; + - (void)outerScrollViewDidScroll; @end @implementation TMLazyScrollView -#pragma mark - Getter & Setter +#pragma mark Getter & Setter - (NSSet *)inScreenVisibleItems { @@ -77,20 +83,29 @@ - (NSSet *)visibleItems return [_visibleItems copy]; } +- (TMLazyOuterScrollViewObserver *)outerScrollViewObserver +{ + if (!_outerScrollViewObserver) { + _outerScrollViewObserver = [TMLazyOuterScrollViewObserver new]; + _outerScrollViewObserver.lazyScrollView = self; + } + return _outerScrollViewObserver; +} + -(void)setOuterScrollView:(UIScrollView *)outerScrollView { if (_outerScrollView != outerScrollView) { if (_outerScrollView) { - [_outerScrollView removeObserver:self forKeyPath:@"contentOffset" context:LazyObserverContext]; + [_outerScrollView removeObserver:self.outerScrollViewObserver forKeyPath:@"contentOffset" context:LazyObserverContext]; } if (outerScrollView) { - [outerScrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:LazyObserverContext]; + [outerScrollView addObserver:self.outerScrollViewObserver forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:LazyObserverContext]; } _outerScrollView = outerScrollView; } } -#pragma mark - Lifecycle +#pragma mark Lifecycle - (id)initWithFrame:(CGRect)frame { @@ -127,7 +142,7 @@ - (void)dealloc self.outerScrollView = nil; } -#pragma mark - ScrollEvent +#pragma mark ScrollEvent - (void)setContentOffset:(CGPoint)contentOffset { @@ -155,7 +170,7 @@ - (void)outerScrollViewDidScroll } } -#pragma mark - Core Logic +#pragma mark Core Logic // Do Binary search here to find index in view model array. - (NSUInteger)binarySearchForIndex:(NSArray *)frameArray baseLine:(CGFloat)baseLine isFromTop:(BOOL)fromTop @@ -506,12 +521,6 @@ - (void)resetViewEnterTimes //**************************************************************** -@interface TMLazyOuterScrollViewObserver: NSObject - -@property (nonatomic, weak) TMLazyScrollView *lazyScrollView; - -@end - @implementation TMLazyOuterScrollViewObserver - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.h b/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.h index 8fd0bd8..cc41b30 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.h +++ b/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.h @@ -7,6 +7,6 @@ #import -@interface OuterViewController : UIViewController +@interface OuterViewController : UITableViewController @end diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m index 4e0cb18..8046660 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m +++ b/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m @@ -6,31 +6,124 @@ // #import "OuterViewController.h" +#import +#import -@interface OuterViewController () +@interface OuterViewController () { + NSMutableArray * _rectArray; + NSMutableArray * _colorArray; + TMLazyScrollView * _scrollView; +} @end @implementation OuterViewController -- (void)viewDidLoad { +- (instancetype)init +{ + if (self = [super init]) { + self.title = @"Outer"; + } + return self; +} + +- (void)dealloc +{ + // VERY IMPORTANT! + _scrollView.outerScrollView = nil; +} + +- (void)viewDidLoad +{ [super viewDidLoad]; - // Do any additional setup after loading the view. + + _scrollView = [[TMLazyScrollView alloc] initWithFrame:self.view.bounds]; + _scrollView.dataSource = self; + _scrollView.autoAddSubview = YES; + + _rectArray = [[NSMutableArray alloc] init]; + CGFloat maxY = 0; + CGFloat viewWidth = CGRectGetWidth(self.view.bounds); + for (int i = 0; i < 20; i++) { + [self addRect:CGRectMake((i % 2) * (viewWidth - 20 + 3) / 2 + 10, i / 2 * 80 + 10, (viewWidth - 20 - 3) / 2, 80 - 3) andUpdateMaxY:&maxY]; + } + + _colorArray = [NSMutableArray arrayWithCapacity:_rectArray.count]; + CGFloat hue = 0; + for (int i = 0; i < _rectArray.count; i++) { + [_colorArray addObject:[UIColor colorWithHue:hue saturation:1 brightness:1 alpha:1]]; + hue += 0.05; + if (hue >= 1) { + hue = 0; + } + } + + _scrollView.contentSize = CGSizeMake(viewWidth, maxY + 10); + _scrollView.frame = CGRectMake(0, 0, viewWidth, maxY + 10); + _scrollView.outerScrollView = self.tableView; + self.tableView.tableFooterView = _scrollView; + [_scrollView reloadData]; +} + +#pragma mark TableView + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return 10; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; + if (!cell) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; + } + if (indexPath.row == 0) { + cell.textLabel.text = @"The table view's footer view is a LazyScrollView."; + } else { + cell.textLabel.text = [@(indexPath.row) stringValue]; + } + return cell; +} + +#pragma mark LazyScrollView + +- (NSUInteger)numberOfItemsInScrollView:(TMLazyScrollView *)scrollView +{ + return _rectArray.count; +} + +- (TMLazyItemModel *)scrollView:(TMLazyScrollView *)scrollView itemModelAtIndex:(NSUInteger)index +{ + CGRect rect = [(NSValue *)[_rectArray objectAtIndex:index] CGRectValue]; + TMLazyItemModel *rectModel = [[TMLazyItemModel alloc] init]; + rectModel.absRect = rect; + rectModel.muiID = [NSString stringWithFormat:@"%zd", index]; + return rectModel; } -- (void)didReceiveMemoryWarning { - [super didReceiveMemoryWarning]; - // Dispose of any resources that can be recreated. +- (UIView *)scrollView:(TMLazyScrollView *)scrollView itemByMuiID:(NSString *)muiID +{ + UIView *view = (UIView *)[scrollView dequeueReusableItemWithIdentifier:@"testView"]; + NSInteger index = [muiID integerValue]; + if (!view) { + NSLog(@"create a new view"); + view = [UIView new]; + view.reuseIdentifier = @"testView"; + view.backgroundColor = [_colorArray tm_safeObjectAtIndex:index]; + } + view.frame = [(NSValue *)[_rectArray objectAtIndex:index] CGRectValue]; + return view; } -/* -#pragma mark - Navigation +#pragma mark Private -// In a storyboard-based application, you will often want to do a little preparation before navigation -- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { - // Get the new view controller using [segue destinationViewController]. - // Pass the selected object to the new view controller. +- (void)addRect:(CGRect)newRect andUpdateMaxY:(CGFloat *)maxY +{ + if (CGRectGetMaxY(newRect) > *maxY) { + *maxY = CGRectGetMaxY(newRect); + } + [_rectArray addObject:[NSValue valueWithCGRect:newRect]]; } -*/ @end diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/ReuseViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/ReuseViewController.m index d899462..b6a7c36 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/ReuseViewController.m +++ b/LazyScrollViewDemo/LazyScrollViewDemo/ReuseViewController.m @@ -96,6 +96,8 @@ - (void)viewDidLoad { [scrollview addSubview:tipLabel]; } +#pragma mark LazyScrollView + // STEP 2: implement datasource. - (NSUInteger)numberOfItemsInScrollView:(TMLazyScrollView *)scrollView { @@ -133,7 +135,7 @@ - (UIView *)scrollView:(TMLazyScrollView *)scrollView itemByMuiID:(NSString *)mu return label; } -#pragma mark - Private +#pragma mark Private - (void)addRect:(CGRect)newRect andUpdateMaxY:(CGFloat *)maxY { From 36ade848d0d788a711f487acd80113fd1b0793ed Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Mon, 12 Mar 2018 20:30:40 +0800 Subject: [PATCH 16/30] add autoClearGestures feature --- LazyScrollView/TMLazyScrollView.h | 8 ++++++++ LazyScrollView/TMLazyScrollView.m | 6 ++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/LazyScrollView/TMLazyScrollView.h b/LazyScrollView/TMLazyScrollView.h index 56c5755..938dd3b 100644 --- a/LazyScrollView/TMLazyScrollView.h +++ b/LazyScrollView/TMLazyScrollView.h @@ -60,9 +60,17 @@ If it is YES, LazyScrollView will add created item view into its subviews automatically. Default value is NO. + Please only set this value before you reload data for the first time. */ @property (nonatomic, assign) BOOL autoAddSubview; +/** + If it is YES, LazyScrollView will clear all gestures for item view before + reusing it. + Default value is YES. + */ +@property (nonatomic, assign) BOOL autoClearGestures; + /** Item views which is in the buffer area. They will be shown soon. diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index 11cb956..8db51fa 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -11,8 +11,8 @@ #import "UIView+TMLazyScrollView.h" #import "TMLazyReusePool.h" -#define LazyBufferHeight 20.0 -#define LazyHalfBufferHeight (LazyBufferHeight / 2.0) +#define LazyBufferHeight 30.0 +#define LazyHalfBufferHeight 15.0 void * const LazyObserverContext = "LazyObserverContext"; @interface TMLazyOuterScrollViewObserver: NSObject @@ -469,7 +469,9 @@ - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSSt result = [self.reusePool dequeueItemViewForReuseIdentifier:identifier]; } if (result) { + if (self.autoClearGestures) { result.gestureRecognizers = nil; + } if ([result respondsToSelector:@selector(mui_prepareForReuse)]) { [(id)result mui_prepareForReuse]; } From 020fec50322ba32d24bc37d209b0152a8fc67d39 Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Mon, 12 Mar 2018 20:31:42 +0800 Subject: [PATCH 17/30] rename properties --- LazyScrollView/TMLazyScrollView.m | 121 ++++++++++++++---------------- 1 file changed, 58 insertions(+), 63 deletions(-) diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index 8db51fa..ae38992 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -28,38 +28,34 @@ @interface TMLazyScrollView () { NSMutableSet *_inScreenVisibleItems; // Store item models. - NSMutableArray *_itemsModels; + NSMutableArray *_itemModels; // Store view models below contentOffset of ScrollView NSMutableSet *_firstSet; // Store view models above contentOffset + height of ScrollView NSMutableSet *_secondSet; - // View Model sorted by Top Edge. NSArray *_modelsSortedByTop; // View Model sorted by Bottom Edge. NSArray *_modelsSortedByBottom; - // It is used to store views need to assign new value after reload. - NSMutableSet *_shouldReloadItems; + // Store items which need to assign new value after reloading. + NSMutableSet *_shouldReloadingItems; - // Store the times of view entered the screen, the key is muiID. - NSMutableDictionary *_enterDic; + // Store the enter screen times of item. + NSMutableDictionary *_enterTimesDict; - // Record current muiID of visible view for calculate. + // Record current muiID of reloading item. // Will be used for dequeueReusableItem methods. - NSString *_currentVisibleItemMuiID; + NSString *_currentReloadingMuiID; // Record muiIDs of visible items. Used for calc enter times. - NSSet *_muiIDOfVisibleViews; - // Store last time visible muiID. Used for calc enter times. - NSSet *_lastVisibleMuiID; - - BOOL _forwardingDelegateCanPerformScrollViewDidScrollSelector; + NSSet *_muiIdOfCurentVisibleItems; + // Store last time visible muiIDs. Used for calc enter times. + NSSet *_muiIdOfLastVisibleItems; - @package - // Record contentOffset of scrollview in previous time that calculate - // views to show + // Record contentOffset of scrollview in previous time that + // calculate views to show. CGPoint _lastScrollOffset; } @@ -111,26 +107,25 @@ - (id)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.clipsToBounds = YES; - self.autoresizesSubviews = NO; self.showsHorizontalScrollIndicator = NO; self.showsVerticalScrollIndicator = NO; - - _shouldReloadItems = [[NSMutableSet alloc] init]; - - _modelsSortedByTop = [[NSArray alloc] init]; - _modelsSortedByBottom = [[NSArray alloc]init]; + _autoClearGestures = YES; _reusePool = [TMLazyReusePool new]; _visibleItems = [[NSMutableSet alloc] init]; _inScreenVisibleItems = [[NSMutableSet alloc] init]; - _itemsModels = [[NSMutableArray alloc] init]; + _itemModels = [[NSMutableArray alloc] init]; _firstSet = [[NSMutableSet alloc] initWithCapacity:30]; _secondSet = [[NSMutableSet alloc] initWithCapacity:30]; + _modelsSortedByTop = [[NSArray alloc] init]; + _modelsSortedByBottom = [[NSArray alloc]init]; + + _shouldReloadingItems = [[NSMutableSet alloc] init]; - _enterDic = [[NSMutableDictionary alloc] init]; + _enterTimesDict = [[NSMutableDictionary alloc] init]; } return self; } @@ -170,7 +165,7 @@ - (void)outerScrollViewDidScroll } } -#pragma mark Core Logic +#pragma mark CoreLogic // Do Binary search here to find index in view model array. - (NSUInteger)binarySearchForIndex:(NSArray *)frameArray baseLine:(CGFloat)baseLine isFromTop:(BOOL)fromTop @@ -249,7 +244,7 @@ - (void)creatScrollViewIndex count = [_dataSource numberOfItemsInScrollView:self]; } - [_itemsModels removeAllObjects]; + [_itemModels removeAllObjects]; for (NSUInteger i = 0 ; i < count ; i++) { TMLazyItemModel *rectmodel = nil; if (_dataSource && @@ -260,10 +255,10 @@ - (void)creatScrollViewIndex rectmodel.muiID = [NSString stringWithFormat:@"%lu", (unsigned long)i]; } } - [_itemsModels tm_safeAddObject:rectmodel]; + [_itemModels tm_safeAddObject:rectmodel]; } - _modelsSortedByTop = [_itemsModels sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { + _modelsSortedByTop = [_itemModels sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { CGRect rect1 = [(TMLazyItemModel *) obj1 absRect]; CGRect rect2 = [(TMLazyItemModel *) obj2 absRect]; if (rect1.origin.y < rect2.origin.y) { @@ -275,7 +270,7 @@ - (void)creatScrollViewIndex } }]; - _modelsSortedByBottom = [_itemsModels sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { + _modelsSortedByBottom = [_itemModels sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { CGRect rect1 = [(TMLazyItemModel *) obj1 absRect]; CGRect rect2 = [(TMLazyItemModel *) obj2 absRect]; CGFloat bottom1 = CGRectGetMaxY(rect1); @@ -292,23 +287,23 @@ - (void)creatScrollViewIndex - (void)findViewsInVisibleRect { - NSMutableSet *itemViewSet = [_muiIDOfVisibleViews mutableCopy]; - [itemViewSet minusSet:_lastVisibleMuiID]; + NSMutableSet *itemViewSet = [_muiIdOfCurentVisibleItems mutableCopy]; + [itemViewSet minusSet:_muiIdOfLastVisibleItems]; for (UIView *view in _visibleItems) { if (view && [itemViewSet containsObject:view.muiID]) { if ([view conformsToProtocol:@protocol(TMLazyItemViewProtocol)] && [view respondsToSelector:@selector(mui_didEnterWithTimes:)]) { NSUInteger times = 0; - if ([_enterDic tm_safeObjectForKey:view.muiID] != nil) { - times = [_enterDic tm_integerForKey:view.muiID] + 1; + if ([_enterTimesDict tm_safeObjectForKey:view.muiID] != nil) { + times = [_enterTimesDict tm_integerForKey:view.muiID] + 1; } NSNumber *showTimes = [NSNumber numberWithUnsignedInteger:times]; - [_enterDic tm_safeSetObject:showTimes forKey:view.muiID]; + [_enterTimesDict tm_safeSetObject:showTimes forKey:view.muiID]; [(UIView *)view mui_didEnterWithTimes:times]; } } } - _lastVisibleMuiID = [_muiIDOfVisibleViews copy]; + _muiIdOfLastVisibleItems = [_muiIdOfCurentVisibleItems copy]; } // A simple method to show view that should be shown in LazyScrollView. @@ -337,10 +332,10 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa { NSSet *itemShouldShowSet = [self showingItemIndexSetFrom:minY to:maxY]; if (_outerScrollView) { - _muiIDOfVisibleViews = [self showingItemIndexSetFrom:minY to:maxY]; + _muiIdOfCurentVisibleItems = [self showingItemIndexSetFrom:minY to:maxY]; } else{ - _muiIDOfVisibleViews = [self showingItemIndexSetFrom:CGRectGetMinY(self.bounds) to:CGRectGetMaxY(self.bounds)]; + _muiIdOfCurentVisibleItems = [self showingItemIndexSetFrom:CGRectGetMinY(self.bounds) to:CGRectGetMaxY(self.bounds)]; } NSMutableSet *recycledItems = [[NSMutableSet alloc] init]; // For recycling. Find which views should not in visible area. @@ -360,10 +355,10 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa [recycledItems addObject:view]; } else if(isReload && view.muiID) { // Need to reload unreusable views. - [_shouldReloadItems addObject:view.muiID]; + [_shouldReloadingItems addObject:view.muiID]; } } else if (isReload && view.muiID) { - [_shouldReloadItems addObject:view.muiID]; + [_shouldReloadingItems addObject:view.muiID]; } } @@ -371,8 +366,8 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa [recycledItems removeAllObjects]; // Creare new view. for (NSString *muiID in itemShouldShowSet) { - BOOL shouldReload = isReload || [_shouldReloadItems containsObject:muiID]; - if (![self isCellVisible:muiID] || [_shouldReloadItems containsObject:muiID]) { + BOOL shouldReload = isReload || [_shouldReloadingItems containsObject:muiID]; + if (![self isItemVisible:muiID] || [_shouldReloadingItems containsObject:muiID]) { if (_dataSource && [_dataSource conformsToProtocol:@protocol(TMLazyScrollViewDataSource)] && [_dataSource respondsToSelector:@selector(scrollView:itemByMuiID:)]) { @@ -380,10 +375,10 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa // If you call dequeue method in your dataSource, the currentVisibleItemMuiID // will be used for searching reusable view. if (shouldReload) { - _currentVisibleItemMuiID = muiID; + _currentReloadingMuiID = muiID; } UIView *viewToShow = [_dataSource scrollView:self itemByMuiID:muiID]; - _currentVisibleItemMuiID = nil; + _currentReloadingMuiID = nil; // Call afterGetView. if ([viewToShow conformsToProtocol:@protocol(TMLazyItemViewProtocol)] && [viewToShow respondsToSelector:@selector(mui_afterGetView)]) { @@ -402,7 +397,7 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa } } } - [_shouldReloadItems removeObject:muiID]; + [_shouldReloadingItems removeObject:muiID]; } } [_inScreenVisibleItems removeAllObjects]; @@ -421,7 +416,7 @@ - (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloa - (void)reloadData { [self creatScrollViewIndex]; - if (_itemsModels.count > 0) { + if (_itemModels.count > 0) { if (_outerScrollView) { CGRect rectInScrollView = [self convertRect:self.frame toView:_outerScrollView]; CGFloat minY = _outerScrollView.contentOffset.y - rectInScrollView.origin.y - LazyBufferHeight; @@ -454,9 +449,9 @@ - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSSt { UIView *result = nil; if (identifier && identifier.length > 0) { - if (_currentVisibleItemMuiID) { + if (_currentReloadingMuiID) { for (UIView *item in _visibleItems) { - if ([item.muiID isEqualToString:_currentVisibleItemMuiID] && [item.reuseIdentifier isEqualToString:identifier]) { + if ([item.muiID isEqualToString:_currentReloadingMuiID] && [item.reuseIdentifier isEqualToString:identifier]) { result = item; break; } @@ -470,7 +465,7 @@ - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSSt } if (result) { if (self.autoClearGestures) { - result.gestureRecognizers = nil; + result.gestureRecognizers = nil; } if ([result respondsToSelector:@selector(mui_prepareForReuse)]) { [(id)result mui_prepareForReuse]; @@ -480,19 +475,7 @@ - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSSt return result; } -//Make sure whether the view is visible accroding to muiID. -- (BOOL)isCellVisible:(NSString *)muiID -{ - BOOL result = NO; - NSSet *visibles = [_visibleItems copy]; - for (UIView *view in visibles) { - if ([view.muiID isEqualToString:muiID]) { - result = YES; - break; - } - } - return result; -} +#pragma mark Clear & Reset - (void)clearItemsAndReusePool { @@ -510,8 +493,8 @@ - (void)removeAllLayouts - (void)resetItemsEnterTimes { - [_enterDic removeAllObjects]; - _lastVisibleMuiID = nil; + [_enterTimesDict removeAllObjects]; + _muiIdOfLastVisibleItems = nil; } - (void)resetViewEnterTimes @@ -519,6 +502,18 @@ - (void)resetViewEnterTimes [self resetItemsEnterTimes]; } +#pragma mark Private + +- (BOOL)isItemVisible:(NSString *)muiID +{ + for (UIView *view in _visibleItems) { + if ([view.muiID isEqualToString:muiID]) { + return YES; + } + } + return NO; +} + @end //**************************************************************** From fd624eb7e82d574ed7a6b8dab11a6ba6478c30d0 Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Tue, 13 Mar 2018 16:47:05 +0800 Subject: [PATCH 18/30] add test project --- .../project.pbxproj | 362 ++++++++++++++++++ .../LazyScrollViewTest/Info.plist | 22 ++ .../LazyScrollViewTest/LazyScrollViewTest.m | 38 ++ Podfile | 7 + 4 files changed, 429 insertions(+) create mode 100644 LazyScrollViewTest/LazyScrollViewTest.xcodeproj/project.pbxproj create mode 100644 LazyScrollViewTest/LazyScrollViewTest/Info.plist create mode 100644 LazyScrollViewTest/LazyScrollViewTest/LazyScrollViewTest.m diff --git a/LazyScrollViewTest/LazyScrollViewTest.xcodeproj/project.pbxproj b/LazyScrollViewTest/LazyScrollViewTest.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ffef7d7 --- /dev/null +++ b/LazyScrollViewTest/LazyScrollViewTest.xcodeproj/project.pbxproj @@ -0,0 +1,362 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 48; + objects = { + +/* Begin PBXBuildFile section */ + 53E176C0B4093753A77EF855 /* libPods-LazyScrollViewTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8ED7BF79578DE1BA510C818B /* libPods-LazyScrollViewTest.a */; }; + 92AED9EE2057C57C00E4D744 /* LazyScrollViewTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92AED9ED2057C57C00E4D744 /* LazyScrollViewTest.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 8ED7BF79578DE1BA510C818B /* libPods-LazyScrollViewTest.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-LazyScrollViewTest.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 92AED9EA2057C57C00E4D744 /* LazyScrollViewTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LazyScrollViewTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 92AED9ED2057C57C00E4D744 /* LazyScrollViewTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LazyScrollViewTest.m; sourceTree = ""; }; + 92AED9EF2057C57C00E4D744 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B5607E29605A5026A43D96A6 /* Pods-LazyScrollViewTest.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LazyScrollViewTest.release.xcconfig"; path = "../Pods/Target Support Files/Pods-LazyScrollViewTest/Pods-LazyScrollViewTest.release.xcconfig"; sourceTree = ""; }; + FCC6B3D1BF91291A7E8BDAF5 /* Pods-LazyScrollViewTest.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LazyScrollViewTest.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-LazyScrollViewTest/Pods-LazyScrollViewTest.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 92AED9E72057C57C00E4D744 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 53E176C0B4093753A77EF855 /* libPods-LazyScrollViewTest.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4B38F8836B3305993BD0FDB5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 8ED7BF79578DE1BA510C818B /* libPods-LazyScrollViewTest.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 92AED9DF2057C56F00E4D744 = { + isa = PBXGroup; + children = ( + 92AED9EC2057C57C00E4D744 /* LazyScrollViewTest */, + 92AED9EB2057C57C00E4D744 /* Products */, + AB4262A336006D10EBD97FE5 /* Pods */, + 4B38F8836B3305993BD0FDB5 /* Frameworks */, + ); + sourceTree = ""; + }; + 92AED9EB2057C57C00E4D744 /* Products */ = { + isa = PBXGroup; + children = ( + 92AED9EA2057C57C00E4D744 /* LazyScrollViewTest.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 92AED9EC2057C57C00E4D744 /* LazyScrollViewTest */ = { + isa = PBXGroup; + children = ( + 92AED9ED2057C57C00E4D744 /* LazyScrollViewTest.m */, + 92AED9EF2057C57C00E4D744 /* Info.plist */, + ); + path = LazyScrollViewTest; + sourceTree = ""; + }; + AB4262A336006D10EBD97FE5 /* Pods */ = { + isa = PBXGroup; + children = ( + FCC6B3D1BF91291A7E8BDAF5 /* Pods-LazyScrollViewTest.debug.xcconfig */, + B5607E29605A5026A43D96A6 /* Pods-LazyScrollViewTest.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 92AED9E92057C57C00E4D744 /* LazyScrollViewTest */ = { + isa = PBXNativeTarget; + buildConfigurationList = 92AED9F02057C57C00E4D744 /* Build configuration list for PBXNativeTarget "LazyScrollViewTest" */; + buildPhases = ( + B8A103919F72E186012F8A7C /* [CP] Check Pods Manifest.lock */, + 92AED9E62057C57C00E4D744 /* Sources */, + 92AED9E72057C57C00E4D744 /* Frameworks */, + 92AED9E82057C57C00E4D744 /* Resources */, + 7357D597561B2C39FF121950 /* [CP] Embed Pods Frameworks */, + 212D0C87D1CE5785C8BFFA53 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = LazyScrollViewTest; + productName = LazyScrollViewTest; + productReference = 92AED9EA2057C57C00E4D744 /* LazyScrollViewTest.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 92AED9E02057C56F00E4D744 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0920; + TargetAttributes = { + 92AED9E92057C57C00E4D744 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 92AED9E32057C56F00E4D744 /* Build configuration list for PBXProject "LazyScrollViewTest" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 92AED9DF2057C56F00E4D744; + productRefGroup = 92AED9EB2057C57C00E4D744 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 92AED9E92057C57C00E4D744 /* LazyScrollViewTest */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 92AED9E82057C57C00E4D744 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 212D0C87D1CE5785C8BFFA53 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-LazyScrollViewTest/Pods-LazyScrollViewTest-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 7357D597561B2C39FF121950 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/../Pods/Target Support Files/Pods-LazyScrollViewTest/Pods-LazyScrollViewTest-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + B8A103919F72E186012F8A7C /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-LazyScrollViewTest-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 92AED9E62057C57C00E4D744 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 92AED9EE2057C57C00E4D744 /* LazyScrollViewTest.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 92AED9E42057C56F00E4D744 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + }; + name = Debug; + }; + 92AED9E52057C56F00E4D744 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + }; + name = Release; + }; + 92AED9F12057C57C00E4D744 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FCC6B3D1BF91291A7E8BDAF5 /* Pods-LazyScrollViewTest.debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = LazyScrollViewTest/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = tmall.LazyScrollViewTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 92AED9F22057C57C00E4D744 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B5607E29605A5026A43D96A6 /* Pods-LazyScrollViewTest.release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = LazyScrollViewTest/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = tmall.LazyScrollViewTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 92AED9E32057C56F00E4D744 /* Build configuration list for PBXProject "LazyScrollViewTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 92AED9E42057C56F00E4D744 /* Debug */, + 92AED9E52057C56F00E4D744 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 92AED9F02057C57C00E4D744 /* Build configuration list for PBXNativeTarget "LazyScrollViewTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 92AED9F12057C57C00E4D744 /* Debug */, + 92AED9F22057C57C00E4D744 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 92AED9E02057C56F00E4D744 /* Project object */; +} diff --git a/LazyScrollViewTest/LazyScrollViewTest/Info.plist b/LazyScrollViewTest/LazyScrollViewTest/Info.plist new file mode 100644 index 0000000..6c40a6c --- /dev/null +++ b/LazyScrollViewTest/LazyScrollViewTest/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/LazyScrollViewTest/LazyScrollViewTest/LazyScrollViewTest.m b/LazyScrollViewTest/LazyScrollViewTest/LazyScrollViewTest.m new file mode 100644 index 0000000..c205800 --- /dev/null +++ b/LazyScrollViewTest/LazyScrollViewTest/LazyScrollViewTest.m @@ -0,0 +1,38 @@ +// +// LazyScrollViewTest.m +// LazyScrollViewTest +// +// Created by HarrisonXi on 2018/3/13. +// + +#import + +@interface LazyScrollViewTest : XCTestCase + +@end + +@implementation LazyScrollViewTest + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testExample { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. +} + +- (void)testPerformanceExample { + // This is an example of a performance test case. + [self measureBlock:^{ + // Put the code you want to measure the time of here. + }]; +} + +@end diff --git a/Podfile b/Podfile index 95e9a44..2153521 100644 --- a/Podfile +++ b/Podfile @@ -9,4 +9,11 @@ target 'LazyScrollViewDemo' do pod 'TMUtils', :path => './' end +target 'LazyScrollViewTest' do + project 'LazyScrollViewTest/LazyScrollViewTest.xcodeproj' + pod 'LazyScroll', :path => './' + pod 'TMUtils', :path => './' + pod 'OCHamcrest' +end + workspace 'LazyScrollView' From b2432116f669a88c2a38f08de67beb917838ecce Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Tue, 13 Mar 2018 19:16:48 +0800 Subject: [PATCH 19/30] tidy the assemble logic --- LazyScrollView/TMLazyReusePool.m | 6 +- LazyScrollView/TMLazyScrollView.h | 4 + LazyScrollView/TMLazyScrollView.m | 382 +++++++++--------- .../LazyScrollViewDemo/AppDelegate.h | 1 - 4 files changed, 195 insertions(+), 198 deletions(-) diff --git a/LazyScrollView/TMLazyReusePool.m b/LazyScrollView/TMLazyReusePool.m index f7e390e..8134fe5 100644 --- a/LazyScrollView/TMLazyReusePool.m +++ b/LazyScrollView/TMLazyReusePool.m @@ -52,7 +52,6 @@ - (UIView *)dequeueItemViewForReuseIdentifier:(NSString *)reuseIdentifier andMui if (reuseSet && reuseSet.count > 0) { if (!muiID) { result = [reuseSet anyObject]; - [reuseSet removeObject:result]; } else { for (UIView *itemView in reuseSet) { if ([itemView.muiID isEqualToString:muiID]) { @@ -60,10 +59,11 @@ - (UIView *)dequeueItemViewForReuseIdentifier:(NSString *)reuseIdentifier andMui break; } } - if (result) { - [reuseSet removeObject:result]; + if (!result) { + result = [reuseSet anyObject]; } } + [reuseSet removeObject:result]; } return result; } diff --git a/LazyScrollView/TMLazyScrollView.h b/LazyScrollView/TMLazyScrollView.h index 938dd3b..a1606f7 100644 --- a/LazyScrollView/TMLazyScrollView.h +++ b/LazyScrollView/TMLazyScrollView.h @@ -96,6 +96,10 @@ - (nullable UIView *)dequeueReusableItemWithIdentifier:(nonnull NSString *)identifier muiID:(nullable NSString *)muiID; +/** + Remember to remove item views from LazyScrollView if the autoAddSubview + property is NO. + */ - (void)clearItemsAndReusePool; - (void)removeAllLayouts __deprecated_msg("use clearItemsAndReusePool"); diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index ae38992..51b6e04 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -31,32 +31,30 @@ @interface TMLazyScrollView () { NSMutableArray *_itemModels; // Store view models below contentOffset of ScrollView - NSMutableSet *_firstSet; + NSMutableSet *_firstSet; // Store view models above contentOffset + height of ScrollView - NSMutableSet *_secondSet; + NSMutableSet *_secondSet; // View Model sorted by Top Edge. NSArray *_modelsSortedByTop; // View Model sorted by Bottom Edge. NSArray *_modelsSortedByBottom; - // Store items which need to assign new value after reloading. - NSMutableSet *_shouldReloadingItems; - - // Store the enter screen times of item. - NSMutableDictionary *_enterTimesDict; + // Store items which need to be reloaded. + NSMutableSet *_needReloadingMuiIDs; // Record current muiID of reloading item. // Will be used for dequeueReusableItem methods. NSString *_currentReloadingMuiID; - // Record muiIDs of visible items. Used for calc enter times. - NSSet *_muiIdOfCurentVisibleItems; - // Store last time visible muiIDs. Used for calc enter times. - NSSet *_muiIdOfLastVisibleItems; + // Store the enter screen times of items. + NSMutableDictionary *_enterTimesDict; + // Store visible models for the last time. Used for calc enter times. + NSSet *_lastInScreenVisibleModels; + // Record contentOffset of scrollview in previous time that // calculate views to show. - CGPoint _lastScrollOffset; + CGPoint _lastContentOffset; } @property (nonatomic, strong) TMLazyOuterScrollViewObserver *outerScrollViewObserver; @@ -69,16 +67,38 @@ @implementation TMLazyScrollView #pragma mark Getter & Setter -- (NSSet *)inScreenVisibleItems +- (NSSet *)inScreenVisibleItems { + if (!_inScreenVisibleItems) { + _inScreenVisibleItems = [NSMutableSet set]; + NSSet *lastInScreenVisibleMuiIDs = [_lastInScreenVisibleModels valueForKey:@"muiID"]; + for (UIView *view in _visibleItems) { + if ([lastInScreenVisibleMuiIDs containsObject:view.muiID]) { + [_inScreenVisibleItems addObject:view]; + } + } + } return [_inScreenVisibleItems copy]; } -- (NSSet *)visibleItems +- (NSSet *)visibleItems { return [_visibleItems copy]; } +- (void)setDataSource:(id)dataSource +{ + if (_dataSource != dataSource) { + if (dataSource == nil || [self isDataSourceValid:dataSource]) { + _dataSource = dataSource; +#ifdef DEBUG + } else { + NSAssert(NO, @"TMLazyScrollView - Invalid dataSource."); +#endif + } + } +} + - (TMLazyOuterScrollViewObserver *)outerScrollViewObserver { if (!_outerScrollViewObserver) { @@ -114,7 +134,6 @@ - (id)initWithFrame:(CGRect)frame _reusePool = [TMLazyReusePool new]; _visibleItems = [[NSMutableSet alloc] init]; - _inScreenVisibleItems = [[NSMutableSet alloc] init]; _itemModels = [[NSMutableArray alloc] init]; @@ -123,9 +142,10 @@ - (id)initWithFrame:(CGRect)frame _modelsSortedByTop = [[NSArray alloc] init]; _modelsSortedByBottom = [[NSArray alloc]init]; - _shouldReloadingItems = [[NSMutableSet alloc] init]; + _needReloadingMuiIDs = [[NSMutableSet alloc] init]; _enterTimesDict = [[NSMutableDictionary alloc] init]; + _lastInScreenVisibleModels = [NSSet set]; } return self; } @@ -142,31 +162,133 @@ - (void)dealloc - (void)setContentOffset:(CGPoint)contentOffset { [super setContentOffset:contentOffset]; - // Calculate which item views should be shown. - // Calculating will cost some time, so here is a buffer for reducing - // times of calculating. - CGFloat currentY = contentOffset.y; - CGFloat buffer = LazyHalfBufferHeight; - if (buffer < ABS(currentY - _lastScrollOffset.y)) { - _lastScrollOffset = self.contentOffset; + if (LazyHalfBufferHeight < ABS(contentOffset.y - _lastContentOffset.y)) { + _lastContentOffset = self.contentOffset; [self assembleSubviews]; - [self findViewsInVisibleRect]; } } - (void)outerScrollViewDidScroll { - CGFloat currentY = _outerScrollView.contentOffset.y; - CGFloat buffer = LazyHalfBufferHeight; - if (buffer < ABS(currentY - _lastScrollOffset.y)) { - _lastScrollOffset = _outerScrollView.contentOffset; + if (LazyHalfBufferHeight < ABS(_outerScrollView.contentOffset.y - _lastContentOffset.y)) { + _lastContentOffset = _outerScrollView.contentOffset; [self assembleSubviews]; - [self findViewsInVisibleRect]; } } #pragma mark CoreLogic +- (void)assembleSubviews +{ + if (_outerScrollView) { + CGRect visibleArea = CGRectIntersection(_outerScrollView.bounds, self.frame); + if (visibleArea.size.height > 0) { + CGFloat offsetY = CGRectGetMinY(self.frame); + CGFloat minY = CGRectGetMinY(visibleArea) - offsetY; + CGFloat maxY = CGRectGetMaxY(visibleArea) - offsetY; + [self assembleSubviews:NO minY:minY maxY:maxY]; + } + } else { + CGFloat minY = CGRectGetMinY(self.bounds); + CGFloat maxY = CGRectGetMaxY(self.bounds); + [self assembleSubviews:NO minY:minY maxY:maxY]; + } +} + +- (void)assembleSubviews:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloat)maxY +{ + // Calculate which item views should be shown. + // Calculating will cost some time, so here is a buffer for reducing + // times of calculating. + NSSet *newVisibleModels = [self showingItemIndexSetFrom:minY - LazyHalfBufferHeight + to:maxY + LazyHalfBufferHeight]; + NSSet *newVisibleMuiIDs = [newVisibleModels valueForKey:@"muiID"]; + + // Find if item views are in visible area. + // Recycle invisible item views. + NSSet *visibleItemsCopy = [_visibleItems copy]; + for (UIView *itemView in visibleItemsCopy) { + BOOL isToShow = [newVisibleMuiIDs containsObject:itemView.muiID]; + if (!isToShow) { + // Call didLeave. + if ([itemView respondsToSelector:@selector(mui_didLeave)]){ + [(UIView *)itemView mui_didLeave]; + } + if (itemView.reuseIdentifier.length > 0) { + itemView.hidden = YES; + [self.reusePool addItemView:itemView forReuseIdentifier:itemView.reuseIdentifier]; + [_visibleItems removeObject:itemView]; + } else if(isReload && itemView.muiID) { + [_needReloadingMuiIDs addObject:itemView.muiID]; + } + } else if (isReload && itemView.muiID) { + [_needReloadingMuiIDs addObject:itemView.muiID]; + } + } + + // Generate or reload visible item views. + for (NSString *muiID in newVisibleMuiIDs) { + // 1. Item view is not visible. We should create or reuse an item view. + // 2. Item view need to be reloaded. + BOOL isVisible = [self isMuiIdVisible:muiID]; + BOOL needReload = [_needReloadingMuiIDs containsObject:muiID]; + if (isVisible == NO || needReload == YES) { + if (_dataSource) { + // If you call dequeue method in your dataSource, the currentReloadingMuiID + // will be used for searching the best-matched reusable view. + if (isVisible) { + _currentReloadingMuiID = muiID; + } + UIView *itemView = [_dataSource scrollView:self itemByMuiID:muiID]; + _currentReloadingMuiID = nil; + // Call afterGetView. + if ([itemView respondsToSelector:@selector(mui_afterGetView)]) { + [(UIView *)itemView mui_afterGetView]; + } + if (itemView) { + itemView.muiID = muiID; + itemView.hidden = NO; + if (![_visibleItems containsObject:itemView]) { + [_visibleItems addObject:itemView]; + } + if (_autoAddSubview) { + if (itemView.superview != self) { + [self addSubview:itemView]; + } + } + } + [_needReloadingMuiIDs removeObject:muiID]; + } + } + } + + // Reset the inScreenVisibleItems. + _inScreenVisibleItems = nil; + + // Calculate the inScreenVisibleModels. + NSMutableSet *newInScreenVisibleModels = [NSMutableSet setWithCapacity:newVisibleModels.count]; + NSMutableSet *enteredMuiIDs = [NSMutableSet set]; + for (TMLazyItemModel *itemModel in newVisibleModels) { + if (itemModel.top < maxY && itemModel.bottom > minY) { + [newInScreenVisibleModels addObject:itemModel]; + if ([_lastInScreenVisibleModels containsObject:itemModel] == NO) { + [enteredMuiIDs addObject:itemModel.muiID]; + } + } + } + for (UIView *itemView in _visibleItems) { + if ([enteredMuiIDs containsObject:itemView.muiID]) { + if ([itemView respondsToSelector:@selector(mui_didEnterWithTimes:)]) { + NSInteger times = [_enterTimesDict tm_integerForKey:itemView.muiID]; + times++; + [_enterTimesDict tm_safeSetObject:@(times) forKey:itemView.muiID]; + [(UIView *)itemView mui_didEnterWithTimes:times]; + } + } + } + _lastInScreenVisibleModels = newInScreenVisibleModels; +} + // Do Binary search here to find index in view model array. - (NSUInteger)binarySearchForIndex:(NSArray *)frameArray baseLine:(CGFloat)baseLine isFromTop:(BOOL)fromTop { @@ -210,14 +332,14 @@ - (NSUInteger)binarySearchForIndex:(NSArray *)frameArray baseLine:(CGFloat)baseL // Get which views should be shown in LazyScrollView. // The kind of values In NSSet is muiID. -- (NSSet *)showingItemIndexSetFrom:(CGFloat)startY to:(CGFloat)endY +- (NSSet *)showingItemIndexSetFrom:(CGFloat)startY to:(CGFloat)endY { NSUInteger endBottomIndex = [self binarySearchForIndex:_modelsSortedByBottom baseLine:startY isFromTop:NO]; [_firstSet removeAllObjects]; for (NSUInteger i = 0; i <= endBottomIndex; i++) { TMLazyItemModel *model = [_modelsSortedByBottom tm_safeObjectAtIndex:i]; if (model != nil) { - [_firstSet addObject:model.muiID]; + [_firstSet addObject:model]; } } @@ -226,7 +348,7 @@ - (NSUInteger)binarySearchForIndex:(NSArray *)frameArray baseLine:(CGFloat)baseL for (NSInteger i = 0; i <= endTopIndex; i++) { TMLazyItemModel *model = [_modelsSortedByTop tm_safeObjectAtIndex:i]; if (model != nil) { - [_secondSet addObject:model.muiID]; + [_secondSet addObject:model]; } } @@ -259,160 +381,32 @@ - (void)creatScrollViewIndex } _modelsSortedByTop = [_itemModels sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { - CGRect rect1 = [(TMLazyItemModel *) obj1 absRect]; - CGRect rect2 = [(TMLazyItemModel *) obj2 absRect]; - if (rect1.origin.y < rect2.origin.y) { - return NSOrderedAscending; - } else if (rect1.origin.y > rect2.origin.y) { - return NSOrderedDescending; - } else { - return NSOrderedSame; - } - }]; + CGRect rect1 = [(TMLazyItemModel *) obj1 absRect]; + CGRect rect2 = [(TMLazyItemModel *) obj2 absRect]; + if (rect1.origin.y < rect2.origin.y) { + return NSOrderedAscending; + } else if (rect1.origin.y > rect2.origin.y) { + return NSOrderedDescending; + } else { + return NSOrderedSame; + } + }]; _modelsSortedByBottom = [_itemModels sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { - CGRect rect1 = [(TMLazyItemModel *) obj1 absRect]; - CGRect rect2 = [(TMLazyItemModel *) obj2 absRect]; - CGFloat bottom1 = CGRectGetMaxY(rect1); - CGFloat bottom2 = CGRectGetMaxY(rect2); - if (bottom1 > bottom2) { - return NSOrderedAscending; - } else if (bottom1 < bottom2) { - return NSOrderedDescending; - } else { - return NSOrderedSame; - } - }]; -} - -- (void)findViewsInVisibleRect -{ - NSMutableSet *itemViewSet = [_muiIdOfCurentVisibleItems mutableCopy]; - [itemViewSet minusSet:_muiIdOfLastVisibleItems]; - for (UIView *view in _visibleItems) { - if (view && [itemViewSet containsObject:view.muiID]) { - if ([view conformsToProtocol:@protocol(TMLazyItemViewProtocol)] && - [view respondsToSelector:@selector(mui_didEnterWithTimes:)]) { - NSUInteger times = 0; - if ([_enterTimesDict tm_safeObjectForKey:view.muiID] != nil) { - times = [_enterTimesDict tm_integerForKey:view.muiID] + 1; - } - NSNumber *showTimes = [NSNumber numberWithUnsignedInteger:times]; - [_enterTimesDict tm_safeSetObject:showTimes forKey:view.muiID]; - [(UIView *)view mui_didEnterWithTimes:times]; - } - } - } - _muiIdOfLastVisibleItems = [_muiIdOfCurentVisibleItems copy]; -} - -// A simple method to show view that should be shown in LazyScrollView. -- (void)assembleSubviews -{ - if (_outerScrollView) { - CGPoint pointInScrollView = [self.superview convertPoint:self.frame.origin toView:_outerScrollView]; - CGFloat minY = _outerScrollView.contentOffset.y - pointInScrollView.y - LazyHalfBufferHeight; - //maxY 计算的逻辑,需要修改,增加的height,需要计算的更加明确 - CGFloat maxY = _outerScrollView.contentOffset.y + _outerScrollView.frame.size.height - pointInScrollView.y + LazyHalfBufferHeight; - if (maxY > 0) { - [self assembleSubviewsForReload:NO minY:minY maxY:maxY]; + CGRect rect1 = [(TMLazyItemModel *) obj1 absRect]; + CGRect rect2 = [(TMLazyItemModel *) obj2 absRect]; + CGFloat bottom1 = CGRectGetMaxY(rect1); + CGFloat bottom2 = CGRectGetMaxY(rect2); + if (bottom1 > bottom2) { + return NSOrderedAscending; + } else if (bottom1 < bottom2) { + return NSOrderedDescending; + } else { + return NSOrderedSame; } - - } - else - { - CGRect visibleBounds = self.bounds; - CGFloat minY = CGRectGetMinY(visibleBounds) - LazyBufferHeight; - CGFloat maxY = CGRectGetMaxY(visibleBounds) + LazyBufferHeight; - [self assembleSubviewsForReload:NO minY:minY maxY:maxY]; - } + }]; } -- (void)assembleSubviewsForReload:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloat)maxY -{ - NSSet *itemShouldShowSet = [self showingItemIndexSetFrom:minY to:maxY]; - if (_outerScrollView) { - _muiIdOfCurentVisibleItems = [self showingItemIndexSetFrom:minY to:maxY]; - } - else{ - _muiIdOfCurentVisibleItems = [self showingItemIndexSetFrom:CGRectGetMinY(self.bounds) to:CGRectGetMaxY(self.bounds)]; - } - NSMutableSet *recycledItems = [[NSMutableSet alloc] init]; - // For recycling. Find which views should not in visible area. - NSSet *visibles = [_visibleItems copy]; - for (UIView *view in visibles) { - // Make sure whether the view should be shown. - BOOL isToShow = [itemShouldShowSet containsObject:view.muiID]; - if (!isToShow) { - if ([view respondsToSelector:@selector(mui_didLeave)]){ - [(UIView *)view mui_didLeave]; - } - // If this view should be recycled and the length of its reuseidentifier is over 0. - if (view.reuseIdentifier.length > 0) { - // Then recycle the view. - [self.reusePool addItemView:view forReuseIdentifier:view.reuseIdentifier]; - view.hidden = YES; - [recycledItems addObject:view]; - } else if(isReload && view.muiID) { - // Need to reload unreusable views. - [_shouldReloadingItems addObject:view.muiID]; - } - } else if (isReload && view.muiID) { - [_shouldReloadingItems addObject:view.muiID]; - } - - } - [_visibleItems minusSet:recycledItems]; - [recycledItems removeAllObjects]; - // Creare new view. - for (NSString *muiID in itemShouldShowSet) { - BOOL shouldReload = isReload || [_shouldReloadingItems containsObject:muiID]; - if (![self isItemVisible:muiID] || [_shouldReloadingItems containsObject:muiID]) { - if (_dataSource && - [_dataSource conformsToProtocol:@protocol(TMLazyScrollViewDataSource)] && - [_dataSource respondsToSelector:@selector(scrollView:itemByMuiID:)]) { - // Create view by dataSource. - // If you call dequeue method in your dataSource, the currentVisibleItemMuiID - // will be used for searching reusable view. - if (shouldReload) { - _currentReloadingMuiID = muiID; - } - UIView *viewToShow = [_dataSource scrollView:self itemByMuiID:muiID]; - _currentReloadingMuiID = nil; - // Call afterGetView. - if ([viewToShow conformsToProtocol:@protocol(TMLazyItemViewProtocol)] && - [viewToShow respondsToSelector:@selector(mui_afterGetView)]) { - [(UIView *)viewToShow mui_afterGetView]; - } - if (viewToShow) { - viewToShow.muiID = muiID; - viewToShow.hidden = NO; - if (![_visibleItems containsObject:viewToShow]) { - [_visibleItems addObject:viewToShow]; - } - if (_autoAddSubview) { - if (viewToShow.superview != self) { - [self addSubview:viewToShow]; - } - } - } - } - [_shouldReloadingItems removeObject:muiID]; - } - } - [_inScreenVisibleItems removeAllObjects]; - for (UIView *view in _visibleItems) { - if ([view isKindOfClass:[UIView class]] && view.superview) { - CGRect absRect = [view.superview convertRect:view.frame toView:self]; - if ((absRect.origin.y + absRect.size.height >= CGRectGetMinY(self.bounds)) && - (absRect.origin.y <= CGRectGetMaxY(self.bounds))) { - [_inScreenVisibleItems addObject:view]; - } - } - } -} - -// Reloads everything and redisplays visible views. - (void)reloadData { [self creatScrollViewIndex]; @@ -422,7 +416,7 @@ - (void)reloadData CGFloat minY = _outerScrollView.contentOffset.y - rectInScrollView.origin.y - LazyBufferHeight; CGFloat maxY = _outerScrollView.contentOffset.y + _outerScrollView.frame.size.height - rectInScrollView.origin.y + _outerScrollView.frame.size.height + LazyBufferHeight; if (maxY > 0) { - [self assembleSubviewsForReload:YES minY:minY maxY:maxY]; + [self assembleSubviews:YES minY:minY maxY:maxY]; } } else{ @@ -430,21 +424,16 @@ - (void)reloadData // 上下增加 20point 的缓冲区 CGFloat minY = CGRectGetMinY(visibleBounds) - LazyBufferHeight; CGFloat maxY = CGRectGetMaxY(visibleBounds) + LazyBufferHeight; - [self assembleSubviewsForReload:YES minY:minY maxY:maxY]; + [self assembleSubviews:YES minY:minY maxY:maxY]; } - [self findViewsInVisibleRect]; } - } -// To acquire an already allocated view that can be reused by reuse identifier. - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier { return [self dequeueReusableItemWithIdentifier:identifier muiID:nil]; } -// To acquire an already allocated view that can be reused by reuse identifier. -// Use muiID for higher priority. - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSString *)muiID { UIView *result = nil; @@ -457,11 +446,8 @@ - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSSt } } } - if (result == nil && muiID && muiID.length > 0) { - result = [self.reusePool dequeueItemViewForReuseIdentifier:identifier andMuiID:muiID]; - } if (result == nil) { - result = [self.reusePool dequeueItemViewForReuseIdentifier:identifier]; + result = [self.reusePool dequeueItemViewForReuseIdentifier:identifier andMuiID:muiID]; } if (result) { if (self.autoClearGestures) { @@ -494,7 +480,7 @@ - (void)removeAllLayouts - (void)resetItemsEnterTimes { [_enterTimesDict removeAllObjects]; - _muiIdOfLastVisibleItems = nil; + _lastInScreenVisibleModels = [NSSet set]; } - (void)resetViewEnterTimes @@ -504,7 +490,7 @@ - (void)resetViewEnterTimes #pragma mark Private -- (BOOL)isItemVisible:(NSString *)muiID +- (BOOL)isMuiIdVisible:(NSString *)muiID { for (UIView *view in _visibleItems) { if ([view.muiID isEqualToString:muiID]) { @@ -514,6 +500,14 @@ - (BOOL)isItemVisible:(NSString *)muiID return NO; } +- (BOOL)isDataSourceValid:(id)dataSource +{ + return dataSource + && [dataSource respondsToSelector:@selector(numberOfItemsInScrollView:)] + && [dataSource respondsToSelector:@selector(scrollView:itemModelAtIndex:)] + && [dataSource respondsToSelector:@selector(scrollView:itemByMuiID:)]; +} + @end //**************************************************************** diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.h b/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.h index e448d2f..d071643 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.h +++ b/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.h @@ -11,6 +11,5 @@ @property (strong, nonatomic) UIWindow *window; - @end From 02bf4e50bbc1a20de6659ea5505186bad2b70b9c Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Wed, 14 Mar 2018 11:48:05 +0800 Subject: [PATCH 20/30] fix some issues --- LazyScrollView/TMLazyScrollView.h | 6 +-- LazyScrollView/TMLazyScrollView.m | 66 +++++++++++++++---------------- 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/LazyScrollView/TMLazyScrollView.h b/LazyScrollView/TMLazyScrollView.h index a1606f7..5db5418 100644 --- a/LazyScrollView/TMLazyScrollView.h +++ b/LazyScrollView/TMLazyScrollView.h @@ -96,11 +96,7 @@ - (nullable UIView *)dequeueReusableItemWithIdentifier:(nonnull NSString *)identifier muiID:(nullable NSString *)muiID; -/** - Remember to remove item views from LazyScrollView if the autoAddSubview - property is NO. - */ -- (void)clearItemsAndReusePool; +- (void)clearVisibleItems; - (void)removeAllLayouts __deprecated_msg("use clearItemsAndReusePool"); /** diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index 51b6e04..d816482 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -11,8 +11,7 @@ #import "UIView+TMLazyScrollView.h" #import "TMLazyReusePool.h" -#define LazyBufferHeight 30.0 -#define LazyHalfBufferHeight 15.0 +#define LazyBufferHeight 20.0 void * const LazyObserverContext = "LazyObserverContext"; @interface TMLazyOuterScrollViewObserver: NSObject @@ -152,7 +151,7 @@ - (id)initWithFrame:(CGRect)frame - (void)dealloc { - _dataSource = nil; + self.dataSource = nil; self.delegate = nil; self.outerScrollView = nil; } @@ -162,7 +161,7 @@ - (void)dealloc - (void)setContentOffset:(CGPoint)contentOffset { [super setContentOffset:contentOffset]; - if (LazyHalfBufferHeight < ABS(contentOffset.y - _lastContentOffset.y)) { + if (LazyBufferHeight < ABS(contentOffset.y - _lastContentOffset.y)) { _lastContentOffset = self.contentOffset; [self assembleSubviews]; } @@ -170,8 +169,8 @@ - (void)setContentOffset:(CGPoint)contentOffset - (void)outerScrollViewDidScroll { - if (LazyHalfBufferHeight < ABS(_outerScrollView.contentOffset.y - _lastContentOffset.y)) { - _lastContentOffset = _outerScrollView.contentOffset; + if (LazyBufferHeight < ABS(self.outerScrollView.contentOffset.y - _lastContentOffset.y)) { + _lastContentOffset = self.outerScrollView.contentOffset; [self assembleSubviews]; } } @@ -180,8 +179,8 @@ - (void)outerScrollViewDidScroll - (void)assembleSubviews { - if (_outerScrollView) { - CGRect visibleArea = CGRectIntersection(_outerScrollView.bounds, self.frame); + if (self.outerScrollView) { + CGRect visibleArea = CGRectIntersection(self.outerScrollView.bounds, self.frame); if (visibleArea.size.height > 0) { CGFloat offsetY = CGRectGetMinY(self.frame); CGFloat minY = CGRectGetMinY(visibleArea) - offsetY; @@ -200,8 +199,8 @@ - (void)assembleSubviews:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloat)maxY // Calculate which item views should be shown. // Calculating will cost some time, so here is a buffer for reducing // times of calculating. - NSSet *newVisibleModels = [self showingItemIndexSetFrom:minY - LazyHalfBufferHeight - to:maxY + LazyHalfBufferHeight]; + NSSet *newVisibleModels = [self showingItemIndexSetFrom:minY - LazyBufferHeight + to:maxY + LazyBufferHeight]; NSSet *newVisibleMuiIDs = [newVisibleModels valueForKey:@"muiID"]; // Find if item views are in visible area. @@ -233,13 +232,13 @@ - (void)assembleSubviews:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloat)maxY BOOL isVisible = [self isMuiIdVisible:muiID]; BOOL needReload = [_needReloadingMuiIDs containsObject:muiID]; if (isVisible == NO || needReload == YES) { - if (_dataSource) { + if (self.dataSource) { // If you call dequeue method in your dataSource, the currentReloadingMuiID // will be used for searching the best-matched reusable view. if (isVisible) { _currentReloadingMuiID = muiID; } - UIView *itemView = [_dataSource scrollView:self itemByMuiID:muiID]; + UIView *itemView = [self.dataSource scrollView:self itemByMuiID:muiID]; _currentReloadingMuiID = nil; // Call afterGetView. if ([itemView respondsToSelector:@selector(mui_afterGetView)]) { @@ -251,7 +250,7 @@ - (void)assembleSubviews:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloat)maxY if (![_visibleItems containsObject:itemView]) { [_visibleItems addObject:itemView]; } - if (_autoAddSubview) { + if (self.autoAddSubview) { if (itemView.superview != self) { [self addSubview:itemView]; } @@ -360,19 +359,15 @@ - (NSUInteger)binarySearchForIndex:(NSArray *)frameArray baseLine:(CGFloat)baseL - (void)creatScrollViewIndex { NSUInteger count = 0; - if (_dataSource && - [_dataSource conformsToProtocol:@protocol(TMLazyScrollViewDataSource)] && - [_dataSource respondsToSelector:@selector(numberOfItemsInScrollView:)]) { - count = [_dataSource numberOfItemsInScrollView:self]; + if (self.dataSource) { + count = [self.dataSource numberOfItemsInScrollView:self]; } [_itemModels removeAllObjects]; for (NSUInteger i = 0 ; i < count ; i++) { TMLazyItemModel *rectmodel = nil; - if (_dataSource && - [_dataSource conformsToProtocol:@protocol(TMLazyScrollViewDataSource)] && - [_dataSource respondsToSelector:@selector(scrollView:itemModelAtIndex:)]) { - rectmodel = [_dataSource scrollView:self itemModelAtIndex:i]; + if (self.dataSource) { + rectmodel = [self.dataSource scrollView:self itemModelAtIndex:i]; if (rectmodel.muiID.length == 0) { rectmodel.muiID = [NSString stringWithFormat:@"%lu", (unsigned long)i]; } @@ -411,10 +406,10 @@ - (void)reloadData { [self creatScrollViewIndex]; if (_itemModels.count > 0) { - if (_outerScrollView) { - CGRect rectInScrollView = [self convertRect:self.frame toView:_outerScrollView]; - CGFloat minY = _outerScrollView.contentOffset.y - rectInScrollView.origin.y - LazyBufferHeight; - CGFloat maxY = _outerScrollView.contentOffset.y + _outerScrollView.frame.size.height - rectInScrollView.origin.y + _outerScrollView.frame.size.height + LazyBufferHeight; + if (self.outerScrollView) { + CGRect rectInScrollView = [self convertRect:self.frame toView:self.outerScrollView]; + CGFloat minY = self.outerScrollView.contentOffset.y - rectInScrollView.origin.y; + CGFloat maxY = self.outerScrollView.contentOffset.y + self.outerScrollView.frame.size.height - rectInScrollView.origin.y + self.outerScrollView.frame.size.height; if (maxY > 0) { [self assembleSubviews:YES minY:minY maxY:maxY]; } @@ -422,8 +417,8 @@ - (void)reloadData else{ CGRect visibleBounds = self.bounds; // 上下增加 20point 的缓冲区 - CGFloat minY = CGRectGetMinY(visibleBounds) - LazyBufferHeight; - CGFloat maxY = CGRectGetMaxY(visibleBounds) + LazyBufferHeight; + CGFloat minY = CGRectGetMinY(visibleBounds); + CGFloat maxY = CGRectGetMaxY(visibleBounds); [self assembleSubviews:YES minY:minY maxY:maxY]; } } @@ -440,7 +435,8 @@ - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSSt if (identifier && identifier.length > 0) { if (_currentReloadingMuiID) { for (UIView *item in _visibleItems) { - if ([item.muiID isEqualToString:_currentReloadingMuiID] && [item.reuseIdentifier isEqualToString:identifier]) { + if ([item.muiID isEqualToString:_currentReloadingMuiID] + && [item.reuseIdentifier isEqualToString:identifier]) { result = item; break; } @@ -463,18 +459,18 @@ - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSSt #pragma mark Clear & Reset -- (void)clearItemsAndReusePool +- (void)clearVisibleItems { - for (UIView *view in _visibleItems) { - view.hidden = YES; + for (UIView *itemView in _visibleItems) { + itemView.hidden = YES; + [self.reusePool addItemView:itemView forReuseIdentifier:itemView.reuseIdentifier]; } [_visibleItems removeAllObjects]; - [self.reusePool clear]; } - (void)removeAllLayouts { - [self clearItemsAndReusePool]; + [self clearVisibleItems]; } - (void)resetItemsEnterTimes @@ -492,8 +488,8 @@ - (void)resetViewEnterTimes - (BOOL)isMuiIdVisible:(NSString *)muiID { - for (UIView *view in _visibleItems) { - if ([view.muiID isEqualToString:muiID]) { + for (UIView *itemView in _visibleItems) { + if ([itemView.muiID isEqualToString:muiID]) { return YES; } } From 28d81d2b3318ac6822e3964563fec6396d62fe4e Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Wed, 14 Mar 2018 16:41:55 +0800 Subject: [PATCH 21/30] add TMLazyModelBucket and unit test for it --- LazyScrollView/LazyScroll.h | 1 + LazyScrollView/TMLazyModelBucket.h | 28 ++++ LazyScrollView/TMLazyModelBucket.m | 87 +++++++++++++ LazyScrollView/TMLazyScrollView.m | 38 ++---- .../project.pbxproj | 8 +- .../LazyScrollViewTest/LazyScrollViewTest.m | 38 ------ .../LazyScrollViewTest/ModelBucketTest.m | 122 ++++++++++++++++++ update_header.py | 1 + 8 files changed, 257 insertions(+), 66 deletions(-) create mode 100644 LazyScrollView/TMLazyModelBucket.h create mode 100644 LazyScrollView/TMLazyModelBucket.m delete mode 100644 LazyScrollViewTest/LazyScrollViewTest/LazyScrollViewTest.m create mode 100644 LazyScrollViewTest/LazyScrollViewTest/ModelBucketTest.m diff --git a/LazyScrollView/LazyScroll.h b/LazyScrollView/LazyScroll.h index fc33e3d..01879ad 100644 --- a/LazyScrollView/LazyScroll.h +++ b/LazyScrollView/LazyScroll.h @@ -11,6 +11,7 @@ #import "TMLazyItemViewProtocol.h" #import "TMLazyItemModel.h" #import "TMLazyReusePool.h" +#import "TMLazyModelBucket.h" #import "UIView+TMLazyScrollView.h" #import "TMLazyScrollView.h" diff --git a/LazyScrollView/TMLazyModelBucket.h b/LazyScrollView/TMLazyModelBucket.h new file mode 100644 index 0000000..5567b39 --- /dev/null +++ b/LazyScrollView/TMLazyModelBucket.h @@ -0,0 +1,28 @@ +// +// TMLazyModelBucket.h +// LazyScrollView +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import +#import "TMLazyItemModel.h" + +/** + Every bucket store item models in an area. + 1st bucket store item models which Y value is from 0 to bucketHeight. + 2nd bucket store item models which Y value is from bucketHeight to bucketHeight * 2. + */ +@interface TMLazyModelBucket : NSObject + +@property (nonatomic, assign, readonly) CGFloat bucketHeight; + +- (instancetype)initWithBucketHeight:(CGFloat)bucketHeight; + +- (void)addModel:(TMLazyItemModel *)itemModel; +- (void)removeModel:(TMLazyItemModel *)itemModel; +- (void)reloadModel:(TMLazyItemModel *)itemModel; +- (void)clear; +- (NSSet *)showingModelsFrom:(CGFloat)startY to:(CGFloat)endY; + +@end diff --git a/LazyScrollView/TMLazyModelBucket.m b/LazyScrollView/TMLazyModelBucket.m new file mode 100644 index 0000000..701601e --- /dev/null +++ b/LazyScrollView/TMLazyModelBucket.m @@ -0,0 +1,87 @@ +// +// TMLazyModelBucket.m +// LazyScrollView +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import "TMLazyModelBucket.h" + +@interface TMLazyModelBucket () { + NSMutableArray *_buckets; +} + +@end + +@implementation TMLazyModelBucket + +@synthesize bucketHeight = _bucketHeight; + +- (instancetype)initWithBucketHeight:(CGFloat)bucketHeight +{ + if (self = [super init]) { + _bucketHeight = bucketHeight; + _buckets = [NSMutableArray array]; + } + return self; +} + +- (void)addModel:(TMLazyItemModel *)itemModel +{ + if (itemModel && itemModel.bottom > itemModel.top) { + NSInteger startIndex = (NSInteger)floor(itemModel.top / _bucketHeight); + NSInteger endIndex = (NSInteger)floor((itemModel.bottom - 0.01) / _bucketHeight); + for (NSInteger index = 0; index <= endIndex; index++) { + if (_buckets.count <= index) { + [_buckets addObject:[NSMutableSet set]]; + } + if (index >= startIndex && index <= endIndex) { + NSMutableSet *bucket = [_buckets objectAtIndex:index]; + [bucket addObject:itemModel]; + } + } + } +} + +- (void)removeModel:(TMLazyItemModel *)itemModel +{ + if (itemModel) { + for (NSMutableSet *bucket in _buckets) { + [bucket removeObject:itemModel]; + } + } +} + +- (void)reloadModel:(TMLazyItemModel *)itemModel +{ + [self removeModel:itemModel]; + [self addModel:itemModel]; +} + +- (void)clear +{ + [_buckets removeAllObjects]; +} + +- (NSSet *)showingModelsFrom:(CGFloat)startY to:(CGFloat)endY +{ + NSMutableSet *result = [NSMutableSet set]; + NSInteger startIndex = (NSInteger)floor(startY / _bucketHeight); + NSInteger endIndex = (NSInteger)floor((endY - 0.01) / _bucketHeight); + for (NSInteger index = 0; index <= endIndex; index++) { + if (_buckets.count > index && index >= startIndex && index <= endIndex) { + NSSet *bucket = [_buckets objectAtIndex:index]; + [result unionSet:bucket]; + } + } + NSMutableSet *needToBeRemoved = [NSMutableSet set]; + for (TMLazyItemModel *itemModel in result) { + if (itemModel.top >= endY || itemModel.bottom <= startY) { + [needToBeRemoved addObject:itemModel]; + } + } + [result minusSet:needToBeRemoved]; + return [result copy]; +} + +@end diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index d816482..43d913c 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -163,7 +163,7 @@ - (void)setContentOffset:(CGPoint)contentOffset [super setContentOffset:contentOffset]; if (LazyBufferHeight < ABS(contentOffset.y - _lastContentOffset.y)) { _lastContentOffset = self.contentOffset; - [self assembleSubviews]; + [self assembleSubviews:NO]; } } @@ -171,13 +171,13 @@ - (void)outerScrollViewDidScroll { if (LazyBufferHeight < ABS(self.outerScrollView.contentOffset.y - _lastContentOffset.y)) { _lastContentOffset = self.outerScrollView.contentOffset; - [self assembleSubviews]; + [self assembleSubviews:NO]; } } #pragma mark CoreLogic -- (void)assembleSubviews +- (void)assembleSubviews:(BOOL)isReload { if (self.outerScrollView) { CGRect visibleArea = CGRectIntersection(self.outerScrollView.bounds, self.frame); @@ -185,12 +185,14 @@ - (void)assembleSubviews CGFloat offsetY = CGRectGetMinY(self.frame); CGFloat minY = CGRectGetMinY(visibleArea) - offsetY; CGFloat maxY = CGRectGetMaxY(visibleArea) - offsetY; - [self assembleSubviews:NO minY:minY maxY:maxY]; + [self assembleSubviews:isReload minY:minY maxY:maxY]; + } else { + [self assembleSubviews:isReload minY:0 maxY:-LazyBufferHeight * 2]; } } else { CGFloat minY = CGRectGetMinY(self.bounds); CGFloat maxY = CGRectGetMaxY(self.bounds); - [self assembleSubviews:NO minY:minY maxY:maxY]; + [self assembleSubviews:isReload minY:minY maxY:maxY]; } } @@ -402,26 +404,12 @@ - (void)creatScrollViewIndex }]; } +#pragma mark Reload + - (void)reloadData { [self creatScrollViewIndex]; - if (_itemModels.count > 0) { - if (self.outerScrollView) { - CGRect rectInScrollView = [self convertRect:self.frame toView:self.outerScrollView]; - CGFloat minY = self.outerScrollView.contentOffset.y - rectInScrollView.origin.y; - CGFloat maxY = self.outerScrollView.contentOffset.y + self.outerScrollView.frame.size.height - rectInScrollView.origin.y + self.outerScrollView.frame.size.height; - if (maxY > 0) { - [self assembleSubviews:YES minY:minY maxY:maxY]; - } - } - else{ - CGRect visibleBounds = self.bounds; - // 上下增加 20point 的缓冲区 - CGFloat minY = CGRectGetMinY(visibleBounds); - CGFloat maxY = CGRectGetMaxY(visibleBounds); - [self assembleSubviews:YES minY:minY maxY:maxY]; - } - } + [self assembleSubviews:YES]; } - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier @@ -462,8 +450,10 @@ - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSSt - (void)clearVisibleItems { for (UIView *itemView in _visibleItems) { - itemView.hidden = YES; - [self.reusePool addItemView:itemView forReuseIdentifier:itemView.reuseIdentifier]; + if (itemView.reuseIdentifier.length > 0) { + itemView.hidden = YES; + [self.reusePool addItemView:itemView forReuseIdentifier:itemView.reuseIdentifier]; + } } [_visibleItems removeAllObjects]; } diff --git a/LazyScrollViewTest/LazyScrollViewTest.xcodeproj/project.pbxproj b/LazyScrollViewTest/LazyScrollViewTest.xcodeproj/project.pbxproj index ffef7d7..9848cc4 100644 --- a/LazyScrollViewTest/LazyScrollViewTest.xcodeproj/project.pbxproj +++ b/LazyScrollViewTest/LazyScrollViewTest.xcodeproj/project.pbxproj @@ -8,13 +8,13 @@ /* Begin PBXBuildFile section */ 53E176C0B4093753A77EF855 /* libPods-LazyScrollViewTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8ED7BF79578DE1BA510C818B /* libPods-LazyScrollViewTest.a */; }; - 92AED9EE2057C57C00E4D744 /* LazyScrollViewTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92AED9ED2057C57C00E4D744 /* LazyScrollViewTest.m */; }; + 92AED9EE2057C57C00E4D744 /* ModelBucketTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92AED9ED2057C57C00E4D744 /* ModelBucketTest.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 8ED7BF79578DE1BA510C818B /* libPods-LazyScrollViewTest.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-LazyScrollViewTest.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 92AED9EA2057C57C00E4D744 /* LazyScrollViewTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LazyScrollViewTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 92AED9ED2057C57C00E4D744 /* LazyScrollViewTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LazyScrollViewTest.m; sourceTree = ""; }; + 92AED9ED2057C57C00E4D744 /* ModelBucketTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ModelBucketTest.m; sourceTree = ""; }; 92AED9EF2057C57C00E4D744 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B5607E29605A5026A43D96A6 /* Pods-LazyScrollViewTest.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LazyScrollViewTest.release.xcconfig"; path = "../Pods/Target Support Files/Pods-LazyScrollViewTest/Pods-LazyScrollViewTest.release.xcconfig"; sourceTree = ""; }; FCC6B3D1BF91291A7E8BDAF5 /* Pods-LazyScrollViewTest.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LazyScrollViewTest.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-LazyScrollViewTest/Pods-LazyScrollViewTest.debug.xcconfig"; sourceTree = ""; }; @@ -61,7 +61,7 @@ 92AED9EC2057C57C00E4D744 /* LazyScrollViewTest */ = { isa = PBXGroup; children = ( - 92AED9ED2057C57C00E4D744 /* LazyScrollViewTest.m */, + 92AED9ED2057C57C00E4D744 /* ModelBucketTest.m */, 92AED9EF2057C57C00E4D744 /* Info.plist */, ); path = LazyScrollViewTest; @@ -196,7 +196,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 92AED9EE2057C57C00E4D744 /* LazyScrollViewTest.m in Sources */, + 92AED9EE2057C57C00E4D744 /* ModelBucketTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/LazyScrollViewTest/LazyScrollViewTest/LazyScrollViewTest.m b/LazyScrollViewTest/LazyScrollViewTest/LazyScrollViewTest.m deleted file mode 100644 index c205800..0000000 --- a/LazyScrollViewTest/LazyScrollViewTest/LazyScrollViewTest.m +++ /dev/null @@ -1,38 +0,0 @@ -// -// LazyScrollViewTest.m -// LazyScrollViewTest -// -// Created by HarrisonXi on 2018/3/13. -// - -#import - -@interface LazyScrollViewTest : XCTestCase - -@end - -@implementation LazyScrollViewTest - -- (void)setUp { - [super setUp]; - // Put setup code here. This method is called before the invocation of each test method in the class. -} - -- (void)tearDown { - // Put teardown code here. This method is called after the invocation of each test method in the class. - [super tearDown]; -} - -- (void)testExample { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. -} - -- (void)testPerformanceExample { - // This is an example of a performance test case. - [self measureBlock:^{ - // Put the code you want to measure the time of here. - }]; -} - -@end diff --git a/LazyScrollViewTest/LazyScrollViewTest/ModelBucketTest.m b/LazyScrollViewTest/LazyScrollViewTest/ModelBucketTest.m new file mode 100644 index 0000000..83ae499 --- /dev/null +++ b/LazyScrollViewTest/LazyScrollViewTest/ModelBucketTest.m @@ -0,0 +1,122 @@ +// +// ModelBucketTest.m +// ModelBucketTest +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import +#import +#import + +@interface ModelBucketTest : XCTestCase + +@end + +@implementation ModelBucketTest + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testModelBucket { + TMLazyModelBucket *bucket= [[TMLazyModelBucket alloc] initWithBucketHeight:20]; + TMLazyItemModel *firstModel = [ModelBucketTest createModelHelperWithY:0 height:10]; + [bucket addModel:firstModel]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:10 height:10]]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:20 height:10]]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:30 height:10]]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:40 height:10]]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:50 height:10]]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:0 height:20]]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:10 height:20]]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:20 height:20]]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:30 height:20]]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:40 height:20]]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:0 height:30]]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:10 height:30]]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:20 height:30]]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:30 height:30]]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:0 height:40]]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:10 height:40]]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:20 height:40]]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:0 height:50]]; + [bucket addModel:[ModelBucketTest createModelHelperWithY:10 height:50]]; + TMLazyItemModel *lastModel = [ModelBucketTest createModelHelperWithY:0 height:60]; + [bucket addModel:lastModel]; + + assertThat([bucket showingModelsFrom:0 to:60], hasCountOf(21)); + assertThat([bucket showingModelsFrom:0 to:50], hasCountOf(20)); + assertThat([bucket showingModelsFrom:0 to:40], hasCountOf(18)); + assertThat([bucket showingModelsFrom:0 to:30], hasCountOf(15)); + NSSet *set = [bucket showingModelsFrom:0 to:20]; + assertThat(set, hasCountOf(11)); + set = [set valueForKey:@"muiID"]; + assertThat(set, containsInAnyOrder(@"10", @"20", @"30", @"40", @"50", @"60", @"1010", @"1020", @"1030", @"1040", @"1050", nil)); + set = [bucket showingModelsFrom:0 to:10]; + assertThat(set, hasCountOf(6)); + set = [set valueForKey:@"muiID"]; + assertThat(set, containsInAnyOrder(@"10", @"20", @"30", @"40", @"50", @"60", nil)); + + [bucket removeModel:lastModel]; + + assertThat([bucket showingModelsFrom:0 to:60], hasCountOf(20)); + assertThat([bucket showingModelsFrom:0 to:50], hasCountOf(19)); + assertThat([bucket showingModelsFrom:0 to:40], hasCountOf(17)); + assertThat([bucket showingModelsFrom:0 to:30], hasCountOf(14)); + set = [bucket showingModelsFrom:0 to:20]; + assertThat(set, hasCountOf(10)); + set = [set valueForKey:@"muiID"]; + assertThat(set, containsInAnyOrder(@"10", @"20", @"30", @"40", @"50", @"1010", @"1020", @"1030", @"1040", @"1050", nil)); + set = [bucket showingModelsFrom:0 to:10]; + assertThat(set, hasCountOf(5)); + set = [set valueForKey:@"muiID"]; + assertThat(set, containsInAnyOrder(@"10", @"20", @"30", @"40", @"50", nil)); + + firstModel.absRect = CGRectMake(0, 30, 10, 30); + [bucket reloadModel:firstModel]; + + assertThat([bucket showingModelsFrom:0 to:60], hasCountOf(20)); + assertThat([bucket showingModelsFrom:0 to:50], hasCountOf(19)); + assertThat([bucket showingModelsFrom:0 to:40], hasCountOf(17)); + assertThat([bucket showingModelsFrom:0 to:30], hasCountOf(13)); + set = [bucket showingModelsFrom:0 to:20]; + assertThat(set, hasCountOf(9)); + set = [set valueForKey:@"muiID"]; + assertThat(set, containsInAnyOrder(@"20", @"30", @"40", @"50", @"1010", @"1020", @"1030", @"1040", @"1050", nil)); + set = [bucket showingModelsFrom:0 to:10]; + assertThat(set, hasCountOf(4)); + set = [set valueForKey:@"muiID"]; + assertThat(set, containsInAnyOrder(@"20", @"30", @"40", @"50", nil)); + set = [bucket showingModelsFrom:30 to:60]; + assertThat(set, hasCountOf(15)); + set = [set valueForKey:@"muiID"]; + assertThat(set, containsInAnyOrder(@"10", @"40", @"50", @"1030", @"1040", @"1050", @"2020", @"2030", @"2040", @"3010", @"3020", @"3030", @"4010", @"4020", @"5010", nil)); + set = [bucket showingModelsFrom:30 to:50]; + assertThat(set, hasCountOf(14)); + set = [set valueForKey:@"muiID"]; + assertThat(set, containsInAnyOrder(@"10", @"40", @"50", @"1030", @"1040", @"1050", @"2020", @"2030", @"2040", @"3010", @"3020", @"3030", @"4010", @"4020", nil)); + + [bucket clear]; + assertThat([bucket showingModelsFrom:0 to:50], hasCountOf(0)); + assertThat([bucket showingModelsFrom:0 to:40], hasCountOf(0)); + assertThat([bucket showingModelsFrom:0 to:30], hasCountOf(0)); + assertThat([bucket showingModelsFrom:0 to:20], hasCountOf(0)); + assertThat([bucket showingModelsFrom:0 to:10], hasCountOf(0)); +} + ++ (TMLazyItemModel *)createModelHelperWithY:(CGFloat)y height:(CGFloat)height +{ + TMLazyItemModel *model = [TMLazyItemModel new]; + model.absRect = CGRectMake(0, y, 10, height); + model.muiID = [NSString stringWithFormat:@"%.0f", y * 100 + height]; + return model; +} + +@end diff --git a/update_header.py b/update_header.py index 49fdef0..fd27ab0 100644 --- a/update_header.py +++ b/update_header.py @@ -28,5 +28,6 @@ def updateHeader(DIR, PROJ): updateHeader(os.path.join(sys.path[0], 'LazyScrollView'), 'LazyScrollView') updateHeader(os.path.join(sys.path[0], 'LazyScrollViewDemo'), 'LazyScrollViewDemo') +updateHeader(os.path.join(sys.path[0], 'LazyScrollViewTest'), 'LazyScrollViewTest') updateHeader(os.path.join(sys.path[0], 'TMUtils'), 'TMUtils') print('Header updating is done.') From aae6f0cede94444c80bec168b8f84dbe15029401 Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Wed, 14 Mar 2018 17:18:12 +0800 Subject: [PATCH 22/30] use model bucket to replace origin logic --- LazyScrollView/TMLazyModelBucket.h | 2 + LazyScrollView/TMLazyModelBucket.m | 18 ++++ LazyScrollView/TMLazyScrollView.m | 159 +++++------------------------ 3 files changed, 44 insertions(+), 135 deletions(-) diff --git a/LazyScrollView/TMLazyModelBucket.h b/LazyScrollView/TMLazyModelBucket.h index 5567b39..9195b8c 100644 --- a/LazyScrollView/TMLazyModelBucket.h +++ b/LazyScrollView/TMLazyModelBucket.h @@ -21,7 +21,9 @@ - (void)addModel:(TMLazyItemModel *)itemModel; - (void)removeModel:(TMLazyItemModel *)itemModel; +- (void)removeModels:(NSArray *)itemModels; - (void)reloadModel:(TMLazyItemModel *)itemModel; +- (void)reloadModels:(NSArray *)itemModels; - (void)clear; - (NSSet *)showingModelsFrom:(CGFloat)startY to:(CGFloat)endY; diff --git a/LazyScrollView/TMLazyModelBucket.m b/LazyScrollView/TMLazyModelBucket.m index 701601e..2e981aa 100644 --- a/LazyScrollView/TMLazyModelBucket.m +++ b/LazyScrollView/TMLazyModelBucket.m @@ -52,12 +52,30 @@ - (void)removeModel:(TMLazyItemModel *)itemModel } } +- (void)removeModels:(NSArray *)itemModels +{ + if (itemModels) { + NSSet *itemModelSet = [NSSet setWithArray:itemModels]; + for (NSMutableSet *bucket in _buckets) { + [bucket minusSet:itemModelSet]; + } + } +} + - (void)reloadModel:(TMLazyItemModel *)itemModel { [self removeModel:itemModel]; [self addModel:itemModel]; } +- (void)reloadModels:(NSArray *)itemModels +{ + [self removeModels:itemModels]; + for (TMLazyItemModel *itemModel in itemModels) { + [self addModel:itemModel]; + } +} + - (void)clear { [_buckets removeAllObjects]; diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index 43d913c..1a22a19 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -10,8 +10,10 @@ #import "TMLazyItemViewProtocol.h" #import "UIView+TMLazyScrollView.h" #import "TMLazyReusePool.h" +#import "TMLazyModelBucket.h" -#define LazyBufferHeight 20.0 +#define LazyBufferHeight 20 +#define LazyBucketHeight 400 void * const LazyObserverContext = "LazyObserverContext"; @interface TMLazyOuterScrollViewObserver: NSObject @@ -27,16 +29,7 @@ @interface TMLazyScrollView () { NSMutableSet *_inScreenVisibleItems; // Store item models. - NSMutableArray *_itemModels; - - // Store view models below contentOffset of ScrollView - NSMutableSet *_firstSet; - // Store view models above contentOffset + height of ScrollView - NSMutableSet *_secondSet; - // View Model sorted by Top Edge. - NSArray *_modelsSortedByTop; - // View Model sorted by Bottom Edge. - NSArray *_modelsSortedByBottom; + TMLazyModelBucket *_modelBucket; // Store items which need to be reloaded. NSMutableSet *_needReloadingMuiIDs; @@ -134,12 +127,7 @@ - (id)initWithFrame:(CGRect)frame _visibleItems = [[NSMutableSet alloc] init]; - _itemModels = [[NSMutableArray alloc] init]; - - _firstSet = [[NSMutableSet alloc] initWithCapacity:30]; - _secondSet = [[NSMutableSet alloc] initWithCapacity:30]; - _modelsSortedByTop = [[NSArray alloc] init]; - _modelsSortedByBottom = [[NSArray alloc]init]; + _modelBucket = [[TMLazyModelBucket alloc] initWithBucketHeight:LazyBucketHeight]; _needReloadingMuiIDs = [[NSMutableSet alloc] init]; @@ -201,8 +189,8 @@ - (void)assembleSubviews:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloat)maxY // Calculate which item views should be shown. // Calculating will cost some time, so here is a buffer for reducing // times of calculating. - NSSet *newVisibleModels = [self showingItemIndexSetFrom:minY - LazyBufferHeight - to:maxY + LazyBufferHeight]; + NSSet *newVisibleModels = [_modelBucket showingModelsFrom:minY - LazyBufferHeight + to:maxY + LazyBufferHeight]; NSSet *newVisibleMuiIDs = [newVisibleModels valueForKey:@"muiID"]; // Find if item views are in visible area. @@ -228,13 +216,13 @@ - (void)assembleSubviews:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloat)maxY } // Generate or reload visible item views. - for (NSString *muiID in newVisibleMuiIDs) { - // 1. Item view is not visible. We should create or reuse an item view. - // 2. Item view need to be reloaded. - BOOL isVisible = [self isMuiIdVisible:muiID]; - BOOL needReload = [_needReloadingMuiIDs containsObject:muiID]; - if (isVisible == NO || needReload == YES) { - if (self.dataSource) { + if (self.dataSource) { + for (NSString *muiID in newVisibleMuiIDs) { + // 1. Item view is not visible. We should create or reuse an item view. + // 2. Item view need to be reloaded. + BOOL isVisible = [self isMuiIdVisible:muiID]; + BOOL needReload = [_needReloadingMuiIDs containsObject:muiID]; + if (isVisible == NO || needReload == YES) { // If you call dequeue method in your dataSource, the currentReloadingMuiID // will be used for searching the best-matched reusable view. if (isVisible) { @@ -290,125 +278,26 @@ - (void)assembleSubviews:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloat)maxY _lastInScreenVisibleModels = newInScreenVisibleModels; } -// Do Binary search here to find index in view model array. -- (NSUInteger)binarySearchForIndex:(NSArray *)frameArray baseLine:(CGFloat)baseLine isFromTop:(BOOL)fromTop -{ - NSInteger min = 0; - NSInteger max = frameArray.count - 1; - NSInteger mid = ceilf((min + max) * 0.5f); - while (mid > min && mid < max) { - CGRect rect = [(TMLazyItemModel *)[frameArray tm_safeObjectAtIndex:mid] absRect]; - // For top - if(fromTop) { - CGFloat itemTop = CGRectGetMinY(rect); - if (itemTop <= baseLine) { - CGRect nextItemRect = [(TMLazyItemModel *)[frameArray tm_safeObjectAtIndex:mid + 1] absRect]; - CGFloat nextTop = CGRectGetMinY(nextItemRect); - if (nextTop > baseLine) { - break; - } - min = mid; - } else { - max = mid; - } - } - // For bottom - else { - CGFloat itemBottom = CGRectGetMaxY(rect); - if (itemBottom >= baseLine) { - CGRect nextItemRect = [(TMLazyItemModel *)[frameArray tm_safeObjectAtIndex:mid + 1] absRect]; - CGFloat nextBottom = CGRectGetMaxY(nextItemRect); - if (nextBottom < baseLine) { - break; - } - min = mid; - } else { - max = mid; - } - } - mid = ceilf((CGFloat)(min + max) / 2.f); - } - return mid; -} - -// Get which views should be shown in LazyScrollView. -// The kind of values In NSSet is muiID. -- (NSSet *)showingItemIndexSetFrom:(CGFloat)startY to:(CGFloat)endY -{ - NSUInteger endBottomIndex = [self binarySearchForIndex:_modelsSortedByBottom baseLine:startY isFromTop:NO]; - [_firstSet removeAllObjects]; - for (NSUInteger i = 0; i <= endBottomIndex; i++) { - TMLazyItemModel *model = [_modelsSortedByBottom tm_safeObjectAtIndex:i]; - if (model != nil) { - [_firstSet addObject:model]; - } - } - - NSUInteger endTopIndex = [self binarySearchForIndex:_modelsSortedByTop baseLine:endY isFromTop:YES]; - [_secondSet removeAllObjects]; - for (NSInteger i = 0; i <= endTopIndex; i++) { - TMLazyItemModel *model = [_modelsSortedByTop tm_safeObjectAtIndex:i]; - if (model != nil) { - [_secondSet addObject:model]; - } - } - - [_firstSet intersectSet:_secondSet]; - return [_firstSet copy]; -} +#pragma mark Reload -// Get view models from delegate. Create to indexes for sorting. -- (void)creatScrollViewIndex +- (void)storeItemModels { - NSUInteger count = 0; + [_modelBucket clear]; if (self.dataSource) { - count = [self.dataSource numberOfItemsInScrollView:self]; - } - - [_itemModels removeAllObjects]; - for (NSUInteger i = 0 ; i < count ; i++) { - TMLazyItemModel *rectmodel = nil; - if (self.dataSource) { - rectmodel = [self.dataSource scrollView:self itemModelAtIndex:i]; - if (rectmodel.muiID.length == 0) { - rectmodel.muiID = [NSString stringWithFormat:@"%lu", (unsigned long)i]; + NSInteger count = [self.dataSource numberOfItemsInScrollView:self]; + for (NSInteger index = 0; index < count; index++) { + TMLazyItemModel *itemModel = [self.dataSource scrollView:self itemModelAtIndex:index]; + if (itemModel.muiID.length == 0) { + itemModel.muiID = [NSString stringWithFormat:@"%zd", index]; } + [_modelBucket addModel:itemModel]; } - [_itemModels tm_safeAddObject:rectmodel]; } - - _modelsSortedByTop = [_itemModels sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { - CGRect rect1 = [(TMLazyItemModel *) obj1 absRect]; - CGRect rect2 = [(TMLazyItemModel *) obj2 absRect]; - if (rect1.origin.y < rect2.origin.y) { - return NSOrderedAscending; - } else if (rect1.origin.y > rect2.origin.y) { - return NSOrderedDescending; - } else { - return NSOrderedSame; - } - }]; - - _modelsSortedByBottom = [_itemModels sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { - CGRect rect1 = [(TMLazyItemModel *) obj1 absRect]; - CGRect rect2 = [(TMLazyItemModel *) obj2 absRect]; - CGFloat bottom1 = CGRectGetMaxY(rect1); - CGFloat bottom2 = CGRectGetMaxY(rect2); - if (bottom1 > bottom2) { - return NSOrderedAscending; - } else if (bottom1 < bottom2) { - return NSOrderedDescending; - } else { - return NSOrderedSame; - } - }]; } -#pragma mark Reload - - (void)reloadData { - [self creatScrollViewIndex]; + [self storeItemModels]; [self assembleSubviews:YES]; } From 665d3598d937a429598d4628b8ed13cd4c14d0eb Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Wed, 14 Mar 2018 19:13:40 +0800 Subject: [PATCH 23/30] add loadMore feature and demo for it --- LazyScrollView/TMLazyScrollView.h | 1 + LazyScrollView/TMLazyScrollView.m | 16 ++- .../project.pbxproj | 6 + .../LazyScrollViewDemo/MainViewController.m | 5 +- .../LazyScrollViewDemo/MoreViewController.h | 12 ++ .../LazyScrollViewDemo/MoreViewController.m | 113 ++++++++++++++++++ .../LazyScrollViewDemo/OuterViewController.m | 7 ++ .../LazyScrollViewDemo/ReuseViewController.m | 14 +-- .../LazyScrollViewTest/ModelBucketTest.m | 2 +- 9 files changed, 163 insertions(+), 13 deletions(-) create mode 100644 LazyScrollViewDemo/LazyScrollViewDemo/MoreViewController.h create mode 100644 LazyScrollViewDemo/LazyScrollViewDemo/MoreViewController.m diff --git a/LazyScrollView/TMLazyScrollView.h b/LazyScrollView/TMLazyScrollView.h index 5db5418..f7e2482 100644 --- a/LazyScrollView/TMLazyScrollView.h +++ b/LazyScrollView/TMLazyScrollView.h @@ -84,6 +84,7 @@ @property (nonatomic, strong, readonly, nonnull) NSSet *inScreenVisibleItems; - (void)reloadData; +- (void)loadMoreDataFromIndex:(NSInteger)index; /** Get reuseable item view by reuseIdentifier. diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index 1a22a19..4e003bc 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -280,12 +280,14 @@ - (void)assembleSubviews:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloat)maxY #pragma mark Reload -- (void)storeItemModels +- (void)storeItemModelsFromIndex:(NSInteger)startIndex { - [_modelBucket clear]; + if (startIndex == 0) { + [_modelBucket clear]; + } if (self.dataSource) { NSInteger count = [self.dataSource numberOfItemsInScrollView:self]; - for (NSInteger index = 0; index < count; index++) { + for (NSInteger index = startIndex; index < count; index++) { TMLazyItemModel *itemModel = [self.dataSource scrollView:self itemModelAtIndex:index]; if (itemModel.muiID.length == 0) { itemModel.muiID = [NSString stringWithFormat:@"%zd", index]; @@ -297,10 +299,16 @@ - (void)storeItemModels - (void)reloadData { - [self storeItemModels]; + [self storeItemModelsFromIndex:0]; [self assembleSubviews:YES]; } +- (void)loadMoreDataFromIndex:(NSInteger)index +{ + [self storeItemModelsFromIndex:index]; + [self assembleSubviews:NO]; +} + - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier { return [self dequeueReusableItemWithIdentifier:identifier muiID:nil]; diff --git a/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj b/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj index 0505582..de58a38 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj +++ b/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 927CAE432046B37700BD3B19 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 927CAE422046B37700BD3B19 /* Assets.xcassets */; }; 927CAE462046B37700BD3B19 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 927CAE442046B37700BD3B19 /* LaunchScreen.storyboard */; }; 927CAE492046B37700BD3B19 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 927CAE482046B37700BD3B19 /* main.m */; }; + 929CC56A20592B1400C2870B /* MoreViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 929CC56920592B1400C2870B /* MoreViewController.m */; }; 92F01C4720493C36000983CA /* MainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 92F01C4620493C36000983CA /* MainViewController.m */; }; 92F01C4A20493D9C000983CA /* OuterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 92F01C4920493D9C000983CA /* OuterViewController.m */; }; D1AB44F5D8E0B6975BF3CDF3 /* libPods-LazyScrollViewDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CA97F266258FD7B207134C76 /* libPods-LazyScrollViewDemo.a */; }; @@ -28,6 +29,8 @@ 927CAE452046B37700BD3B19 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 927CAE472046B37700BD3B19 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 927CAE482046B37700BD3B19 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 929CC56820592B1400C2870B /* MoreViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MoreViewController.h; sourceTree = ""; }; + 929CC56920592B1400C2870B /* MoreViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MoreViewController.m; sourceTree = ""; }; 92F01C4520493C36000983CA /* MainViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainViewController.h; sourceTree = ""; }; 92F01C4620493C36000983CA /* MainViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MainViewController.m; sourceTree = ""; }; 92F01C4820493D9C000983CA /* OuterViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OuterViewController.h; sourceTree = ""; }; @@ -86,6 +89,8 @@ 927CAE3D2046B37700BD3B19 /* ReuseViewController.m */, 92F01C4820493D9C000983CA /* OuterViewController.h */, 92F01C4920493D9C000983CA /* OuterViewController.m */, + 929CC56820592B1400C2870B /* MoreViewController.h */, + 929CC56920592B1400C2870B /* MoreViewController.m */, 927CAE422046B37700BD3B19 /* Assets.xcassets */, 927CAE442046B37700BD3B19 /* LaunchScreen.storyboard */, 927CAE472046B37700BD3B19 /* Info.plist */, @@ -227,6 +232,7 @@ buildActionMask = 2147483647; files = ( 927CAE3E2046B37700BD3B19 /* ReuseViewController.m in Sources */, + 929CC56A20592B1400C2870B /* MoreViewController.m in Sources */, 927CAE492046B37700BD3B19 /* main.m in Sources */, 92F01C4A20493D9C000983CA /* OuterViewController.m in Sources */, 927CAE3B2046B37700BD3B19 /* AppDelegate.m in Sources */, diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/MainViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/MainViewController.m index b5be26c..bdb8c0f 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/MainViewController.m +++ b/LazyScrollViewDemo/LazyScrollViewDemo/MainViewController.m @@ -7,6 +7,7 @@ #import "MainViewController.h" #import "OuterViewController.h" +#import "MoreViewController.h" @interface MainViewController () @@ -20,7 +21,7 @@ - (instancetype)init { if (self = [super init]) { self.title = @"LazyScrollDemo"; - self.demoArray = @[@"Reuse", @"OuterScrollView"]; + self.demoArray = @[@"Reuse", @"OuterScrollView", @"LoadMore"]; } return self; } @@ -46,6 +47,8 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath UIViewController *vc; if ([demoName isEqualToString:@"OuterScrollView"]) { vc = [OuterViewController new]; + } else if ([demoName isEqualToString:@"LoadMore"]) { + vc = [MoreViewController new]; } else { Class demoVcClass = NSClassFromString([demoName stringByAppendingString:@"ViewController"]); vc = [demoVcClass new]; diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/MoreViewController.h b/LazyScrollViewDemo/LazyScrollViewDemo/MoreViewController.h new file mode 100644 index 0000000..7de694e --- /dev/null +++ b/LazyScrollViewDemo/LazyScrollViewDemo/MoreViewController.h @@ -0,0 +1,12 @@ +// +// MoreViewController.h +// LazyScrollViewDemo +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import + +@interface MoreViewController : UIViewController + +@end diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/MoreViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/MoreViewController.m new file mode 100644 index 0000000..a21d99a --- /dev/null +++ b/LazyScrollViewDemo/LazyScrollViewDemo/MoreViewController.m @@ -0,0 +1,113 @@ +// +// MoreViewController.m +// LazyScrollViewDemo +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import "MoreViewController.h" +#import +#import + +@interface MoreViewController () { + NSMutableArray * _rectArray; + NSMutableArray * _colorArray; + TMLazyScrollView * _scrollView; + + CGFloat maxY; +} + +@end + +@implementation MoreViewController + +- (instancetype)init +{ + if (self = [super init]) { + self.title = @"More"; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + _scrollView = [[TMLazyScrollView alloc] initWithFrame:self.view.bounds]; + _scrollView.dataSource = self; + _scrollView.autoAddSubview = YES; + [self.view addSubview:_scrollView]; + + _rectArray = [[NSMutableArray alloc] init]; + maxY = 0; + CGFloat currentY = 10; + CGFloat viewWidth = CGRectGetWidth(self.view.bounds); + for (int i = 0; i < 10; i++) { + [self addRect:CGRectMake(10, i * 80 + currentY, viewWidth - 20, 80 - 3)]; + } + + _colorArray = [NSMutableArray arrayWithCapacity:_rectArray.count]; + CGFloat hue = 0; + for (int i = 0; i < 20; i++) { + [_colorArray addObject:[UIColor colorWithHue:hue saturation:1 brightness:1 alpha:1]]; + hue += 0.05; + } + + _scrollView.contentSize = CGSizeMake(viewWidth, maxY + 10); + [_scrollView reloadData]; + + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"LoadMore" style:UIBarButtonItemStylePlain target:self action:@selector(loadMoreAction)]; +} + +- (void)loadMoreAction +{ + CGFloat currentY = maxY + 3; + CGFloat viewWidth = CGRectGetWidth(self.view.bounds); + for (int i = 0; i < 10; i++) { + [self addRect:CGRectMake(10, i * 80 + currentY, viewWidth - 20, 80 - 3)]; + } + + _scrollView.contentSize = CGSizeMake(viewWidth, maxY + 10); + [_scrollView loadMoreDataFromIndex:_rectArray.count - 10]; +} + +#pragma mark LazyScrollView + +- (NSUInteger)numberOfItemsInScrollView:(TMLazyScrollView *)scrollView +{ + return _rectArray.count; +} + +- (TMLazyItemModel *)scrollView:(TMLazyScrollView *)scrollView itemModelAtIndex:(NSUInteger)index +{ + CGRect rect = [(NSValue *)[_rectArray objectAtIndex:index] CGRectValue]; + TMLazyItemModel *rectModel = [[TMLazyItemModel alloc] init]; + rectModel.absRect = rect; + rectModel.muiID = [NSString stringWithFormat:@"%zd", index]; + return rectModel; +} + +- (UIView *)scrollView:(TMLazyScrollView *)scrollView itemByMuiID:(NSString *)muiID +{ + UIView *view = (UIView *)[scrollView dequeueReusableItemWithIdentifier:@"testView"]; + NSInteger index = [muiID integerValue]; + if (!view) { + NSLog(@"create a new view"); + view = [UIView new]; + view.reuseIdentifier = @"testView"; + view.backgroundColor = [_colorArray tm_safeObjectAtIndex:index % 20]; + } + view.frame = [(NSValue *)[_rectArray objectAtIndex:index] CGRectValue]; + return view; +} + +#pragma mark Private + +- (void)addRect:(CGRect)newRect +{ + if (CGRectGetMaxY(newRect) > maxY) { + maxY = CGRectGetMaxY(newRect); + } + [_rectArray addObject:[NSValue valueWithCGRect:newRect]]; +} + +@end diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m index 8046660..97f7755 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m +++ b/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m @@ -63,6 +63,13 @@ - (void)viewDidLoad _scrollView.outerScrollView = self.tableView; self.tableView.tableFooterView = _scrollView; [_scrollView reloadData]; + + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Reload" style:UIBarButtonItemStylePlain target:self action:@selector(reloadAction)]; +} + +- (void)reloadAction +{ + [_scrollView reloadData]; } #pragma mark TableView diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/ReuseViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/ReuseViewController.m index b6a7c36..903203a 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/ReuseViewController.m +++ b/LazyScrollViewDemo/LazyScrollViewDemo/ReuseViewController.m @@ -47,10 +47,10 @@ - (void)viewDidLoad { [super viewDidLoad]; // STEP 1: Create LazyScrollView - TMLazyScrollView *scrollview = [[TMLazyScrollView alloc] initWithFrame:self.view.bounds]; - scrollview.dataSource = self; - scrollview.autoAddSubview = YES; - [self.view addSubview:scrollview]; + TMLazyScrollView *scrollView = [[TMLazyScrollView alloc] initWithFrame:self.view.bounds]; + scrollView.dataSource = self; + scrollView.autoAddSubview = YES; + [self.view addSubview:scrollView]; // Here is frame array for test. // LazyScrollView must know item view's frame before rending. @@ -85,15 +85,15 @@ - (void)viewDidLoad { } // STEP 3: reload LazyScrollView - scrollview.contentSize = CGSizeMake(viewWidth, maxY + 10); - [scrollview reloadData]; + scrollView.contentSize = CGSizeMake(viewWidth, maxY + 10); + [scrollView reloadData]; // A tip. UILabel *tipLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, viewWidth - 20, 30)]; tipLabel.font = [UIFont systemFontOfSize:12]; tipLabel.numberOfLines = 0; tipLabel.text = @"Item views's color should be from red to blue. They are reused. Magenta should not be appeared."; - [scrollview addSubview:tipLabel]; + [scrollView addSubview:tipLabel]; } #pragma mark LazyScrollView diff --git a/LazyScrollViewTest/LazyScrollViewTest/ModelBucketTest.m b/LazyScrollViewTest/LazyScrollViewTest/ModelBucketTest.m index 83ae499..a16ae8e 100644 --- a/LazyScrollViewTest/LazyScrollViewTest/ModelBucketTest.m +++ b/LazyScrollViewTest/LazyScrollViewTest/ModelBucketTest.m @@ -1,6 +1,6 @@ // // ModelBucketTest.m -// ModelBucketTest +// LazyScrollViewTest // // Copyright (c) 2015-2018 Alibaba. All rights reserved. // From 61a9abe532889c8da387bf895de2f1636c706560 Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Thu, 15 Mar 2018 16:27:05 +0800 Subject: [PATCH 24/30] modify the loadMoreData interface --- LazyScrollView/TMLazyScrollView.h | 2 +- LazyScrollView/TMLazyScrollView.m | 10 ++++++---- .../LazyScrollViewDemo/MoreViewController.m | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/LazyScrollView/TMLazyScrollView.h b/LazyScrollView/TMLazyScrollView.h index f7e2482..f33c989 100644 --- a/LazyScrollView/TMLazyScrollView.h +++ b/LazyScrollView/TMLazyScrollView.h @@ -84,7 +84,7 @@ @property (nonatomic, strong, readonly, nonnull) NSSet *inScreenVisibleItems; - (void)reloadData; -- (void)loadMoreDataFromIndex:(NSInteger)index; +- (void)loadMoreData; /** Get reuseable item view by reuseIdentifier. diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index 4e003bc..21763cc 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -30,6 +30,7 @@ @interface TMLazyScrollView () { // Store item models. TMLazyModelBucket *_modelBucket; + NSInteger _itemCount; // Store items which need to be reloaded. NSMutableSet *_needReloadingMuiIDs; @@ -283,11 +284,12 @@ - (void)assembleSubviews:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloat)maxY - (void)storeItemModelsFromIndex:(NSInteger)startIndex { if (startIndex == 0) { + _itemCount = 0; [_modelBucket clear]; } if (self.dataSource) { - NSInteger count = [self.dataSource numberOfItemsInScrollView:self]; - for (NSInteger index = startIndex; index < count; index++) { + _itemCount = [self.dataSource numberOfItemsInScrollView:self]; + for (NSInteger index = startIndex; index < _itemCount; index++) { TMLazyItemModel *itemModel = [self.dataSource scrollView:self itemModelAtIndex:index]; if (itemModel.muiID.length == 0) { itemModel.muiID = [NSString stringWithFormat:@"%zd", index]; @@ -303,9 +305,9 @@ - (void)reloadData [self assembleSubviews:YES]; } -- (void)loadMoreDataFromIndex:(NSInteger)index +- (void)loadMoreData { - [self storeItemModelsFromIndex:index]; + [self storeItemModelsFromIndex:_itemCount]; [self assembleSubviews:NO]; } diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/MoreViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/MoreViewController.m index a21d99a..1eda0bf 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/MoreViewController.m +++ b/LazyScrollViewDemo/LazyScrollViewDemo/MoreViewController.m @@ -67,7 +67,7 @@ - (void)loadMoreAction } _scrollView.contentSize = CGSizeMake(viewWidth, maxY + 10); - [_scrollView loadMoreDataFromIndex:_rectArray.count - 10]; + [_scrollView loadMoreData]; } #pragma mark LazyScrollView From 6372b367d6ec8157dc20aa11ebeadb14fa91da5d Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Thu, 15 Mar 2018 20:39:03 +0800 Subject: [PATCH 25/30] Add load item views "async" feature --- LazyScrollView/TMLazyScrollView.h | 6 + LazyScrollView/TMLazyScrollView.m | 162 ++++++++++-------- .../project.pbxproj | 6 + .../LazyScrollViewDemo/AsyncViewController.h | 12 ++ .../LazyScrollViewDemo/AsyncViewController.m | 116 +++++++++++++ .../LazyScrollViewDemo/MainViewController.m | 2 +- .../LazyScrollViewDemo/MoreViewController.m | 1 - .../LazyScrollViewDemo/OuterViewController.m | 1 - 8 files changed, 231 insertions(+), 75 deletions(-) create mode 100644 LazyScrollViewDemo/LazyScrollViewDemo/AsyncViewController.h create mode 100644 LazyScrollViewDemo/LazyScrollViewDemo/AsyncViewController.m diff --git a/LazyScrollView/TMLazyScrollView.h b/LazyScrollView/TMLazyScrollView.h index f33c989..adae3f3 100644 --- a/LazyScrollView/TMLazyScrollView.h +++ b/LazyScrollView/TMLazyScrollView.h @@ -71,6 +71,12 @@ */ @property (nonatomic, assign) BOOL autoClearGestures; +/** + If it is NO, LazyScrollView will try to load new item views in several frames. + Default value is YES. + */ +@property (nonatomic, assign) BOOL loadAllItemsImmediately; + /** Item views which is in the buffer area. They will be shown soon. diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index 21763cc..57d419b 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -26,27 +26,27 @@ @interface TMLazyOuterScrollViewObserver: NSObject @interface TMLazyScrollView () { NSMutableSet *_visibleItems; - NSMutableSet *_inScreenVisibleItems; + NSMutableSet *_inScreenVisibleMuiIDs; // Store item models. TMLazyModelBucket *_modelBucket; NSInteger _itemCount; - // Store items which need to be reloaded. + // Store muiID of items which need to be reloaded. NSMutableSet *_needReloadingMuiIDs; + // Store muiID of items which should be visible. + NSMutableSet *_newVisibleMuiIDs; + // Record current muiID of reloading item. // Will be used for dequeueReusableItem methods. NSString *_currentReloadingMuiID; // Store the enter screen times of items. NSMutableDictionary *_enterTimesDict; - - // Store visible models for the last time. Used for calc enter times. - NSSet *_lastInScreenVisibleModels; - // Record contentOffset of scrollview in previous time that - // calculate views to show. + // Record contentOffset of scrollView that used for calculating + // views to show last time. CGPoint _lastContentOffset; } @@ -62,16 +62,13 @@ @implementation TMLazyScrollView - (NSSet *)inScreenVisibleItems { - if (!_inScreenVisibleItems) { - _inScreenVisibleItems = [NSMutableSet set]; - NSSet *lastInScreenVisibleMuiIDs = [_lastInScreenVisibleModels valueForKey:@"muiID"]; - for (UIView *view in _visibleItems) { - if ([lastInScreenVisibleMuiIDs containsObject:view.muiID]) { - [_inScreenVisibleItems addObject:view]; - } + NSMutableSet * inScreenVisibleItems = [NSMutableSet set]; + for (UIView *view in _visibleItems) { + if ([_inScreenVisibleMuiIDs containsObject:view.muiID]) { + [inScreenVisibleItems addObject:view]; } } - return [_inScreenVisibleItems copy]; + return [inScreenVisibleItems copy]; } - (NSSet *)visibleItems @@ -123,17 +120,19 @@ - (id)initWithFrame:(CGRect)frame self.showsHorizontalScrollIndicator = NO; self.showsVerticalScrollIndicator = NO; _autoClearGestures = YES; + _loadAllItemsImmediately = YES; _reusePool = [TMLazyReusePool new]; _visibleItems = [[NSMutableSet alloc] init]; + _inScreenVisibleMuiIDs = [NSMutableSet set]; + _modelBucket = [[TMLazyModelBucket alloc] initWithBucketHeight:LazyBucketHeight]; _needReloadingMuiIDs = [[NSMutableSet alloc] init]; _enterTimesDict = [[NSMutableDictionary alloc] init]; - _lastInScreenVisibleModels = [NSSet set]; } return self; } @@ -185,17 +184,8 @@ - (void)assembleSubviews:(BOOL)isReload } } -- (void)assembleSubviews:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloat)maxY +- (void)recycleItems:(BOOL)isReload newVisibleMuiIDs:(NSSet *)newVisibleMuiIDs { - // Calculate which item views should be shown. - // Calculating will cost some time, so here is a buffer for reducing - // times of calculating. - NSSet *newVisibleModels = [_modelBucket showingModelsFrom:minY - LazyBufferHeight - to:maxY + LazyBufferHeight]; - NSSet *newVisibleMuiIDs = [newVisibleModels valueForKey:@"muiID"]; - - // Find if item views are in visible area. - // Recycle invisible item views. NSSet *visibleItemsCopy = [_visibleItems copy]; for (UIView *itemView in visibleItemsCopy) { BOOL isToShow = [newVisibleMuiIDs containsObject:itemView.muiID]; @@ -215,68 +205,97 @@ - (void)assembleSubviews:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloat)maxY [_needReloadingMuiIDs addObject:itemView.muiID]; } } +} + +- (void)generateItems:(BOOL)isReload +{ + if (_newVisibleMuiIDs == nil || _newVisibleMuiIDs.count == 0) { + return; + } - // Generate or reload visible item views. - if (self.dataSource) { - for (NSString *muiID in newVisibleMuiIDs) { - // 1. Item view is not visible. We should create or reuse an item view. - // 2. Item view need to be reloaded. - BOOL isVisible = [self isMuiIdVisible:muiID]; - BOOL needReload = [_needReloadingMuiIDs containsObject:muiID]; - if (isVisible == NO || needReload == YES) { - // If you call dequeue method in your dataSource, the currentReloadingMuiID - // will be used for searching the best-matched reusable view. - if (isVisible) { - _currentReloadingMuiID = muiID; - } - UIView *itemView = [self.dataSource scrollView:self itemByMuiID:muiID]; - _currentReloadingMuiID = nil; + NSString *muiID = [_newVisibleMuiIDs anyObject]; + BOOL hasLoadAnItem = NO; + + // 1. Item view is not visible. We should create or reuse an item view. + // 2. Item view need to be reloaded. + BOOL isVisible = [self isMuiIdVisible:muiID]; + BOOL needReload = [_needReloadingMuiIDs containsObject:muiID]; + if (isVisible == NO || needReload == YES) { + if (self.dataSource) { + hasLoadAnItem = YES; + // If you call dequeue method in your dataSource, the currentReloadingMuiID + // will be used for searching the best-matched reusable view. + if (isVisible == YES) { + _currentReloadingMuiID = muiID; + } + UIView *itemView = [self.dataSource scrollView:self itemByMuiID:muiID]; + _currentReloadingMuiID = nil; + + if (itemView) { // Call afterGetView. if ([itemView respondsToSelector:@selector(mui_afterGetView)]) { [(UIView *)itemView mui_afterGetView]; } - if (itemView) { - itemView.muiID = muiID; - itemView.hidden = NO; - if (![_visibleItems containsObject:itemView]) { - [_visibleItems addObject:itemView]; + // Show the item view. + itemView.muiID = muiID; + itemView.hidden = NO; + if (self.autoAddSubview) { + if (itemView.superview != self) { + [self addSubview:itemView]; } - if (self.autoAddSubview) { - if (itemView.superview != self) { - [self addSubview:itemView]; - } + } + // Add item view to visibleItems. + if (isVisible == NO) { + [_visibleItems addObject:itemView]; + // Call didEnterWithTimes. + if ([itemView respondsToSelector:@selector(mui_didEnterWithTimes:)]) { + NSInteger times = [_enterTimesDict tm_integerForKey:itemView.muiID]; + times++; + [_enterTimesDict tm_safeSetObject:@(times) forKey:itemView.muiID]; + [(UIView *)itemView mui_didEnterWithTimes:times]; } } - [_needReloadingMuiIDs removeObject:muiID]; } + + [_needReloadingMuiIDs removeObject:muiID]; } } - // Reset the inScreenVisibleItems. - _inScreenVisibleItems = nil; + [_newVisibleMuiIDs removeObject:muiID]; + if (_newVisibleMuiIDs.count > 0) { + if (isReload == YES || self.loadAllItemsImmediately == YES || hasLoadAnItem == NO) { + [self generateItems:isReload]; + } else { + [self performSelector:@selector(generateItems:) withObject:@(NO) afterDelay:0.0000001]; + } + } +} + +- (void)assembleSubviews:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloat)maxY +{ + // Calculate which item views should be shown. + // Calculating will cost some time, so here is a buffer for reducing + // times of calculating. + NSSet *newVisibleModels = [_modelBucket showingModelsFrom:minY - LazyBufferHeight + to:maxY + LazyBufferHeight]; + NSSet *newVisibleMuiIDs = [newVisibleModels valueForKey:@"muiID"]; + + // Find if item views are in visible area. + // Recycle invisible item views. + [self recycleItems:isReload newVisibleMuiIDs:newVisibleMuiIDs]; + + // Generate or reload visible item views. + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(generateItems:) object:@(NO)]; + _newVisibleMuiIDs = [newVisibleMuiIDs mutableCopy]; + [self generateItems:isReload]; // Calculate the inScreenVisibleModels. - NSMutableSet *newInScreenVisibleModels = [NSMutableSet setWithCapacity:newVisibleModels.count]; - NSMutableSet *enteredMuiIDs = [NSMutableSet set]; + [_inScreenVisibleMuiIDs removeAllObjects]; for (TMLazyItemModel *itemModel in newVisibleModels) { if (itemModel.top < maxY && itemModel.bottom > minY) { - [newInScreenVisibleModels addObject:itemModel]; - if ([_lastInScreenVisibleModels containsObject:itemModel] == NO) { - [enteredMuiIDs addObject:itemModel.muiID]; - } - } - } - for (UIView *itemView in _visibleItems) { - if ([enteredMuiIDs containsObject:itemView.muiID]) { - if ([itemView respondsToSelector:@selector(mui_didEnterWithTimes:)]) { - NSInteger times = [_enterTimesDict tm_integerForKey:itemView.muiID]; - times++; - [_enterTimesDict tm_safeSetObject:@(times) forKey:itemView.muiID]; - [(UIView *)itemView mui_didEnterWithTimes:times]; - } + [_inScreenVisibleMuiIDs addObject:itemModel.muiID]; } } - _lastInScreenVisibleModels = newInScreenVisibleModels; } #pragma mark Reload @@ -337,7 +356,7 @@ - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSSt result.gestureRecognizers = nil; } if ([result respondsToSelector:@selector(mui_prepareForReuse)]) { - [(id)result mui_prepareForReuse]; + [(UIView *)result mui_prepareForReuse]; } } } @@ -365,7 +384,6 @@ - (void)removeAllLayouts - (void)resetItemsEnterTimes { [_enterTimesDict removeAllObjects]; - _lastInScreenVisibleModels = [NSSet set]; } - (void)resetViewEnterTimes diff --git a/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj b/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj index de58a38..4bbc0db 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj +++ b/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 9247FDBD205A9311004FB14E /* AsyncViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9247FDBC205A9310004FB14E /* AsyncViewController.m */; }; 927CAE3B2046B37700BD3B19 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 927CAE3A2046B37700BD3B19 /* AppDelegate.m */; }; 927CAE3E2046B37700BD3B19 /* ReuseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 927CAE3D2046B37700BD3B19 /* ReuseViewController.m */; }; 927CAE432046B37700BD3B19 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 927CAE422046B37700BD3B19 /* Assets.xcassets */; }; @@ -20,6 +21,8 @@ /* Begin PBXFileReference section */ 80196663B6BB20F868052CE9 /* Pods-LazyScrollViewDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LazyScrollViewDemo.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-LazyScrollViewDemo/Pods-LazyScrollViewDemo.debug.xcconfig"; sourceTree = ""; }; + 9247FDBB205A9310004FB14E /* AsyncViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AsyncViewController.h; sourceTree = ""; }; + 9247FDBC205A9310004FB14E /* AsyncViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AsyncViewController.m; sourceTree = ""; }; 927CAE362046B37700BD3B19 /* LazyScrollViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LazyScrollViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 927CAE392046B37700BD3B19 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 927CAE3A2046B37700BD3B19 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -91,6 +94,8 @@ 92F01C4920493D9C000983CA /* OuterViewController.m */, 929CC56820592B1400C2870B /* MoreViewController.h */, 929CC56920592B1400C2870B /* MoreViewController.m */, + 9247FDBB205A9310004FB14E /* AsyncViewController.h */, + 9247FDBC205A9310004FB14E /* AsyncViewController.m */, 927CAE422046B37700BD3B19 /* Assets.xcassets */, 927CAE442046B37700BD3B19 /* LaunchScreen.storyboard */, 927CAE472046B37700BD3B19 /* Info.plist */, @@ -237,6 +242,7 @@ 92F01C4A20493D9C000983CA /* OuterViewController.m in Sources */, 927CAE3B2046B37700BD3B19 /* AppDelegate.m in Sources */, 92F01C4720493C36000983CA /* MainViewController.m in Sources */, + 9247FDBD205A9311004FB14E /* AsyncViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/AsyncViewController.h b/LazyScrollViewDemo/LazyScrollViewDemo/AsyncViewController.h new file mode 100644 index 0000000..e41fc03 --- /dev/null +++ b/LazyScrollViewDemo/LazyScrollViewDemo/AsyncViewController.h @@ -0,0 +1,12 @@ +// +// AsyncViewController.h +// LazyScrollViewDemo +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import + +@interface AsyncViewController : UIViewController + +@end diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/AsyncViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/AsyncViewController.m new file mode 100644 index 0000000..654bdc7 --- /dev/null +++ b/LazyScrollViewDemo/LazyScrollViewDemo/AsyncViewController.m @@ -0,0 +1,116 @@ +// +// AsyncViewController.m +// LazyScrollViewDemo +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import "AsyncViewController.h" +#import +#import + +@interface AsyncViewController () { + NSMutableArray * _rectArray; + NSMutableArray * _colorArray; + TMLazyScrollView * _scrollView; + + BOOL enableDelay; +} + +@end + +@implementation AsyncViewController + +- (instancetype)init +{ + if (self = [super init]) { + self.title = @"Sync"; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + _scrollView = [[TMLazyScrollView alloc] initWithFrame:self.view.bounds]; + _scrollView.dataSource = self; + _scrollView.autoAddSubview = YES; + [self.view addSubview:_scrollView]; + + _rectArray = [[NSMutableArray alloc] init]; + CGFloat currentY = 10, maxY = 0; + CGFloat viewWidth = CGRectGetWidth(self.view.bounds); + for (int i = 0; i < 50; i++) { + [self addRect:CGRectMake(10, i * 80 + currentY, 98, 80 - 3) andUpdateMaxY:&maxY]; + [self addRect:CGRectMake(111, i * 80 + currentY, 98, 80 - 3) andUpdateMaxY:&maxY]; + [self addRect:CGRectMake(212, i * 80 + currentY, 98, 80 - 3) andUpdateMaxY:&maxY]; + } + + _colorArray = [NSMutableArray arrayWithCapacity:_rectArray.count]; + CGFloat hue = 0; + for (int i = 0; i < 20; i++) { + [_colorArray addObject:[UIColor colorWithHue:hue saturation:1 brightness:1 alpha:1]]; + hue += 0.05; + } + + _scrollView.contentSize = CGSizeMake(viewWidth, maxY + 10); + [_scrollView reloadData]; + enableDelay = YES; + + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Switch" style:UIBarButtonItemStylePlain target:self action:@selector(switchAction)]; +} + +- (void)switchAction +{ + _scrollView.loadAllItemsImmediately = !_scrollView.loadAllItemsImmediately; + if (_scrollView.loadAllItemsImmediately) { + self.title = @"Sync"; + } else { + self.title = @"Async"; + } +} + +#pragma mark LazyScrollView + +- (NSUInteger)numberOfItemsInScrollView:(TMLazyScrollView *)scrollView +{ + return _rectArray.count; +} + +- (TMLazyItemModel *)scrollView:(TMLazyScrollView *)scrollView itemModelAtIndex:(NSUInteger)index +{ + CGRect rect = [(NSValue *)[_rectArray objectAtIndex:index] CGRectValue]; + TMLazyItemModel *rectModel = [[TMLazyItemModel alloc] init]; + rectModel.absRect = rect; + rectModel.muiID = [NSString stringWithFormat:@"%zd", index]; + return rectModel; +} + +- (UIView *)scrollView:(TMLazyScrollView *)scrollView itemByMuiID:(NSString *)muiID +{ + UIView *view = (UIView *)[scrollView dequeueReusableItemWithIdentifier:@"testView"]; + NSInteger index = [muiID integerValue]; + if (!view) { + view = [UIView new]; + view.reuseIdentifier = @"testView"; + view.backgroundColor = [_colorArray tm_safeObjectAtIndex:index % 20]; + } + view.frame = [(NSValue *)[_rectArray objectAtIndex:index] CGRectValue]; + if (enableDelay) { + // Add a delay manually. + [NSThread sleepForTimeInterval:0.015]; + } + return view; +} + +#pragma mark Private + +- (void)addRect:(CGRect)newRect andUpdateMaxY:(CGFloat *)maxY +{ + if (CGRectGetMaxY(newRect) > *maxY) { + *maxY = CGRectGetMaxY(newRect); + } + [_rectArray addObject:[NSValue valueWithCGRect:newRect]]; +} + +@end diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/MainViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/MainViewController.m index bdb8c0f..26573ba 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/MainViewController.m +++ b/LazyScrollViewDemo/LazyScrollViewDemo/MainViewController.m @@ -21,7 +21,7 @@ - (instancetype)init { if (self = [super init]) { self.title = @"LazyScrollDemo"; - self.demoArray = @[@"Reuse", @"OuterScrollView", @"LoadMore"]; + self.demoArray = @[@"Reuse", @"OuterScrollView", @"LoadMore", @"Async"]; } return self; } diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/MoreViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/MoreViewController.m index 1eda0bf..a38ac37 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/MoreViewController.m +++ b/LazyScrollViewDemo/LazyScrollViewDemo/MoreViewController.m @@ -91,7 +91,6 @@ - (UIView *)scrollView:(TMLazyScrollView *)scrollView itemByMuiID:(NSString *)mu UIView *view = (UIView *)[scrollView dequeueReusableItemWithIdentifier:@"testView"]; NSInteger index = [muiID integerValue]; if (!view) { - NSLog(@"create a new view"); view = [UIView new]; view.reuseIdentifier = @"testView"; view.backgroundColor = [_colorArray tm_safeObjectAtIndex:index % 20]; diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m index 97f7755..7db7c1b 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m +++ b/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m @@ -114,7 +114,6 @@ - (UIView *)scrollView:(TMLazyScrollView *)scrollView itemByMuiID:(NSString *)mu UIView *view = (UIView *)[scrollView dequeueReusableItemWithIdentifier:@"testView"]; NSInteger index = [muiID integerValue]; if (!view) { - NSLog(@"create a new view"); view = [UIView new]; view.reuseIdentifier = @"testView"; view.backgroundColor = [_colorArray tm_safeObjectAtIndex:index]; From a928dec2438fe31a3f911a4f06e13808807e1471 Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Thu, 15 Mar 2018 20:39:49 +0800 Subject: [PATCH 26/30] modify the ModelBucket interface a little --- LazyScrollView/TMLazyModelBucket.h | 5 +-- LazyScrollView/TMLazyModelBucket.m | 20 +++++++---- .../LazyScrollViewTest/ModelBucketTest.m | 36 +++++++++++-------- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/LazyScrollView/TMLazyModelBucket.h b/LazyScrollView/TMLazyModelBucket.h index 9195b8c..2c8c6b5 100644 --- a/LazyScrollView/TMLazyModelBucket.h +++ b/LazyScrollView/TMLazyModelBucket.h @@ -20,10 +20,11 @@ - (instancetype)initWithBucketHeight:(CGFloat)bucketHeight; - (void)addModel:(TMLazyItemModel *)itemModel; +- (void)addModels:(NSSet *)itemModels; - (void)removeModel:(TMLazyItemModel *)itemModel; -- (void)removeModels:(NSArray *)itemModels; +- (void)removeModels:(NSSet *)itemModels; - (void)reloadModel:(TMLazyItemModel *)itemModel; -- (void)reloadModels:(NSArray *)itemModels; +- (void)reloadModels:(NSSet *)itemModels; - (void)clear; - (NSSet *)showingModelsFrom:(CGFloat)startY to:(CGFloat)endY; diff --git a/LazyScrollView/TMLazyModelBucket.m b/LazyScrollView/TMLazyModelBucket.m index 2e981aa..02473f3 100644 --- a/LazyScrollView/TMLazyModelBucket.m +++ b/LazyScrollView/TMLazyModelBucket.m @@ -43,6 +43,15 @@ - (void)addModel:(TMLazyItemModel *)itemModel } } +- (void)addModels:(NSSet *)itemModels +{ + if (itemModels) { + for (TMLazyItemModel *itemModel in itemModels) { + [self addModel:itemModel]; + } + } +} + - (void)removeModel:(TMLazyItemModel *)itemModel { if (itemModel) { @@ -52,12 +61,11 @@ - (void)removeModel:(TMLazyItemModel *)itemModel } } -- (void)removeModels:(NSArray *)itemModels +- (void)removeModels:(NSSet *)itemModels { if (itemModels) { - NSSet *itemModelSet = [NSSet setWithArray:itemModels]; for (NSMutableSet *bucket in _buckets) { - [bucket minusSet:itemModelSet]; + [bucket minusSet:itemModels]; } } } @@ -68,12 +76,10 @@ - (void)reloadModel:(TMLazyItemModel *)itemModel [self addModel:itemModel]; } -- (void)reloadModels:(NSArray *)itemModels +- (void)reloadModels:(NSSet *)itemModels { [self removeModels:itemModels]; - for (TMLazyItemModel *itemModel in itemModels) { - [self addModel:itemModel]; - } + [self addModels:itemModels]; } - (void)clear diff --git a/LazyScrollViewTest/LazyScrollViewTest/ModelBucketTest.m b/LazyScrollViewTest/LazyScrollViewTest/ModelBucketTest.m index a16ae8e..49ba395 100644 --- a/LazyScrollViewTest/LazyScrollViewTest/ModelBucketTest.m +++ b/LazyScrollViewTest/LazyScrollViewTest/ModelBucketTest.m @@ -34,20 +34,22 @@ - (void)testModelBucket { [bucket addModel:[ModelBucketTest createModelHelperWithY:30 height:10]]; [bucket addModel:[ModelBucketTest createModelHelperWithY:40 height:10]]; [bucket addModel:[ModelBucketTest createModelHelperWithY:50 height:10]]; - [bucket addModel:[ModelBucketTest createModelHelperWithY:0 height:20]]; - [bucket addModel:[ModelBucketTest createModelHelperWithY:10 height:20]]; - [bucket addModel:[ModelBucketTest createModelHelperWithY:20 height:20]]; - [bucket addModel:[ModelBucketTest createModelHelperWithY:30 height:20]]; - [bucket addModel:[ModelBucketTest createModelHelperWithY:40 height:20]]; - [bucket addModel:[ModelBucketTest createModelHelperWithY:0 height:30]]; - [bucket addModel:[ModelBucketTest createModelHelperWithY:10 height:30]]; - [bucket addModel:[ModelBucketTest createModelHelperWithY:20 height:30]]; - [bucket addModel:[ModelBucketTest createModelHelperWithY:30 height:30]]; - [bucket addModel:[ModelBucketTest createModelHelperWithY:0 height:40]]; - [bucket addModel:[ModelBucketTest createModelHelperWithY:10 height:40]]; - [bucket addModel:[ModelBucketTest createModelHelperWithY:20 height:40]]; - [bucket addModel:[ModelBucketTest createModelHelperWithY:0 height:50]]; - [bucket addModel:[ModelBucketTest createModelHelperWithY:10 height:50]]; + [bucket addModels:[NSSet setWithArray:@[ + [ModelBucketTest createModelHelperWithY:0 height:20], + [ModelBucketTest createModelHelperWithY:10 height:20], + [ModelBucketTest createModelHelperWithY:20 height:20], + [ModelBucketTest createModelHelperWithY:30 height:20], + [ModelBucketTest createModelHelperWithY:40 height:20], + [ModelBucketTest createModelHelperWithY:0 height:30], + [ModelBucketTest createModelHelperWithY:10 height:30], + [ModelBucketTest createModelHelperWithY:20 height:30], + [ModelBucketTest createModelHelperWithY:30 height:30], + [ModelBucketTest createModelHelperWithY:0 height:40], + [ModelBucketTest createModelHelperWithY:10 height:40], + [ModelBucketTest createModelHelperWithY:20 height:40], + [ModelBucketTest createModelHelperWithY:0 height:50], + [ModelBucketTest createModelHelperWithY:10 height:50] + ]]]; TMLazyItemModel *lastModel = [ModelBucketTest createModelHelperWithY:0 height:60]; [bucket addModel:lastModel]; @@ -103,6 +105,12 @@ - (void)testModelBucket { set = [set valueForKey:@"muiID"]; assertThat(set, containsInAnyOrder(@"10", @"40", @"50", @"1030", @"1040", @"1050", @"2020", @"2030", @"2040", @"3010", @"3020", @"3030", @"4010", @"4020", nil)); + [bucket removeModels:[bucket showingModelsFrom:0 to:40]]; + set = [bucket showingModelsFrom:0 to:60]; + assertThat(set, hasCountOf(3)); + set = [set valueForKey:@"muiID"]; + assertThat(set, containsInAnyOrder(@"4010", @"4020", @"5010", nil)); + [bucket clear]; assertThat([bucket showingModelsFrom:0 to:50], hasCountOf(0)); assertThat([bucket showingModelsFrom:0 to:40], hasCountOf(0)); From 8d0c8717de6ae364191fa1c40436051de1ecdfc7 Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Fri, 23 Mar 2018 15:23:55 +0800 Subject: [PATCH 27/30] fix some issues --- LazyScrollView/TMLazyItemModel.h | 1 + LazyScrollView/TMLazyReusePool.h | 3 +- LazyScrollView/TMLazyReusePool.m | 13 +++++++-- LazyScrollView/TMLazyScrollView.h | 28 +++++++++++++++++-- LazyScrollView/TMLazyScrollView.m | 46 +++++++++++++++++++++++++++---- 5 files changed, 79 insertions(+), 12 deletions(-) diff --git a/LazyScrollView/TMLazyItemModel.h b/LazyScrollView/TMLazyItemModel.h index fc74a61..cc83ee9 100644 --- a/LazyScrollView/TMLazyItemModel.h +++ b/LazyScrollView/TMLazyItemModel.h @@ -5,6 +5,7 @@ // Copyright (c) 2015-2018 Alibaba. All rights reserved. // +#import #import /** diff --git a/LazyScrollView/TMLazyReusePool.h b/LazyScrollView/TMLazyReusePool.h index 760ab48..c80897d 100644 --- a/LazyScrollView/TMLazyReusePool.h +++ b/LazyScrollView/TMLazyReusePool.h @@ -5,7 +5,7 @@ // Copyright (c) 2015-2018 Alibaba. All rights reserved. // -#import +#import @interface TMLazyReusePool : NSObject @@ -13,5 +13,6 @@ - (UIView *)dequeueItemViewForReuseIdentifier:(NSString *)reuseIdentifier; - (UIView *)dequeueItemViewForReuseIdentifier:(NSString *)reuseIdentifier andMuiID:(NSString *)muiID; - (void)clear; +- (NSSet *)allItemViews; @end diff --git a/LazyScrollView/TMLazyReusePool.m b/LazyScrollView/TMLazyReusePool.m index 8134fe5..1926420 100644 --- a/LazyScrollView/TMLazyReusePool.m +++ b/LazyScrollView/TMLazyReusePool.m @@ -26,7 +26,7 @@ - (instancetype)init - (void)addItemView:(UIView *)itemView forReuseIdentifier:(NSString *)reuseIdentifier { - if (reuseIdentifier == nil || itemView == nil) { + if (reuseIdentifier == nil || reuseIdentifier.length == 0 || itemView == nil) { return; } NSMutableSet *reuseSet = [_reuseDict tm_safeObjectForKey:reuseIdentifier]; @@ -44,7 +44,7 @@ - (UIView *)dequeueItemViewForReuseIdentifier:(NSString *)reuseIdentifier - (UIView *)dequeueItemViewForReuseIdentifier:(NSString *)reuseIdentifier andMuiID:(NSString *)muiID { - if (reuseIdentifier == nil) { + if (reuseIdentifier == nil || reuseIdentifier.length == 0) { return nil; } UIView *result = nil; @@ -73,4 +73,13 @@ - (void)clear [_reuseDict removeAllObjects]; } +- (NSSet *)allItemViews +{ + NSMutableSet *result = [NSMutableSet set]; + for (NSMutableSet *reuseSet in _reuseDict.allValues) { + [result unionSet:reuseSet]; + } + return [result copy]; +} + @end diff --git a/LazyScrollView/TMLazyScrollView.h b/LazyScrollView/TMLazyScrollView.h index adae3f3..8175391 100644 --- a/LazyScrollView/TMLazyScrollView.h +++ b/LazyScrollView/TMLazyScrollView.h @@ -7,6 +7,7 @@ #import #import "TMLazyItemModel.h" +#import "UIView+TMLazyScrollView.h" @class TMLazyReusePool; @class TMLazyScrollView; @@ -103,13 +104,34 @@ - (nullable UIView *)dequeueReusableItemWithIdentifier:(nonnull NSString *)identifier muiID:(nullable NSString *)muiID; -- (void)clearVisibleItems; -- (void)removeAllLayouts __deprecated_msg("use clearItemsAndReusePool"); +/** + Hide all visible items and recycle reusable item views. + After call this method, every item view will receive + 'afterGetView' & 'didEnterWithTimes' again. + + @param enableRecycle Recycle items or remove them. + */ +- (void)clearVisibleItems:(BOOL)enableRecycle; +- (void)removeAllLayouts __deprecated_msg("use clearVisibleItems: or resetAll"); + +/** + Remove reusable item views from reuse pool to release memory. + */ +- (void)clearReuseItems; +- (void)cleanRecycledView __deprecated_msg("use clearReuseItems"); /** - After call this method, the times of 'mui_didEnterWithTimes:' will start from 0. + After call this method, the times of 'didEnterWithTimes' will start from 0. */ - (void)resetItemsEnterTimes; - (void)resetViewEnterTimes __deprecated_msg("use resetItemsEnterTimes"); +/** + Reset the state of LazyScrollView. + */ +- (void)resetAll; + +- (void)removeContentOffsetObserver __deprecated_msg("set outerScrollView to nil"); +- (void)reLayout __deprecated_msg("use reloadData"); + @end diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index 57d419b..6ed4c84 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -8,7 +8,6 @@ #import "TMLazyScrollView.h" #import #import "TMLazyItemViewProtocol.h" -#import "UIView+TMLazyScrollView.h" #import "TMLazyReusePool.h" #import "TMLazyModelBucket.h" @@ -338,7 +337,6 @@ - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSString *)muiID { UIView *result = nil; - if (identifier && identifier.length > 0) { if (_currentReloadingMuiID) { for (UIView *item in _visibleItems) { if ([item.muiID isEqualToString:_currentReloadingMuiID] @@ -359,26 +357,44 @@ - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSSt [(UIView *)result mui_prepareForReuse]; } } - } return result; } #pragma mark Clear & Reset -- (void)clearVisibleItems +- (void)clearVisibleItems:(BOOL)enableRecycle { + if (enableRecycle) { for (UIView *itemView in _visibleItems) { - if (itemView.reuseIdentifier.length > 0) { itemView.hidden = YES; + if (itemView.reuseIdentifier.length > 0) { [self.reusePool addItemView:itemView forReuseIdentifier:itemView.reuseIdentifier]; } } + } else { + for (UIView *itemView in _visibleItems) { + [itemView removeFromSuperview]; + } + } [_visibleItems removeAllObjects]; } - (void)removeAllLayouts { - [self clearVisibleItems]; + [self clearVisibleItems:YES]; +} + +- (void)clearReuseItems +{ + for (UIView *itemView in [self.reusePool allItemViews]) { + [itemView removeFromSuperview]; + } + [self.reusePool clear]; +} + +- (void)cleanRecycledView +{ + [self clearReuseItems]; } - (void)resetItemsEnterTimes @@ -391,6 +407,24 @@ - (void)resetViewEnterTimes [self resetItemsEnterTimes]; } +- (void)resetAll +{ + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(generateItems:) object:@(NO)]; + [self clearVisibleItems:NO]; + [self clearReuseItems]; + [self resetItemsEnterTimes]; +} + +- (void)removeContentOffsetObserver +{ + self.outerScrollView = nil; +} + +- (void)reLayout +{ + [self reloadData]; +} + #pragma mark Private - (BOOL)isMuiIdVisible:(NSString *)muiID From 9a18414902891e1a39f5bf4504577c7a3d7a4bcb Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Fri, 23 Mar 2018 15:25:52 +0800 Subject: [PATCH 28/30] fix a bug of outerScrollView feature: cannot work correctly when LazyScrollView is not outerScrollView's direct subview. --- LazyScrollView/TMLazyScrollView.m | 58 ++++++++++++++++++------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index 6ed4c84..4366ecd 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -156,20 +156,30 @@ - (void)setContentOffset:(CGPoint)contentOffset - (void)outerScrollViewDidScroll { - if (LazyBufferHeight < ABS(self.outerScrollView.contentOffset.y - _lastContentOffset.y)) { - _lastContentOffset = self.outerScrollView.contentOffset; - [self assembleSubviews:NO]; + if (self.outerScrollView) { + CGPoint contentOffset = [self.outerScrollView convertPoint:self.outerScrollView.contentOffset toView:self]; + if (LazyBufferHeight < ABS(contentOffset.y - _lastContentOffset.y)) { + _lastContentOffset = contentOffset; + [self assembleSubviews:NO]; + } } } +- (void)layoutSubviews +{ + [super layoutSubviews]; + [self outerScrollViewDidScroll]; +} + #pragma mark CoreLogic - (void)assembleSubviews:(BOOL)isReload { if (self.outerScrollView) { - CGRect visibleArea = CGRectIntersection(self.outerScrollView.bounds, self.frame); + CGRect frame = [self.superview convertRect:self.frame toView:self.outerScrollView]; + CGRect visibleArea = CGRectIntersection(self.outerScrollView.bounds, frame); if (visibleArea.size.height > 0) { - CGFloat offsetY = CGRectGetMinY(self.frame); + CGFloat offsetY = CGRectGetMinY(frame); CGFloat minY = CGRectGetMinY(visibleArea) - offsetY; CGFloat maxY = CGRectGetMaxY(visibleArea) - offsetY; [self assembleSubviews:isReload minY:minY maxY:maxY]; @@ -337,26 +347,26 @@ - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSString *)muiID { UIView *result = nil; - if (_currentReloadingMuiID) { - for (UIView *item in _visibleItems) { - if ([item.muiID isEqualToString:_currentReloadingMuiID] - && [item.reuseIdentifier isEqualToString:identifier]) { - result = item; - break; - } + if (_currentReloadingMuiID) { + for (UIView *item in _visibleItems) { + if ([item.muiID isEqualToString:_currentReloadingMuiID] + && [item.reuseIdentifier isEqualToString:identifier]) { + result = item; + break; } } - if (result == nil) { - result = [self.reusePool dequeueItemViewForReuseIdentifier:identifier andMuiID:muiID]; + } + if (result == nil) { + result = [self.reusePool dequeueItemViewForReuseIdentifier:identifier andMuiID:muiID]; + } + if (result) { + if (self.autoClearGestures) { + result.gestureRecognizers = nil; } - if (result) { - if (self.autoClearGestures) { - result.gestureRecognizers = nil; - } - if ([result respondsToSelector:@selector(mui_prepareForReuse)]) { - [(UIView *)result mui_prepareForReuse]; - } + if ([result respondsToSelector:@selector(mui_prepareForReuse)]) { + [(UIView *)result mui_prepareForReuse]; } + } return result; } @@ -365,12 +375,12 @@ - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSSt - (void)clearVisibleItems:(BOOL)enableRecycle { if (enableRecycle) { - for (UIView *itemView in _visibleItems) { + for (UIView *itemView in _visibleItems) { itemView.hidden = YES; if (itemView.reuseIdentifier.length > 0) { - [self.reusePool addItemView:itemView forReuseIdentifier:itemView.reuseIdentifier]; + [self.reusePool addItemView:itemView forReuseIdentifier:itemView.reuseIdentifier]; + } } - } } else { for (UIView *itemView in _visibleItems) { [itemView removeFromSuperview]; From 010f41a8dc1e58ca2cf8447dc948b8f0ee6589f9 Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Mon, 26 Mar 2018 13:15:51 +0800 Subject: [PATCH 29/30] change to common runloop modes --- LazyScrollView/TMLazyScrollView.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index 4366ecd..8da2d19 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -275,7 +275,10 @@ - (void)generateItems:(BOOL)isReload if (isReload == YES || self.loadAllItemsImmediately == YES || hasLoadAnItem == NO) { [self generateItems:isReload]; } else { - [self performSelector:@selector(generateItems:) withObject:@(NO) afterDelay:0.0000001]; + [self performSelector:@selector(generateItems:) + withObject:@(NO) + afterDelay:0.0000001 + inModes:@[NSRunLoopCommonModes]]; } } } From 64644181582a236c510d2a7dd7f3a364d70a8eac Mon Sep 17 00:00:00 2001 From: HarrisonXi Date: Tue, 27 Mar 2018 17:21:39 +0800 Subject: [PATCH 30/30] fix didEnterTimes logic --- LazyScrollView/TMLazyScrollView.m | 50 ++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/LazyScrollView/TMLazyScrollView.m b/LazyScrollView/TMLazyScrollView.m index 8da2d19..18edc89 100644 --- a/LazyScrollView/TMLazyScrollView.m +++ b/LazyScrollView/TMLazyScrollView.m @@ -34,13 +34,16 @@ @interface TMLazyScrollView () { // Store muiID of items which need to be reloaded. NSMutableSet *_needReloadingMuiIDs; - // Store muiID of items which should be visible. - NSMutableSet *_newVisibleMuiIDs; - // Record current muiID of reloading item. // Will be used for dequeueReusableItem methods. NSString *_currentReloadingMuiID; + // Store muiID of items which should be visible. + NSMutableSet *_newVisibleMuiIDs; + + // Store muiID of items which are visible last time. + NSSet *_lastInScreenVisibleMuiIDs; + // Store the enter screen times of items. NSMutableDictionary *_enterTimesDict; @@ -232,6 +235,7 @@ - (void)generateItems:(BOOL)isReload if (isVisible == NO || needReload == YES) { if (self.dataSource) { hasLoadAnItem = YES; + // If you call dequeue method in your dataSource, the currentReloadingMuiID // will be used for searching the best-matched reusable view. if (isVisible == YES) { @@ -256,13 +260,6 @@ - (void)generateItems:(BOOL)isReload // Add item view to visibleItems. if (isVisible == NO) { [_visibleItems addObject:itemView]; - // Call didEnterWithTimes. - if ([itemView respondsToSelector:@selector(mui_didEnterWithTimes:)]) { - NSInteger times = [_enterTimesDict tm_integerForKey:itemView.muiID]; - times++; - [_enterTimesDict tm_safeSetObject:@(times) forKey:itemView.muiID]; - [(UIView *)itemView mui_didEnterWithTimes:times]; - } } } @@ -270,13 +267,31 @@ - (void)generateItems:(BOOL)isReload } } + // Call didEnterWithTimes. + // didEnterWithTimes will only be called when item view enter the in screen + // visible area, so we have to write the logic at here. + if ([_lastInScreenVisibleMuiIDs containsObject:muiID] == NO + && [_inScreenVisibleMuiIDs containsObject:muiID] == YES) { + for (UIView *itemView in _visibleItems) { + if ([itemView.muiID isEqualToString:muiID]) { + if ([itemView respondsToSelector:@selector(mui_didEnterWithTimes:)]) { + NSInteger times = [_enterTimesDict tm_integerForKey:itemView.muiID]; + times++; + [_enterTimesDict tm_safeSetObject:@(times) forKey:itemView.muiID]; + [(UIView *)itemView mui_didEnterWithTimes:times]; + } + break; + } + } + } + [_newVisibleMuiIDs removeObject:muiID]; if (_newVisibleMuiIDs.count > 0) { if (isReload == YES || self.loadAllItemsImmediately == YES || hasLoadAnItem == NO) { [self generateItems:isReload]; } else { [self performSelector:@selector(generateItems:) - withObject:@(NO) + withObject:@(isReload) afterDelay:0.0000001 inModes:@[NSRunLoopCommonModes]]; } @@ -296,18 +311,19 @@ - (void)assembleSubviews:(BOOL)isReload minY:(CGFloat)minY maxY:(CGFloat)maxY // Recycle invisible item views. [self recycleItems:isReload newVisibleMuiIDs:newVisibleMuiIDs]; - // Generate or reload visible item views. - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(generateItems:) object:@(NO)]; - _newVisibleMuiIDs = [newVisibleMuiIDs mutableCopy]; - [self generateItems:isReload]; - // Calculate the inScreenVisibleModels. + _lastInScreenVisibleMuiIDs = [_inScreenVisibleMuiIDs copy]; [_inScreenVisibleMuiIDs removeAllObjects]; for (TMLazyItemModel *itemModel in newVisibleModels) { if (itemModel.top < maxY && itemModel.bottom > minY) { [_inScreenVisibleMuiIDs addObject:itemModel.muiID]; } } + + // Generate or reload visible item views. + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(generateItems:) object:@(NO)]; + _newVisibleMuiIDs = [newVisibleMuiIDs mutableCopy]; + [self generateItems:isReload]; } #pragma mark Reload @@ -353,7 +369,7 @@ - (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier muiID:(NSSt if (_currentReloadingMuiID) { for (UIView *item in _visibleItems) { if ([item.muiID isEqualToString:_currentReloadingMuiID] - && [item.reuseIdentifier isEqualToString:identifier]) { + && [item.reuseIdentifier isEqualToString:identifier]) { result = item; break; }