前几天实现轮播图,调试也费了点时间,顺便做个记录,方便以后使用。
使用pod管理代码,podfile里引入如下库:
pod 'Masonry'
pod 'SDWebImage'
轮播需要定时器,自定义定时器如下,可以防止循环引用,导致内存泄漏:
@interface LZTimer : NSObject
+(void)startWithDuration:(NSInteger)seconds andInterval:(CGFloat)interval intervalOperation:(void(^)(NSInteger))tickBlock finished:(void(^)(void))finishBlock;
+(LZTimer *)timerWithDuration:(NSInteger)seconds andInterval:(CGFloat)interval intervalOperation:(void(^) (NSInteger))tickBlock finished:(void(^)(void))finishBlock;
+(LZTimer *)timerInterval:(CGFloat)interval intervalOperation:(void(^)(void))tickBlock;
-(void)invalidate;
-(void)suspend;
-(void)resume;
@end
@interface LZTimer()
@property (nonatomic, strong) dispatch_source_t timer;
@end
@implementation LZTimer
+(void)startWithDuration:(NSInteger)seconds andInterval:(CGFloat)interval intervalOperation:(void(^)(NSInteger))tickBlock finished:(void(^)(void))finishBlock{
__block NSInteger timeout = seconds/interval;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t _ltimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_ltimer,dispatch_walltime(NULL, 0), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_ltimer, ^{
if(timeout <= 0){
dispatch_async(dispatch_get_main_queue(), ^{
if(finishBlock) finishBlock();
});
dispatch_source_cancel(_ltimer);
}else{
dispatch_async(dispatch_get_main_queue(), ^{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1];
if (tickBlock) tickBlock(timeout);
[UIView commitAnimations];
timeout--;
});
}
});
dispatch_resume(_ltimer);
}
+(LZTimer *)timerWithDuration:(NSInteger)seconds andInterval:(CGFloat)interval intervalOperation:(void(^) (NSInteger))tickBlock finished:(void(^)(void))finishBlock
{
LZTimer *tm = [[LZTimer alloc] init];
[tm startWithDuration:seconds andInterval:interval intervalOperation:tickBlock finished:finishBlock];
return tm;
}
-(void)startWithDuration:(NSInteger)seconds andInterval:(CGFloat)interval intervalOperation:(void(^) (NSInteger))tickBlock finished:(void(^)(void))finishBlock{
__block NSInteger timeout = seconds/interval;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(self.timer,dispatch_walltime(NULL, 0), interval * NSEC_PER_SEC, 0);
__weak typeof(self) weakSelf = self;
dispatch_source_set_event_handler(self.timer, ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if(timeout <= 0){
dispatch_async(dispatch_get_main_queue(), ^{
if(finishBlock) finishBlock();
});
dispatch_source_cancel(strongSelf.timer);
strongSelf.timer = nil;
}else{
dispatch_async(dispatch_get_main_queue(), ^{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1];
if (tickBlock) tickBlock(timeout);
[UIView commitAnimations];
timeout--;
});
}
});
dispatch_resume(self.timer);
}
+(LZTimer *)timerInterval:(CGFloat)interval intervalOperation:(void(^)(void))tickBlock
{
LZTimer *tm = [[LZTimer alloc] init];
[tm startWithInterval:interval intervalOperation:tickBlock];
return tm;
}
-(void)startWithInterval:(CGFloat)interval intervalOperation:(void(^)(void))tickBlock{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(self.timer,dispatch_walltime(NULL, 0), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(self.timer, ^{
if (tickBlock) tickBlock();
});
dispatch_resume(self.timer);
}
-(void)invalidate
{
if (self.timer)
{
dispatch_source_cancel(self.timer);
self.timer = nil;
}
}
-(void)suspend
{
if (self.timer)
{
dispatch_suspend(self.timer);
}
}
-(void)resume
{
if (self.timer)
{
dispatch_resume(self.timer);
}
}
@end
本文的主角轮播图功能代码如下:
@interface LZImageBanner : UIView
@property (nonatomic, copy) NSArray *images_url;
@property (nonatomic, assign) CGFloat bannerWidth;
@end
@interface LZImageBanner()<UIScrollViewDelegate>
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIPageControl *pageControl;
@property (nonatomic, strong) LZTimer *timer;
@end
@implementation LZImageBanner
-(instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:frame];
scrollView.delegate = self;
scrollView.bounces = NO;
scrollView.pagingEnabled = YES;
scrollView.showsHorizontalScrollIndicator = NO;
scrollView.showsVerticalScrollIndicator = NO;
[self addSubview:scrollView];
[scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(0);
}];
self.scrollView = scrollView;
UIPageControl *pageControl = [UIPageControl new];
pageControl.currentPage = 0;
pageControl.hidesForSinglePage = YES;
[self addSubview:pageControl];
[pageControl mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self);
make.bottom.mas_equalTo(0);
}];
self.pageControl = pageControl;
}
return self;
}
-(void)setImages_url:(NSArray *)images_url
{
_images_url = images_url;
[self.scrollView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
if (images_url.count == 0) return;
if (images_url.count > 1)
{
NSMutableArray *cycle_banner_url = [[NSMutableArray alloc] initWithArray:images_url];
[cycle_banner_url addObject:images_url.firstObject];
[cycle_banner_url insertObject:images_url.lastObject atIndex:0];
_images_url = [cycle_banner_url copy];
}
UIImageView *lastImgView = nil;
for (NSInteger index = 0; index < _images_url.count; index++)
{
UIImageView *imgView = [UIImageView new];
[self.scrollView addSubview:imgView];
[imgView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.bottom.mas_equalTo(0);
if (lastImgView)
{
make.left.equalTo(lastImgView.mas_right);
}
else {
make.left.mas_equalTo(0);
}
if (index == _images_url.count -1)
{
make.right.mas_equalTo(0);
}
make.width.equalTo(self.scrollView);
make.height.equalTo(self.scrollView);
}];
[imgView sd_setImageWithURL:[NSURL URLWithString:_images_url[index]] completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
}];
lastImgView = imgView;
}
self.pageControl.currentPage = 0;
self.pageControl.numberOfPages = images_url.count;
if (images_url.count > 1)
{
self.scrollView.contentOffset = CGPointMake(self.bannerWidth, 0);
[self startTimerWithInterval:3.0];
}
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat currentOffsetX = scrollView.contentOffset.x;
NSInteger pageIndex = currentOffsetX / self.bannerWidth;
if (pageIndex >= self.images_url.count - 1)
{
self.pageControl.currentPage = 0;
scrollView.contentOffset = CGPointMake(self.bannerWidth, scrollView.contentOffset.y);
}
else if (currentOffsetX <= 0)
{
self.pageControl.currentPage = self.pageControl.numberOfPages - 1;
scrollView.contentOffset = CGPointMake(self.bannerWidth * self.pageControl.numberOfPages, scrollView.contentOffset.y);
}
else {
self.pageControl.currentPage = pageIndex - 1;
}
}
//-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
//{
// [self.timer suspend];
//}
//-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
//{
// [self.timer resume];
//}
-(void)startTimerWithInterval:(NSInteger)interval
{
[self.timer invalidate];
__weak typeof(self) ws = self;
self.timer = [LZTimer timerInterval:interval intervalOperation:^{
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(ws) ss = ws;
CGFloat currentOffsetX = ss.scrollView.contentOffset.x;
NSInteger currentIndex = currentOffsetX / ss.bannerWidth;
NSInteger targetIndex = currentIndex + 1;
CGPoint targetOffset = CGPointMake(ss.bannerWidth * targetIndex, ss.scrollView.contentOffset.y);
[ss.scrollView setContentOffset:targetOffset animated:YES];
});
}];
}
-(void)dealloc
{
[self.timer invalidate];
}
@end
轮播图调用代码如下:
LZImageBanner *imgBanner = [LZImageBanner new];
[self.view addSubview:imgBanner];
[imgBanner mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(15);
make.right.mas_equalTo(-15);
make.height.mas_equalTo(60);
make.top.mas_equalTo(100);
}];
imgBanner.bannerWidth = [UIScreen mainScreen].bounds.size.width - 30;
imgBanner.images_url = @[path1, path2, path3]; //path1, path2, path3 就是banner图片的url
该轮播图使用中遇到的问题:
- dispatch_suspend 状态下直接释放释放定时器,会导致定时器崩溃。
- 初始状态(未调用dispatch_resume)直接调用dispatch_source_cancel(timer),app崩溃。
- 重复调用dispatch_resume,app崩溃。
网友评论