打造一款iOS高可用的輪播組件(廣告,跑馬燈)

jpg
其實(shí)這個(gè)問(wèn)題是這樣的:

在一個(gè)需求里,看到類似跑馬燈上下輪播的一個(gè)需求,要是只有文字還好說(shuō),我直接github上down一個(gè)輪子立馬就可以套用啊,當(dāng)然具體實(shí)現(xiàn)方式或者基本原理得懂一遍,不然做的啥都不知道,要有這么一點(diǎn)點(diǎn)責(zé)任心传泊。搞不好是給自己挖坑呢, 可需求是多變的,絕不僅僅是只有文字辣么簡(jiǎn)單,這次增加了一個(gè)icon圖標(biāo)也要跟著文字一起滾動(dòng),要是下次在增加一個(gè)按鈕還是啥的呢.... 將永無(wú)止境了,我們改起來(lái)也忙的一筆,焦頭爛額的感覺(jué)砍鸠。基于此,我想這個(gè)玩意一定是可以定制的,比如把輪播這個(gè)組件化,分離出去,里面的子視圖是根據(jù)數(shù)據(jù)源來(lái)創(chuàng)建,然后輪播的邏輯是輪播組件自身攜帶,只要遵守了協(xié)議就可以定制豐富多彩的子視圖進(jìn)行輪播,目的是想要打造這樣一款組件舆逃。夢(mèng)想還是要有的,萬(wàn)一實(shí)現(xiàn)了呢,從簡(jiǎn)單的一步步開始,慢工出細(xì)活。

操作步驟

  1. 一開始寫最丑的代碼和難看的UI,先實(shí)現(xiàn)基本功能先
  2. 改進(jìn)代碼,優(yōu)化UI的細(xì)節(jié)
  3. 定制協(xié)議接口方法
  4. 細(xì)節(jié)調(diào)整

- 滾動(dòng)方向,創(chuàng)建一個(gè)枚舉

typedef NS_ENUM(NSInteger,WGBScrollDirectionType){
    WGBScrollDirectionTypeHorizontal = 0,
    WGBScrollDirectionTypeVertical
};

- 數(shù)據(jù)源以及協(xié)議方法

@class WGBScrollContainerView;

@protocol WGBScrollContainerViewDataSourceDelegate <NSObject>

@required
  ///時(shí)間
- (NSTimeInterval)wgb_autoScrollDuration;
  ///滾動(dòng)的方向
- (WGBScrollDirectionType)wgb_ScrollDirectionType;
  ///有多少個(gè)子控件
- (NSInteger)wgb_numberOfRowsInWithContainerView:(WGBScrollContainerView *) containerView ;
  ///子控件
- (UIView *)wgb_subContentViewWithContainerView:(WGBScrollContainerView *) containerView  subViewForRowAtIndex:(NSInteger)index;

@optional
  ///點(diǎn)擊事件
- (void)wgb_containerView:(WGBScrollContainerView *)containerView didSelectRowAtIndex:(NSInteger)index;

@end

- InterFace

@interface WGBScrollContainerView : UIView

@property (nonatomic,weak) id<WGBScrollContainerViewDataSourceDelegate> wgbDataSourceDalegate;

@property (nonatomic,assign,readonly) NSTimeInterval duration;
@property (nonatomic,assign,readonly) WGBScrollDirectionType directionType;

- (void)start;
- (void)stop;
- (void)pause;
- (void)reloadData ;

- (void)clickItemViewWithIndex:(void(^)(NSInteger index))clickBlock;

@end

- imp

#import "WGBScrollContainerView.h"

@interface WGBScrollContainerView ()<UIScrollViewDelegate>

@property (nonatomic,strong) UIScrollView *bgScrollView;
@property (nonatomic,strong) NSTimer *timer;
@property (nonatomic,assign) NSInteger flagIndex;
@property (nonatomic,copy) void(^clickIndex) (NSInteger index);

@property (nonatomic,assign,readwrite) NSTimeInterval duration;
@property (nonatomic,assign,readwrite) WGBScrollDirectionType directionType;

@end

@implementation WGBScrollContainerView

