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..c857bb1 100644 --- a/LazyScroll.podspec +++ b/LazyScroll.podspec @@ -2,22 +2,18 @@ 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.prefix_header_contents = '#import ' - s.dependency 'TMUtils', '~> 1.0' + s.dependency 'TMUtils', '~> 1.0.0' end diff --git a/LazyScrollView/LazyScroll.h b/LazyScrollView/LazyScroll.h new file mode 100644 index 0000000..01879ad --- /dev/null +++ b/LazyScrollView/LazyScroll.h @@ -0,0 +1,18 @@ +// +// LazyScroll.h +// LazyScrollView +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#ifndef LazyScroll_h +#define LazyScroll_h + +#import "TMLazyItemViewProtocol.h" +#import "TMLazyItemModel.h" +#import "TMLazyReusePool.h" +#import "TMLazyModelBucket.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..cc83ee9 --- /dev/null +++ b/LazyScrollView/TMLazyItemModel.h @@ -0,0 +1,30 @@ +// +// TMLazyItemModel.h +// LazyScrollView +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import +#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 new file mode 100644 index 0000000..9a169c4 --- /dev/null +++ b/LazyScrollView/TMLazyItemViewProtocol.h @@ -0,0 +1,42 @@ +// +// TMLazyItemViewProtocol.h +// LazyScrollView +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +/** + If the item view in LazyScrollView implements this protocol, it + can receive specified event callback in LazyScrollView's lifecycle. + */ +@protocol TMLazyItemViewProtocol + +@optional +/** + Will be called if the item view is dequeued in + 'dequeueReusableItemWithIdentifier:' method. + It is similar with 'prepareForReuse' method of UITableViewCell. + */ +- (void)mui_prepareForReuse; +/** + 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; +/** + 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/TMLazyModelBucket.h b/LazyScrollView/TMLazyModelBucket.h new file mode 100644 index 0000000..2c8c6b5 --- /dev/null +++ b/LazyScrollView/TMLazyModelBucket.h @@ -0,0 +1,31 @@ +// +// 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)addModels:(NSSet *)itemModels; +- (void)removeModel:(TMLazyItemModel *)itemModel; +- (void)removeModels:(NSSet *)itemModels; +- (void)reloadModel:(TMLazyItemModel *)itemModel; +- (void)reloadModels:(NSSet *)itemModels; +- (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..02473f3 --- /dev/null +++ b/LazyScrollView/TMLazyModelBucket.m @@ -0,0 +1,111 @@ +// +// 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)addModels:(NSSet *)itemModels +{ + if (itemModels) { + for (TMLazyItemModel *itemModel in itemModels) { + [self addModel:itemModel]; + } + } +} + +- (void)removeModel:(TMLazyItemModel *)itemModel +{ + if (itemModel) { + for (NSMutableSet *bucket in _buckets) { + [bucket removeObject:itemModel]; + } + } +} + +- (void)removeModels:(NSSet *)itemModels +{ + if (itemModels) { + for (NSMutableSet *bucket in _buckets) { + [bucket minusSet:itemModels]; + } + } +} + +- (void)reloadModel:(TMLazyItemModel *)itemModel +{ + [self removeModel:itemModel]; + [self addModel:itemModel]; +} + +- (void)reloadModels:(NSSet *)itemModels +{ + [self removeModels:itemModels]; + [self addModels:itemModels]; +} + +- (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/TMLazyReusePool.h b/LazyScrollView/TMLazyReusePool.h new file mode 100644 index 0000000..c80897d --- /dev/null +++ b/LazyScrollView/TMLazyReusePool.h @@ -0,0 +1,18 @@ +// +// 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; +- (NSSet *)allItemViews; + +@end diff --git a/LazyScrollView/TMLazyReusePool.m b/LazyScrollView/TMLazyReusePool.m new file mode 100644 index 0000000..1926420 --- /dev/null +++ b/LazyScrollView/TMLazyReusePool.m @@ -0,0 +1,85 @@ +// +// 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 || reuseIdentifier.length == 0 || 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 || reuseIdentifier.length == 0) { + return nil; + } + UIView *result = nil; + NSMutableSet *reuseSet = [_reuseDict tm_safeObjectForKey:reuseIdentifier]; + if (reuseSet && reuseSet.count > 0) { + if (!muiID) { + result = [reuseSet anyObject]; + } else { + for (UIView *itemView in reuseSet) { + if ([itemView.muiID isEqualToString:muiID]) { + result = itemView; + break; + } + } + if (!result) { + result = [reuseSet anyObject]; + } + } + [reuseSet removeObject:result]; + } + return result; +} + +- (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 new file mode 100644 index 0000000..8175391 --- /dev/null +++ b/LazyScrollView/TMLazyScrollView.h @@ -0,0 +1,137 @@ +// +// TMLazyScrollView.h +// LazyScrollView +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import +#import "TMLazyItemModel.h" +#import "UIView+TMLazyScrollView.h" + +@class TMLazyReusePool; +@class TMLazyScrollView; + +@protocol TMLazyScrollViewDataSource + +@required + +/** + Similar with 'tableView:numberOfRowsInSection:' of UITableView. + */ +- (NSUInteger)numberOfItemsInScrollView:(nonnull TMLazyScrollView *)scrollView; + +/** + Similar with 'tableView:heightForRowAtIndexPath:' of UITableView. + Manager the correct muiID of item views will bring a higher performance. + */ +- (nonnull TMLazyItemModel *)scrollView:(nonnull TMLazyScrollView *)scrollView + itemModelAtIndex:(NSUInteger)index; + +/** + 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 + +@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: + 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; + +/** + 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; + +/** + 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. + */ +@property (nonatomic, strong, readonly, nonnull) NSSet *visibleItems; + +/** + Item views which is in the screen visible area. + It is a sub set of "visibleItems". + */ +@property (nonatomic, strong, readonly, nonnull) NSSet *inScreenVisibleItems; + +- (void)reloadData; +- (void)loadMoreData; + +/** + Get reuseable item view by reuseIdentifier. + */ +- (nullable UIView *)dequeueReusableItemWithIdentifier:(nonnull NSString *)identifier; +/** + Get reuseable item view by reuseIdentifier and muiID. + MuiID has higher priority. + */ +- (nullable UIView *)dequeueReusableItemWithIdentifier:(nonnull NSString *)identifier + muiID:(nullable NSString *)muiID; + +/** + 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 '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 new file mode 100644 index 0000000..18edc89 --- /dev/null +++ b/LazyScrollView/TMLazyScrollView.m @@ -0,0 +1,490 @@ +// +// TMLazyScrollView.m +// LazyScrollView +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import "TMLazyScrollView.h" +#import +#import "TMLazyItemViewProtocol.h" +#import "TMLazyReusePool.h" +#import "TMLazyModelBucket.h" + +#define LazyBufferHeight 20 +#define LazyBucketHeight 400 +void * const LazyObserverContext = "LazyObserverContext"; + +@interface TMLazyOuterScrollViewObserver: NSObject + +@property (nonatomic, weak) TMLazyScrollView *lazyScrollView; + +@end + +//**************************************************************** + +@interface TMLazyScrollView () { + NSMutableSet *_visibleItems; + NSMutableSet *_inScreenVisibleMuiIDs; + + // Store item models. + TMLazyModelBucket *_modelBucket; + NSInteger _itemCount; + + // Store muiID of items which need to be reloaded. + NSMutableSet *_needReloadingMuiIDs; + + // 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; + + // Record contentOffset of scrollView that used for calculating + // views to show last time. + CGPoint _lastContentOffset; +} + +@property (nonatomic, strong) TMLazyOuterScrollViewObserver *outerScrollViewObserver; + +- (void)outerScrollViewDidScroll; + +@end + +@implementation TMLazyScrollView + +#pragma mark Getter & Setter + +- (NSSet *)inScreenVisibleItems +{ + NSMutableSet * inScreenVisibleItems = [NSMutableSet set]; + for (UIView *view in _visibleItems) { + if ([_inScreenVisibleMuiIDs containsObject:view.muiID]) { + [inScreenVisibleItems addObject:view]; + } + } + return [inScreenVisibleItems copy]; +} + +- (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) { + _outerScrollViewObserver = [TMLazyOuterScrollViewObserver new]; + _outerScrollViewObserver.lazyScrollView = self; + } + return _outerScrollViewObserver; +} + +-(void)setOuterScrollView:(UIScrollView *)outerScrollView +{ + if (_outerScrollView != outerScrollView) { + if (_outerScrollView) { + [_outerScrollView removeObserver:self.outerScrollViewObserver forKeyPath:@"contentOffset" context:LazyObserverContext]; + } + if (outerScrollView) { + [outerScrollView addObserver:self.outerScrollViewObserver forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:LazyObserverContext]; + } + _outerScrollView = outerScrollView; + } +} + +#pragma mark Lifecycle + +- (id)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + self.clipsToBounds = YES; + 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]; + } + return self; +} + +- (void)dealloc +{ + self.dataSource = nil; + self.delegate = nil; + self.outerScrollView = nil; +} + +#pragma mark ScrollEvent + +- (void)setContentOffset:(CGPoint)contentOffset +{ + [super setContentOffset:contentOffset]; + if (LazyBufferHeight < ABS(contentOffset.y - _lastContentOffset.y)) { + _lastContentOffset = self.contentOffset; + [self assembleSubviews:NO]; + } +} + +- (void)outerScrollViewDidScroll +{ + 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 frame = [self.superview convertRect:self.frame toView:self.outerScrollView]; + CGRect visibleArea = CGRectIntersection(self.outerScrollView.bounds, frame); + if (visibleArea.size.height > 0) { + CGFloat offsetY = CGRectGetMinY(frame); + CGFloat minY = CGRectGetMinY(visibleArea) - offsetY; + CGFloat maxY = CGRectGetMaxY(visibleArea) - offsetY; + [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:isReload minY:minY maxY:maxY]; + } +} + +- (void)recycleItems:(BOOL)isReload newVisibleMuiIDs:(NSSet *)newVisibleMuiIDs +{ + 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]; + } + } +} + +- (void)generateItems:(BOOL)isReload +{ + if (_newVisibleMuiIDs == nil || _newVisibleMuiIDs.count == 0) { + return; + } + + 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]; + } + // Show the item view. + itemView.muiID = muiID; + itemView.hidden = NO; + if (self.autoAddSubview) { + if (itemView.superview != self) { + [self addSubview:itemView]; + } + } + // Add item view to visibleItems. + if (isVisible == NO) { + [_visibleItems addObject:itemView]; + } + } + + [_needReloadingMuiIDs removeObject:muiID]; + } + } + + // 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:@(isReload) + afterDelay:0.0000001 + inModes:@[NSRunLoopCommonModes]]; + } + } +} + +- (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]; + + // 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 + +- (void)storeItemModelsFromIndex:(NSInteger)startIndex +{ + if (startIndex == 0) { + _itemCount = 0; + [_modelBucket clear]; + } + if (self.dataSource) { + _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]; + } + [_modelBucket addModel:itemModel]; + } + } +} + +- (void)reloadData +{ + [self storeItemModelsFromIndex:0]; + [self assembleSubviews:YES]; +} + +- (void)loadMoreData +{ + [self storeItemModelsFromIndex:_itemCount]; + [self assembleSubviews:NO]; +} + +- (UIView *)dequeueReusableItemWithIdentifier:(NSString *)identifier +{ + return [self dequeueReusableItemWithIdentifier:identifier muiID:nil]; +} + +- (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 (result == nil) { + result = [self.reusePool dequeueItemViewForReuseIdentifier:identifier andMuiID:muiID]; + } + if (result) { + if (self.autoClearGestures) { + result.gestureRecognizers = nil; + } + if ([result respondsToSelector:@selector(mui_prepareForReuse)]) { + [(UIView *)result mui_prepareForReuse]; + } + } + return result; +} + +#pragma mark Clear & Reset + +- (void)clearVisibleItems:(BOOL)enableRecycle +{ + if (enableRecycle) { + for (UIView *itemView in _visibleItems) { + 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:YES]; +} + +- (void)clearReuseItems +{ + for (UIView *itemView in [self.reusePool allItemViews]) { + [itemView removeFromSuperview]; + } + [self.reusePool clear]; +} + +- (void)cleanRecycledView +{ + [self clearReuseItems]; +} + +- (void)resetItemsEnterTimes +{ + [_enterTimesDict removeAllObjects]; +} + +- (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 +{ + for (UIView *itemView in _visibleItems) { + if ([itemView.muiID isEqualToString:muiID]) { + return YES; + } + } + return NO; +} + +- (BOOL)isDataSourceValid:(id)dataSource +{ + return dataSource + && [dataSource respondsToSelector:@selector(numberOfItemsInScrollView:)] + && [dataSource respondsToSelector:@selector(scrollView:itemModelAtIndex:)] + && [dataSource respondsToSelector:@selector(scrollView:itemByMuiID:)]; +} + +@end + +//**************************************************************** + +@implementation TMLazyOuterScrollViewObserver + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +{ + if (context == LazyObserverContext && [keyPath isEqualToString:@"contentOffset"] && _lazyScrollView) { + [_lazyScrollView outerScrollViewDidScroll]; + } +} + +@end diff --git a/LazyScrollView/TMMuiLazyScrollView.h b/LazyScrollView/TMMuiLazyScrollView.h deleted file mode 100644 index dd025f0..0000000 --- a/LazyScrollView/TMMuiLazyScrollView.h +++ /dev/null @@ -1,96 +0,0 @@ -// -// TMMuiLazyScrollView.h -// LazyScrollView -// -// Copyright (c) 2015-2017 Alibaba. All rights reserved. -// - -#import -#import "TMMuiLazyScrollViewCellProtocol.h" -#import "TMMuiRectModel.h" - -@class TMMuiLazyScrollView; - - -/** - A UIView category required by LazyScrollView. - */ -@interface UIView (TMMuiLazyScrollView) - -// 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 - -//**************************************************************** - -/** - This protocol represents the data model object. - */ -@protocol TMMuiLazyScrollViewDataSource - -@required - -// 0 by default. -- (NSUInteger)numberOfItemInScrollView:(nonnull TMMuiLazyScrollView *)scrollView; -// Return the view model by spcial index. -- (nonnull TMMuiRectModel *)scrollView:(nonnull TMMuiLazyScrollView *)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 - itemByMuiID:(nonnull NSString *)muiID; - -@end -//**************************************************************** - - -@interface TMMuiLazyScrollView : UIScrollView - -// 注意,修改 delegate 属性后需要将 scrollViewDidScroll: 事件转发回给 TangramView - -@property (nonatomic, weak, nullable) id dataSource; - -@property (nonatomic, weak, nullable) id forwardingDelegate; - -// Default value is NO. -@property (nonatomic, assign) BOOL autoAddSubview; - -// Items which has been added to LazyScrollView. -@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; - - -// 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. -- (nullable UIView *)dequeueReusableItemWithIdentifier:(nonnull NSString *)identifier; -// 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; - -@end - -//**************************************************************** - -@interface TMMuiLazyScrollViewObserver: NSObject -@property (nonatomic, weak, nullable) TMMuiLazyScrollView *lazyScrollView; -@end diff --git a/LazyScrollView/TMMuiLazyScrollView.m b/LazyScrollView/TMMuiLazyScrollView.m deleted file mode 100644 index 9570bc1..0000000 --- a/LazyScrollView/TMMuiLazyScrollView.m +++ /dev/null @@ -1,667 +0,0 @@ -// -// TMMuiLazyScrollView.m -// LazyScrollView -// -// Copyright (c) 2015-2017 Alibaba. All rights reserved. -// - -#import "TMMuiLazyScrollView.h" -#import -#import - -#define RenderBufferWindow 20.f - - -@implementation UIView(TMMuiLazyScrollView) - -- (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 TMMuiLazyScrollView() { - NSMutableSet *_visibleItems; - NSMutableSet *_inScreenVisibleItems; -} - -// 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]; -} - -- (NSSet *)visibleItems -{ - return [_visibleItems copy]; -} - -- (void)setFrame:(CGRect)frame -{ - if (!CGRectEqualToRect(frame, self.frame)) { - [super setFrame:frame]; - } -} - --(void)setOuterScrollView:(UIScrollView *)outerScrollView -{ - _outerScrollView = outerScrollView; - if (self.outerScrollViewObserver == nil) { - self.outerScrollViewObserver = [[TMMuiLazyScrollViewObserver alloc]init]; - self.outerScrollViewObserver.lazyScrollView = self; - } - - @try { - [outerScrollView removeObserver:self.outerScrollViewObserver forKeyPath:@"contentOffset"]; - } - @catch (NSException * __unused exception) {} - [outerScrollView addObserver:self.outerScrollViewObserver forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; -} - -#pragma mark - Lifecycle - -- (id)initWithFrame:(CGRect)frame -{ - if (self = [super initWithFrame:frame]) { - self.clipsToBounds = YES; - self.autoresizesSubviews = NO; - self.showsHorizontalScrollIndicator = NO; - self.showsVerticalScrollIndicator = NO; - _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]; - self.delegate = self; - } - return self; -} - -- (void)dealloc -{ - _dataSource = nil; - self.delegate = nil; - _forwardingDelegate = nil; - if (_outerScrollView) { - @try { - [_outerScrollView removeObserver:_outerScrollViewObserver forKeyPath:@"contentOffset"]; - } - @catch (NSException *exception) { - - } - } -} - -#pragma mark - ScrollViewDelegate - -- (void)didScroll -{ - // 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; - if (buffer < ABS(currentY - self.lastScrollOffset.y)) { - self.lastScrollOffset = self.contentOffset; - [self assembleSubviews]; - [self findViewsInVisibleRect]; - } - -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - [self didScroll]; - - if (self.forwardingDelegate && - [self.forwardingDelegate conformsToProtocol:@protocol(UIScrollViewDelegate)] && - [self.forwardingDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) { - [self.forwardingDelegate scrollViewDidScroll:self]; - } -} - -- (id)forwardingTargetForSelector:(SEL)aSelector -{ - if (self.forwardingDelegate) { - struct objc_method_description md = protocol_getMethodDescription(@protocol(UIScrollViewDelegate), aSelector, NO, YES); - if (NULL != md.name) { - return self.forwardingDelegate; - } - } - return [super forwardingTargetForSelector:aSelector]; -} - -- (BOOL)respondsToSelector:(SEL)aSelector -{ - BOOL result = [super respondsToSelector:aSelector]; - if (NO == result && self.forwardingDelegate) { - struct objc_method_description md = protocol_getMethodDescription(@protocol(UIScrollViewDelegate), aSelector, NO, YES); - if (NULL != md.name) { - result = [self.forwardingDelegate respondsToSelector:aSelector]; - } - } - return result; -} - -#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 -{ - NSInteger min = 0 ; - 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]; - // For top - if(fromTop) { - CGFloat itemTop = CGRectGetMinY(rect); - if (itemTop <= baseLine) { - CGRect nextItemRect = [(TMMuiRectModel *)[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 = [(TMMuiRectModel *)[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:self.modelsSortedByBottom baseLine:startY isFromTop:NO]; - [self.firstSet removeAllObjects]; - for (NSUInteger i = 0; i <= endBottomIndex; i++) { - TMMuiRectModel *model = [self.modelsSortedByBottom tm_safeObjectAtIndex:i]; - if (model != nil) { - [self.firstSet addObject:model.muiID]; - } - } - - NSUInteger endTopIndex = [self binarySearchForIndex:self.modelsSortedByTop baseLine:endY isFromTop:YES]; - [self.secondSet removeAllObjects]; - for (NSInteger i = 0; i <= endTopIndex; i++) { - TMMuiRectModel *model = [self.modelsSortedByTop tm_safeObjectAtIndex:i]; - if (model != nil) { - [self.secondSet addObject:model.muiID]; - } - } - - [self.firstSet intersectSet:self.secondSet]; - return [self.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]; - } - - [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]; - if (rectmodel.muiID.length == 0) { - rectmodel.muiID = [NSString stringWithFormat:@"%lu", (unsigned long)i]; - } - } - [self.itemsFrames tm_safeAddObject:rectmodel]; - } - - self.modelsSortedByTop = [self.itemsFrames sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { - CGRect rect1 = [(TMMuiRectModel *) obj1 absRect]; - CGRect rect2 = [(TMMuiRectModel *) obj2 absRect]; - if (rect1.origin.y < rect2.origin.y) { - return NSOrderedAscending; - } else if (rect1.origin.y > rect2.origin.y) { - return NSOrderedDescending; - } else { - return NSOrderedSame; - } - }]; - - self.modelsSortedByBottom = [self.itemsFrames sortedArrayUsingComparator:^NSComparisonResult(id obj1 ,id obj2) { - CGRect rect1 = [(TMMuiRectModel *) obj1 absRect]; - CGRect rect2 = [(TMMuiRectModel *) 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 = [self.muiIDOfVisibleViews mutableCopy]; - [itemViewSet minusSet:self.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; - } - NSNumber *showTimes = [NSNumber numberWithUnsignedInteger:times]; - [self.enterDict tm_safeSetObject:showTimes forKey:view.muiID]; - [(UIView *)view mui_didEnterWithTimes:times]; - } - } - } - self.lastVisibleMuiID = [self.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 ; - //maxY 计算的逻辑,需要修改,增加的height,需要计算的更加明确 - CGFloat maxY = self.outerScrollView.contentOffset.y + self.outerScrollView.frame.size.height - pointInScrollView.y + RenderBufferWindow/2; - if (maxY > 0) { - [self assembleSubviewsForReload:NO minY:minY maxY:maxY]; - } - - } - else - { - CGRect visibleBounds = self.bounds; - CGFloat minY = CGRectGetMinY(visibleBounds) - RenderBufferWindow; - CGFloat maxY = CGRectGetMaxY(visibleBounds) + RenderBufferWindow; - [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 (self.outerScrollView) { - self.muiIDOfVisibleViews = [self showingItemIndexSetFrom:minY to:maxY]; - } - else{ - self.muiIDOfVisibleViews = [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. - 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]; - } else if(isReload && view.muiID) { - // Need to reload unreusable views. - [self.shouldReloadItems addObject:view.muiID]; - } - } else if (isReload && view.muiID) { - [self.shouldReloadItems addObject:view.muiID]; - } - - } - [_visibleItems minusSet:recycledItems]; - [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:)]) { - // 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; - } - UIView *viewToShow = [self.dataSource scrollView:self itemByMuiID:muiID]; - self.currentVisibleItemMuiID = nil; - // Call afterGetView. - if ([viewToShow conformsToProtocol:@protocol(TMMuiLazyScrollViewCellProtocol)] && - [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 (self.autoAddSubview) { - if (viewToShow.superview != self) { - if (viewToShow.superview) { - [viewToShow removeFromSuperview]; - } - [self addSubview:viewToShow]; - } - } - } - } - [self.shouldReloadItems 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]; - } - } - } -} - -// Get NSSet accroding to reuse identifier. -- (NSMutableSet *)recycledIdentifierSet:(NSString *)reuseIdentifier; -{ - if (reuseIdentifier.length == 0) { - return nil; - } - - NSMutableSet *result = [self.recycledIdentifierItemsDic tm_safeObjectForKey:reuseIdentifier]; - if (result == nil) { - result = [[NSMutableSet alloc] init]; - [self.recycledIdentifierItemsDic setObject:result forKey:reuseIdentifier]; - } - return result; -} - -// Reloads everything and redisplays visible views. -- (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 (maxY > 0) { - [self assembleSubviewsForReload:YES minY:minY maxY:maxY]; - } - } - else{ - CGRect visibleBounds = self.bounds; - // 上下增加200像素的缓冲区 - CGFloat minY = CGRectGetMinY(visibleBounds) - RenderBufferWindow; - CGFloat maxY = CGRectGetMaxY(visibleBounds) + RenderBufferWindow; - [self assembleSubviewsForReload:YES minY:minY maxY:maxY]; - } - [self findViewsInVisibleRect]; - } - -} - -// 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 -{ - 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 *view = nil; - - if (self.currentVisibleItemMuiID) { - NSSet *visibles = _visibleItems; - for (UIView *v in visibles) { - if ([v.muiID isEqualToString:self.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]]; - 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]; - } - [recycledIdentifierSet removeObject:view]; - view.gestureRecognizers = nil; - } else { - view = nil; - } - } - - 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) { - [self.recycledMuiIDItemsDic removeObjectForKey:view.muiID]; - } - [recycledIdentifierSet removeObject:view]; - // Then remove all gesture recognizers of it. - view.gestureRecognizers = nil; - } else { - view = nil; - } - } - - if ([view conformsToProtocol:@protocol(TMMuiLazyScrollViewCellProtocol)] && [view respondsToSelector:@selector(mui_prepareForReuse)]) { - [(UIView *)view mui_prepareForReuse]; - } - return view; -} - -//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; -} - -- (void)resetViewEnterTimes -{ - [self.enterDict removeAllObjects]; - self.lastVisibleMuiID = nil; -} - -@end - -@implementation TMMuiLazyScrollViewObserver - -- (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 = RenderBufferWindow / 2; - if (buffer < ABS(newPoint.y - self.lazyScrollView.lastScrollOffset.y)) { - self.lazyScrollView.lastScrollOffset = newPoint; - [self.lazyScrollView assembleSubviews]; - [self.lazyScrollView findViewsInVisibleRect]; - } - } -} - -@end - - diff --git a/LazyScrollView/TMMuiLazyScrollViewCellProtocol.h b/LazyScrollView/TMMuiLazyScrollViewCellProtocol.h deleted file mode 100644 index 3404304..0000000 --- a/LazyScrollView/TMMuiLazyScrollViewCellProtocol.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// TMMuiLazyScrollViewCellProtocol.h -// LazyScrollView -// -// Copyright (c) 2017 Alibaba. All rights reserved. -// - -/** - If the view in LazyScrollView implement this protocol, - view can do something in its lifecycle. - */ -@protocol TMMuiLazyScrollViewCellProtocol - -@optional -// Will be called if call dequeueReusableItemWithIdentifier -// to get a reuseable view, the same as "prepareForReuse" -// in 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). -- (void)mui_afterGetView; -// When the view is out of screen, this method will be -// called. -- (void)mui_didLeave; - -@end diff --git a/LazyScrollView/TMMuiRectModel.h b/LazyScrollView/TMMuiRectModel.h deleted file mode 100644 index 200e020..0000000 --- a/LazyScrollView/TMMuiRectModel.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// TMMuiRectModel.h -// LazyScrollView -// -// Copyright (c) 2017 Alibaba. All rights reserved. -// - -#import - -/** - It is a view model that holding information of view. - At least holding absRect and muiID. - */ -@interface TMMuiRectModel : 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/TMMuiRectModel.m b/LazyScrollView/TMMuiRectModel.m deleted file mode 100644 index 9023d27..0000000 --- a/LazyScrollView/TMMuiRectModel.m +++ /dev/null @@ -1,12 +0,0 @@ -// -// TMMuiRectModel.m -// LazyScrollView -// -// Copyright (c) 2017 Alibaba. All rights reserved. -// - -#import "TMMuiRectModel.h" - -@implementation TMMuiRectModel - -@end 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.xcodeproj/project.pbxproj b/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj index d098f34..4bbc0db 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj +++ b/LazyScrollViewDemo/LazyScrollViewDemo.xcodeproj/project.pbxproj @@ -3,102 +3,111 @@ 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 */; }; + 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 */; }; + 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 */; }; /* 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 = ""; }; + 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 = ""; }; + 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 = ""; }; + 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 = ""; }; + 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 */ /* 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 */, + 92F01C4520493C36000983CA /* MainViewController.h */, + 92F01C4620493C36000983CA /* MainViewController.m */, + 927CAE3C2046B37700BD3B19 /* ReuseViewController.h */, + 927CAE3D2046B37700BD3B19 /* ReuseViewController.m */, + 92F01C4820493D9C000983CA /* OuterViewController.h */, + 92F01C4920493D9C000983CA /* OuterViewController.m */, + 929CC56820592B1400C2870B /* MoreViewController.h */, + 929CC56920592B1400C2870B /* MoreViewController.m */, + 9247FDBB205A9310004FB14E /* AsyncViewController.h */, + 9247FDBC205A9310004FB14E /* AsyncViewController.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 +115,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 +132,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 +193,66 @@ ); 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 /* ReuseViewController.m in Sources */, + 929CC56A20592B1400C2870B /* MoreViewController.m in Sources */, + 927CAE492046B37700BD3B19 /* main.m in Sources */, + 92F01C4A20493D9C000983CA /* OuterViewController.m in Sources */, + 927CAE3B2046B37700BD3B19 /* AppDelegate.m in Sources */, + 92F01C4720493C36000983CA /* MainViewController.m in Sources */, + 9247FDBD205A9311004FB14E /* AsyncViewController.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 +260,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 +286,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 +315,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 +341,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 +364,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.h b/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.h index 2f24573..d071643 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 @@ -11,6 +11,5 @@ @property (strong, nonatomic) UIWindow *window; - @end diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.m b/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.m index 756607b..b70811f 100644 --- a/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.m +++ b/LazyScrollViewDemo/LazyScrollViewDemo/AppDelegate.m @@ -2,49 +2,21 @@ // AppDelegate.m // LazyScrollViewDemo // -// Copyright (c) 2017 Alibaba. All rights reserved. +// Copyright (c) 2015-2018 Alibaba. All rights reserved. // #import "AppDelegate.h" - -@interface AppDelegate () - -@end +#import "MainViewController.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:[MainViewController 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/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/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/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..26573ba --- /dev/null +++ b/LazyScrollViewDemo/LazyScrollViewDemo/MainViewController.m @@ -0,0 +1,59 @@ +// +// MainViewController.m +// LazyScrollViewDemo +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import "MainViewController.h" +#import "OuterViewController.h" +#import "MoreViewController.h" + +@interface MainViewController () + +@property (nonatomic, strong) NSArray *demoArray; + +@end + +@implementation MainViewController + +- (instancetype)init +{ + if (self = [super init]) { + self.title = @"LazyScrollDemo"; + self.demoArray = @[@"Reuse", @"OuterScrollView", @"LoadMore", @"Async"]; + } + 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 if ([demoName isEqualToString:@"LoadMore"]) { + vc = [MoreViewController new]; + } else { + Class demoVcClass = NSClassFromString([demoName stringByAppendingString:@"ViewController"]); + vc = [demoVcClass new]; + } + [self.navigationController pushViewController:vc animated:YES]; +} + +@end 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..a38ac37 --- /dev/null +++ b/LazyScrollViewDemo/LazyScrollViewDemo/MoreViewController.m @@ -0,0 +1,112 @@ +// +// 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 loadMoreData]; +} + +#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]; + 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.h b/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.h new file mode 100644 index 0000000..cc41b30 --- /dev/null +++ b/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.h @@ -0,0 +1,12 @@ +// +// OuterViewController.h +// LazyScrollViewDemo +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import + +@interface OuterViewController : UITableViewController + +@end diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m new file mode 100644 index 0000000..7db7c1b --- /dev/null +++ b/LazyScrollViewDemo/LazyScrollViewDemo/OuterViewController.m @@ -0,0 +1,135 @@ +// +// OuterViewController.m +// LazyScrollViewDemo +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import "OuterViewController.h" +#import +#import + +@interface OuterViewController () { + NSMutableArray * _rectArray; + NSMutableArray * _colorArray; + TMLazyScrollView * _scrollView; +} + +@end + +@implementation OuterViewController + +- (instancetype)init +{ + if (self = [super init]) { + self.title = @"Outer"; + } + return self; +} + +- (void)dealloc +{ + // VERY IMPORTANT! + _scrollView.outerScrollView = nil; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + _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]; + + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Reload" style:UIBarButtonItemStylePlain target:self action:@selector(reloadAction)]; +} + +- (void)reloadAction +{ + [_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; +} + +- (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]; + } + view.frame = [(NSValue *)[_rectArray objectAtIndex:index] CGRectValue]; + 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/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/ReuseViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/ReuseViewController.m new file mode 100644 index 0000000..903203a --- /dev/null +++ b/LazyScrollViewDemo/LazyScrollViewDemo/ReuseViewController.m @@ -0,0 +1,148 @@ +// +// ReuseViewController.m +// LazyScrollViewDemo +// +// Copyright (c) 2015-2018 Alibaba. All rights reserved. +// + +#import "ReuseViewController.h" +#import +#import + +@interface LazyScrollViewCustomView : UILabel + +@property (nonatomic, assign) NSUInteger reuseTimes; + +@end + +@implementation LazyScrollViewCustomView + +- (void)mui_prepareForReuse +{ + self.reuseTimes++; +} + +@end + +//**************************************************************** + +@interface ReuseViewController () { + NSMutableArray * _rectArray; + NSMutableArray * _colorArray; +} + +@end + +@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] 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. + _rectArray = [[NSMutableArray alloc] init]; + CGFloat maxY = 0, currentY = 50; + CGFloat viewWidth = CGRectGetWidth(self.view.bounds); + // 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. + 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 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(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]; +} + +#pragma mark LazyScrollView + +// STEP 2: implement datasource. +- (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 +{ + // Find view that is reuseable first. + LazyScrollViewCustomView *label = (LazyScrollViewCustomView *)[scrollView dequeueReusableItemWithIdentifier:@"testView"]; + NSInteger index = [muiID integerValue]; + if (!label) { + NSLog(@"create a new label"); + label = [LazyScrollViewCustomView new]; + label.textAlignment = NSTextAlignmentCenter; + label.numberOfLines = 0; + label.reuseIdentifier = @"testView"; + label.backgroundColor = [_colorArray tm_safeObjectAtIndex:index]; + } + 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 { + label.text = [NSString stringWithFormat:@"%zd", index]; + } + return label; +} + +#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/ViewController.h b/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.h deleted file mode 100644 index 793ce05..0000000 --- a/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// ViewController.h -// LazyScrollViewDemo -// -// Copyright (c) 2017 Alibaba. All rights reserved. -// - -#import - -@interface ViewController : UIViewController - - -@end - diff --git a/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.m b/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.m deleted file mode 100644 index 67db586..0000000 --- a/LazyScrollViewDemo/LazyScrollViewDemo/ViewController.m +++ /dev/null @@ -1,126 +0,0 @@ -// -// ViewController.m -// LazyScrollViewDemo -// -// Copyright (c) 2017 Alibaba. All rights reserved. -// - -#import "ViewController.h" -#import - - -@interface LazyScrollViewCustomView : UILabel - -@property (nonatomic, assign) NSUInteger reuseTimes; - -@end - -@implementation LazyScrollViewCustomView - -- (void)mui_prepareForReuse -{ - self.reuseTimes++; -} - -@end - - -@interface ViewController () { - NSMutableArray * rectArray; -} - -@end - -@implementation ViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - - // STEP 1 . Create LazyScrollView - TMMuiLazyScrollView *scrollview = [[TMMuiLazyScrollView alloc] init]; - scrollview.frame = 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; - CGFloat viewWidth = CGRectGetWidth(self.view.bounds); - // Create a single column layout with 5 elements; - for (int i = 0; i < 5; i++) { - [self addRect:CGRectMake(10, i * 80 + currentY, viewWidth - 20, 80 - 3) andUpdateMaxY:&maxY]; - } - // 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]; - } - - // STEP 3 reload LazyScrollView - scrollview.contentSize = CGSizeMake(CGRectGetWidth(self.view.bounds), maxY + 10); - [scrollview reloadData]; -} - -// STEP 2 implement datasource delegate. -- (NSUInteger)numberOfItemInScrollView:(TMMuiLazyScrollView *)scrollView -{ - return rectArray.count; -} - -- (TMMuiRectModel *)scrollView:(TMMuiLazyScrollView *)scrollView rectModelAtIndex:(NSUInteger)index -{ - CGRect rect = [(NSValue *)[rectArray objectAtIndex:index] CGRectValue]; - TMMuiRectModel *rectModel = [[TMMuiRectModel alloc] init]; - rectModel.absRect = rect; - rectModel.muiID = [NSString stringWithFormat:@"%zd", index]; - return rectModel; -} - -- (UIView *)scrollView:(TMMuiLazyScrollView *)scrollView itemByMuiID:(NSString *)muiID -{ - // Find view that is reuseable first. - LazyScrollViewCustomView *label = (LazyScrollViewCustomView *)[scrollView dequeueReusableItemWithIdentifier:@"testView"]; - NSInteger index = [muiID integerValue]; - if (!label) { - NSLog(@"create a new label"); - label = [LazyScrollViewCustomView new]; - label.textAlignment = NSTextAlignmentCenter; - label.numberOfLines = 0; - label.reuseIdentifier = @"testView"; - label.backgroundColor = [self randomColor]; - } - 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 { - label.text = [NSString stringWithFormat:@"%zd", index]; - } - return label; -} - -#pragma mark - Private - -- (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]; -} - -@end 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/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/LazyScrollViewTest/LazyScrollViewTest.xcodeproj/project.pbxproj b/LazyScrollViewTest/LazyScrollViewTest.xcodeproj/project.pbxproj new file mode 100644 index 0000000..9848cc4 --- /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 /* 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 /* 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 = ""; }; +/* 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 /* ModelBucketTest.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 /* ModelBucketTest.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/ModelBucketTest.m b/LazyScrollViewTest/LazyScrollViewTest/ModelBucketTest.m new file mode 100644 index 0000000..49ba395 --- /dev/null +++ b/LazyScrollViewTest/LazyScrollViewTest/ModelBucketTest.m @@ -0,0 +1,130 @@ +// +// ModelBucketTest.m +// LazyScrollViewTest +// +// 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 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]; + + 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 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)); + 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/Podfile b/Podfile new file mode 100644 index 0000000..2153521 --- /dev/null +++ b/Podfile @@ -0,0 +1,19 @@ +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 + +target 'LazyScrollViewTest' do + project 'LazyScrollViewTest/LazyScrollViewTest.xcodeproj' + pod 'LazyScroll', :path => './' + pod 'TMUtils', :path => './' + pod 'OCHamcrest' +end + +workspace 'LazyScrollView' diff --git a/README.md b/README.md index cfcdaf1..2509696 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,7 +66,7 @@ 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/NSArray+TMSafeUtils.h b/TMUtils/NSArray+TMSafeUtils.h new file mode 100644 index 0000000..217d167 --- /dev/null +++ b/TMUtils/NSArray+TMSafeUtils.h @@ -0,0 +1,38 @@ +// +// 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; +- (void)tm_safeRemoveObject:(id)anObject; + +@end diff --git a/TMUtils/TMUtils.m b/TMUtils/NSArray+TMSafeUtils.m similarity index 53% rename from TMUtils/TMUtils.m rename to TMUtils/NSArray+TMSafeUtils.m index e1a3d48..5030804 100644 --- a/TMUtils/TMUtils.m +++ b/TMUtils/NSArray+TMSafeUtils.m @@ -1,13 +1,14 @@ // -// TMUtils.m -// LazyScrollView +// NSArray+TMSafeUtils.m +// TMUtils // -// Copyright (c) 2017 Alibaba. All rights reserved. +// Copyright (c) 2015-2018 Alibaba. All rights reserved. // -#import "TMUtils.h" +#import "NSArray+TMSafeUtils.h" +#import "NSString+TMSafeUtils.h" -@implementation NSArray (TMUtil) +@implementation NSArray (TMSafeUtils) - (id)tm_safeObjectAtIndex:(NSUInteger)index { @@ -57,127 +58,106 @@ - (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 (TMUtil) - -- (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 (TMUtil) - -- (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 +@end + +@implementation NSMutableArray (TMSafeUtils) + +- (void)tm_safeAddObject:(id)anObject { - return [self tm_safeObjectForKey:key class:[NSDictionary class]]; + if (anObject) { + [self addObject:anObject]; + } } -- (NSArray *)tm_arrayForKey:(id)key +- (void)tm_safeInsertObject:(id)anObject atIndex:(NSUInteger)index { - return [self tm_safeObjectForKey:key class:[NSArray class]]; + if (anObject && index <= self.count) { + [self insertObject:anObject atIndex:index]; + } } -- (id)tm_safeValueForKey:(NSString *)key +- (void)tm_safeReplaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject { - return [self tm_safeObjectForKey:key]; + if (anObject && index < self.count) { + [self replaceObjectAtIndex:index withObject:anObject]; + } } -- (void)tm_safeSetValue:(id)value forKey:(NSString *)key +- (void)tm_safeRemoveObjectAtIndex:(NSUInteger)index { - if (key && [key isKindOfClass:[NSString class]]) { - [self setValue:value forKey:key]; + if (index < self.count) { + [self removeObjectAtIndex:index]; } } -@end - -@implementation NSMutableDictionary (TMUtil) - -- (void)tm_safeSetObject:(id)anObject forKey:(id)key +- (void)tm_safeRemoveObject:(id)anObject { - if (key && anObject) { - [self setObject:anObject forKey:key]; + if (anObject != nil) { + [self removeObject:anObject]; } } @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 d28f688..0ae7f75 100644 --- a/TMUtils/TMUtils.h +++ b/TMUtils/TMUtils.h @@ -1,53 +1,15 @@ // // TMUtils.h -// LazyScrollView +// TMUtils // -// Copyright (c) 2017 Alibaba. All rights reserved. +// Copyright (c) 2015-2018 Alibaba. All rights reserved. // -#import -#import +#ifndef TMUtils_h +#define TMUtils_h -@interface NSArray (TMUtil) +#import "NSString+TMSafeUtils.h" +#import "NSArray+TMSafeUtils.h" +#import "NSDictionary+TMSafeUtils.h" -- (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 (TMUtil) - -- (void)tm_safeAddObject:(id)anObject; -- (void)tm_safeInsertObject:(id)anObject atIndex:(NSUInteger)index; - -@end - -@interface NSDictionary (TMUtil) - -- (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 (TMUtil) - -- (void)tm_safeSetObject:(id)anObject forKey:(id)key; - -@end +#endif /* TMUtils_h */ diff --git a/update_header.py b/update_header.py new file mode 100644 index 0000000..fd27ab0 --- /dev/null +++ b/update_header.py @@ -0,0 +1,33 @@ +# -*- 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], 'LazyScrollViewTest'), 'LazyScrollViewTest') +updateHeader(os.path.join(sys.path[0], 'TMUtils'), 'TMUtils') +print('Header updating is done.')