06iOS圖片輪播控件-version0.1

為何要再寫圖片輪播控件

當(dāng)然圖片輪播控件這種東西真是被寫爛了,這里再寫一次的原因有三點(diǎn):一是目前已知的圖片輪播控件都在重復(fù)同樣的功能點(diǎn)(類似于簡(jiǎn)書客戶端的輪播),其實(shí)開發(fā)者對(duì)于圖片輪播控件還有很多其它的需求;二是目前我并沒有看到比較好的代碼,同時(shí)基于開發(fā)者對(duì)于輪播控件的復(fù)雜需求,代碼也會(huì)變得復(fù)雜起來(上千行的代碼實(shí)現(xiàn));三是我在學(xué)校里組織的興趣小組打算同時(shí)編寫iOS、Android、Web(基于JS)三個(gè)平臺(tái)的代碼,提供一致的特性和服務(wù),并將其開源。

version 0.1所實(shí)現(xiàn)的功能和提供的特性

輸入:用戶提供控件的frame,控件所要播放的images
輸出:一個(gè)圖片輪播組件,能夠定時(shí)切換圖片。
詳情請(qǐng)見需求文檔

著手實(shí)現(xiàn)version 0.1

實(shí)現(xiàn)原理請(qǐng)參照

私有屬性列表
@interface LZPPictureCarousel ()

@property (nonatomic, strong, nonnull) NSArray<UIImage *> *images;

@property (nonatomic, strong, nonnull) UIScrollView *imageScrollView;
@property (nonatomic, strong, nonnull) NSArray *imageViewArray;
@property (nonatomic, strong, nonnull) UIPageControl *pageControl;

@property (nonatomic, strong, nullable) NSTimer *timer; // null when timer is invalidate

@end

images:被展示的圖片所組成的數(shù)組
imageScrollView:可滾動(dòng)區(qū)域
imageViewArray:代碼實(shí)現(xiàn)中的imageView數(shù)組
pageControl:頁(yè)碼指示器
timer:計(jì)時(shí)器,用于定時(shí)切換圖片

為每一個(gè)私有屬性提供getter,在getter中只做邏輯初始化,以imageScrollView為例

- (UIScrollView *)imageScrollView
{
    if (!_imageScrollView) {
        _imageScrollView = ({
            UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
            
            scrollView.backgroundColor = [UIColor lightGrayColor];
            scrollView.showsHorizontalScrollIndicator = NO;
            scrollView.showsVerticalScrollIndicator = NO;
            scrollView.bounces = NO;
            scrollView.pagingEnabled = YES;
            
            scrollView;
        });
    }
    
    return _imageScrollView;
}
實(shí)例初始化邏輯
- (instancetype)initWithFrame:(CGRect)frame andImages:(nonnull NSArray<UIImage *> *)images
{
    // security checking...
    if (images == nil || images.count <= 0) {
        return nil;
    }

    for (id obj in images) {
        if (![obj isMemberOfClass:[UIImage class]]) {
            return nil;
        }
    }
    
    // custom initializer
    self = [super initWithFrame:frame];
    
    if (self) {
        
        self.images = images;
        
        [self customInitialization];
        
    }
    
    return self;
}

- (void)customInitialization
{
    [self addSubview:self.imageScrollView];
    [self addImageViewToScrollView];
    [self addPageControl];
}
圖片切換邏輯

根據(jù)設(shè)計(jì)原理,定時(shí)器觸發(fā)時(shí),scrollView要做的動(dòng)作很簡(jiǎn)單,就是向左滑動(dòng)一頁(yè)?;瑒?dòng)后根據(jù)scrollView的偏移量來調(diào)整pageControl和scrollView的偏移量

- (void)moveToNextPage
{
    CGFloat contentOffsetX = self.imageScrollView.contentOffset.x + self.imageScrollView.frame.size.width;
    [UIView animateWithDuration:0.5 animations:^{
        [self.imageScrollView setContentOffset:CGPointMake(contentOffsetX, 0)];
    } completion:^(BOOL finished) {
        [self adjustPageIndicatorAndContentOffset];
    }];
}

- (void)adjustPageIndicatorAndContentOffset
{
    NSInteger currentPage = floorf(self.imageScrollView.contentOffset.x / self.imageScrollView.frame.size.width + 0.5);
    
    if (currentPage == 0) {
        // the last page
        [self.imageScrollView setContentOffset:CGPointMake(self.imageScrollView.frame.size.width * self.images.count, 0)];
        self.pageControl.currentPage = self.images.count;
    } else if (currentPage == self.images.count + 1) {
        // the first page
        [self.imageScrollView setContentOffset:CGPointMake(self.imageScrollView.frame.size.width, 0)];
        self.pageControl.currentPage = 0;
    } else {
        self.pageControl.currentPage = currentPage - 1;
    }
}
讓圖片動(dòng)起來

定時(shí)器大法好,廢話不多說,直接上代碼

- (void)willMoveToSuperview:(UIView *)newSuperview
{
    [self startPlaying];
}

- (void)startPlaying
{
    [self.imageScrollView setContentOffset:CGPointMake(self.imageScrollView.frame.size.width, 0)];
    [self.timer fire];
}

- (void)stopPlaying
{
    [self.timer invalidate];
    self.timer = nil;
}
性能優(yōu)化

考慮定時(shí)器(及其所觸發(fā)的動(dòng)畫)帶來的性能消耗,在控件被移除或隱藏時(shí),應(yīng)該invalidate定時(shí)器

- (void)removeFromSuperview
{
    [super removeFromSuperview];
    
    [self stopPlaying];
}

- (void)setHidden:(BOOL)hidden
{
    [super setHidden:hidden];
    
    if (hidden) {
        [self stopPlaying];
    } else {
        [self startPlaying];
    }
}

- (void)dealloc
{
    if (!_timer) {
        [_timer invalidate];
    }
}
魯棒性

其它的文章里應(yīng)該給出了使用三個(gè)imageView實(shí)現(xiàn)功能做到內(nèi)存方面的性能優(yōu)化的問題。這里因?yàn)橹皇?.1版本,很多需求點(diǎn)都還沒有實(shí)現(xiàn),所以暫時(shí)不考慮內(nèi)存方面的優(yōu)化。后續(xù)版本應(yīng)該會(huì)采用合適的方案優(yōu)化內(nèi)存。如果你想提前弄清楚如何實(shí)現(xiàn)內(nèi)存上的優(yōu)化,你可以訪問其它作者的博客,譬如如何快速封裝一個(gè)輪播廣告,讓代碼更優(yōu)雅!

代碼和Demo已上傳至Github,歡迎大家使用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容