- (UIScrollView *)bgScrollView{
    if (!_bgScrollView) {
        _bgScrollView = [[UIScrollView alloc] initWithFrame:self.bounds];
        _bgScrollView.delegate = self;
        _bgScrollView.backgroundColor = [UIColor whiteColor];
//      _bgScrollView.userInteractionEnabled = NO;
        _bgScrollView.showsVerticalScrollIndicator = NO;
        _bgScrollView.showsHorizontalScrollIndicator = NO;
        _bgScrollView.bounces = NO;
        _bgScrollView.pagingEnabled = YES;
        [self addSubview: _bgScrollView];
    }
    return _bgScrollView;
}

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {

    }
    return self;
}

#pragma mark - 即將進(jìn)入窗口
- (void)willMoveToWindow:(UIWindow *)newWindow
{
    [super willMoveToWindow:newWindow];
    [self reloadData];
}

- (void)reloadData{
    [self stop];
        ///沒(méi)有設(shè)置數(shù)據(jù)源 return
    if (self.wgbDataSourceDalegate == nil) {
        return;
    }

    if (![self.wgbDataSourceDalegate respondsToSelector:@selector(wgb_autoScrollDuration)]) {
        @throw [NSException exceptionWithName:@"WGBError" reason:@"未實(shí)現(xiàn)(wgb_autoScrollDuration:)" userInfo:nil];
    }

    if (![self.wgbDataSourceDalegate respondsToSelector:@selector(wgb_ScrollDirectionType)]) {
        @throw [NSException exceptionWithName:@"WGBError" reason:@"未實(shí)現(xiàn)(wgb_ScrollDirectionType:)" userInfo:nil];
    }

    if (![self.wgbDataSourceDalegate respondsToSelector:@selector(wgb_numberOfRowsInWithContainerView:)]) {
        @throw [NSException exceptionWithName:@"WGBError" reason:@"未實(shí)現(xiàn)(wgb_numberOfRowsInWithContainerView:)" userInfo:nil];
    }

    if (![self.wgbDataSourceDalegate respondsToSelector:@selector(wgb_subContentViewWithContainerView:subViewForRowAtIndex:)]) {
        @throw [NSException exceptionWithName:@"WGBError" reason:@"未實(shí)現(xiàn)(wgb_subContentViewWithContainerView:subViewForRowAtIndex:)" userInfo:nil];
    }

    self.duration = [self.wgbDataSourceDalegate wgb_autoScrollDuration];
    self.directionType = [self.wgbDataSourceDalegate wgb_ScrollDirectionType];

    [self setup];
    [self start];

}


- (void)setDirectionType:(WGBScrollDirectionType)directionType{
    _directionType = directionType;

}

- (void)setDuration:(NSTimeInterval)duration{
    _duration = duration;
    self.timer = [NSTimer scheduledTimerWithTimeInterval: duration target:self selector:@selector(changeContent) userInfo:nil repeats:YES];
    [[NSRunLoop mainRunLoop] addTimer: self.timer forMode:NSRunLoopCommonModes];
    self.flagIndex = 0;
}

- (void)setup{
    CGRect frame = self.bounds;
    CGFloat width = frame.size.width;
    CGFloat height = frame.size.height;
    CGFloat count = [self.wgbDataSourceDalegate wgb_numberOfRowsInWithContainerView: self];

    for (NSInteger i = 0; i < count ; i += 1) {
        UIView *subView = [self.wgbDataSourceDalegate wgb_subContentViewWithContainerView:self subViewForRowAtIndex: i];
        if (self.directionType == WGBScrollDirectionTypeVertical) {
            subView.frame = CGRectMake(0, height*i , width , height);
            self.bgScrollView.contentSize = CGSizeMake(width, height*count);
        }else{
            subView.frame = CGRectMake(width*i, 0, width , height);
            self.bgScrollView.contentSize = CGSizeMake(width*count, height);
        }
        subView.tag = i;
        subView.userInteractionEnabled = YES;
        UITapGestureRecognizer *tapGes = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(clickItemViewIndex:)];
        [subView addGestureRecognizer: tapGes];
        [self.bgScrollView addSubview: subView];
    }
    [self.bgScrollView setContentOffset:CGPointMake(0, 0)];
}

- (void)clickItemViewIndex:(UITapGestureRecognizer *)tap{
    if (self.clickIndex) { ///之前是用block實(shí)現(xiàn)的
        self.clickIndex(tap.view.tag);
    }

    if ([self.wgbDataSourceDalegate respondsToSelector:@selector(wgb_containerView:didSelectRowAtIndex:)]) {
        [self.wgbDataSourceDalegate wgb_containerView:self didSelectRowAtIndex:tap.view.tag];
    }
}

- (void)clickItemViewWithIndex:(void (^)(NSInteger index))clickBlock{
    self.clickIndex = clickBlock;
}


    //// 這里的做法是將數(shù)據(jù)源的第一條放置到了最后一條 ,等輪完一輪的時(shí)候,重新回到第一條時(shí),這里需要一個(gè)等待時(shí)間,這個(gè)時(shí)間間隔沒(méi)處理,一輪結(jié)束休息一波,也是人之常情,科學(xué)都源自于生活,我覺(jué)得這一點(diǎn)也是沒(méi)有毛病的
- (void)changeContent{
    CGRect frame = self.bounds;
    CGFloat width = frame.size.width;
    CGFloat height = frame.size.height;
    CGFloat count =  [self.wgbDataSourceDalegate wgb_numberOfRowsInWithContainerView: self];

    if (self.flagIndex == count) {
        self.flagIndex = 0;
        [self.bgScrollView setContentOffset:CGPointMake(0, 0)];
    }

    if (self.directionType == WGBScrollDirectionTypeVertical) {
        [self.bgScrollView setContentOffset:CGPointMake(0, height*self.flagIndex) animated:YES];
    }else{
        [self.bgScrollView setContentOffset:CGPointMake(width *self.flagIndex, 0) animated:YES];
    }
    self.flagIndex++;
}

- (void)start{
    [self.timer fire];
}

- (void)stop{
    [self.timer invalidate];
    self.timer = nil;
}

- (void)pause{
    [self.timer setFireDate:[NSDate distantPast]];
}

- (void)dealloc{
    [self stop];
}

  ///這一步是關(guān)鍵點(diǎn),取消scrollView的手動(dòng)滑動(dòng)手勢(shì),只保留定時(shí)器自動(dòng)滾動(dòng)
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    self.bgScrollView.panGestureRecognizer.enabled = NO;
}
@end

遇到的困難

1. 禁用UIScrollView的Pan滑動(dòng)手勢(shì) [已解決]

一開始也是沒(méi)有頭緒,關(guān)掉人機(jī)交互,,但是又要點(diǎn)擊事件,還想到過(guò)用穿透視圖來(lái)攔截,仍然沒(méi)有DidScroll: 這個(gè)方法方便

2. 視圖輪播完一輪回到初始位置的時(shí)間間隔的處理 [待定]

暫時(shí)沒(méi)處理,輪播完一波就休息一會(huì)兒

3. 子視圖復(fù)用的問(wèn)題 [待定]

子視圖需要設(shè)計(jì)一種像創(chuàng)建cell那樣注冊(cè)或者復(fù)用的方案,但是目前一點(diǎn)頭緒也沒(méi)有...

Demo已經(jīng)躺在github了點(diǎn)我下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末沸停,一起剝皮案震驚了整個(gè)濱河市辆影,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌后添,老刑警劉巖笨枯,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異遇西,居然都是意外死亡馅精,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門粱檀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)洲敢,“玉大人,你說(shuō)我怎么就攤上這事茄蚯⊙古恚” “怎么了睦优?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)壮不。 經(jīng)常有香客問(wèn)我汗盘,道長(zhǎng),這世上最難降的妖魔是什么询一? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任衡未,我火速辦了婚禮,結(jié)果婚禮上家凯,老公的妹妹穿的比我還像新娘缓醋。我一直安慰自己,他們只是感情好绊诲,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布送粱。 她就那樣靜靜地躺著,像睡著了一般掂之。 火紅的嫁衣襯著肌膚如雪抗俄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天世舰,我揣著相機(jī)與錄音动雹,去河邊找鬼。 笑死跟压,一個(gè)胖子當(dāng)著我的面吹牛胰蝠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播震蒋,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼茸塞,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了查剖?” 一聲冷哼從身側(cè)響起钾虐,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎笋庄,沒(méi)想到半個(gè)月后效扫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡直砂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年菌仁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哆键。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡掘托,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出籍嘹,到底是詐尸還是另有隱情闪盔,我是刑警寧澤弯院,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站泪掀,受9級(jí)特大地震影響柑司,放射性物質(zhì)發(fā)生泄漏沦辙。R本人自食惡果不足惜棕洋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一遂庄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧塔拳,春花似錦鼠证、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至颂碧,卻和暖如春荠列,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背载城。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工肌似, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人诉瓦。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓川队,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親垦搬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子呼寸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容