Banner的實(shí)現(xiàn)過程描述

基礎(chǔ)知識(shí)

  • 輪播廣告(Banner)是一個(gè)常見的需求,以前一般就出現(xiàn)在最頂部者蠕,現(xiàn)在也可能出現(xiàn)在中間陋率。所以需要放在一個(gè)表格的cell中。無論出現(xiàn)在頂部還是出現(xiàn)在中間公条,或者是放在cell中,還是放在其他view或者控制其中迂曲,把它單獨(dú)做成一個(gè)view是最靈活的靶橱。view是最方便復(fù)用的一種形式。如果說界面層要考慮復(fù)用路捧,那么通過代碼實(shí)現(xiàn)自定義的view关霸,作為組件形式(不要代入不必要的邏輯和數(shù)據(jù)獲取,View僅僅考慮怎么顯示)還是非辰苌ǎ可取的队寇。如果再配上一個(gè)ViewModel作為對(duì)外的數(shù)據(jù)接口,那么將更靈活章姓。文件多出來佳遣,怎么取舍,根據(jù)實(shí)際權(quán)衡凡伊。

  • 一般都用UIScrollView實(shí)現(xiàn)零渐,以前是計(jì)算frame,現(xiàn)在是引入第三方庫(kù)Masonry系忙,用AutoLayout通過加限制實(shí)現(xiàn)诵盼。

  • UIScrollView能夠滑動(dòng)本質(zhì)contentSize比frame大,就會(huì)滑動(dòng)银还,否則就不用滑動(dòng)风宁。所以UIScrollView加4個(gè)限制確定本身frame之外,還要再加width和height兩個(gè)條件蛹疯,用來確定contentSize戒财。絕對(duì)布局時(shí),算出一個(gè)size給它捺弦,AutoLayout時(shí)饮寞,一般是引入一個(gè)輔助的containerView,通過子view的大小羹呵,反向來決定父view的大小骂际。這兩者的思路是相反的疗琉,這點(diǎn)要理解一下冈欢。另外,還要注意一點(diǎn)的是盈简,在確定contentSize時(shí)凑耻,這個(gè)輔助的containerView不能當(dāng)做UIScrollView的子view看待太示,(在確定frame的時(shí)候確實(shí)是父子關(guān)系),這種情況下應(yīng)該把containerView和UIScrollView看成是同級(jí)別的兄弟view香浩。

  • 如何理解contentSize和frame:contentSize就是平鋪所有內(nèi)容的全部范圍类缤,不要被手機(jī)屏幕大小限制住。而frame相當(dāng)于觀察的窗口邻吭,滑動(dòng)就是這個(gè)窗口在移動(dòng)餐弱。contentOffset就是用來描述這種滑動(dòng)的,包括x囱晴、y兩個(gè)變量膏蚓,跟iOS通用坐標(biāo)系一直,向右畸写,向下變大驮瞧。Banner一般向左滑動(dòng),只要考慮x方向枯芬,y方向不用操心论笔。

  • 一般banner上會(huì)有指示當(dāng)前頁(yè)面的指示器,這個(gè)叫UIPageControl千所。這個(gè)控件比較簡(jiǎn)單狂魔。不過要理解的是,(1)UIScrollView的pagingEnabled屬性要設(shè)為YES淫痰,默認(rèn)是NO毅臊,這樣滑動(dòng)不滿一半會(huì)退回來,超過一半就會(huì)自動(dòng)往前走黑界,這樣就有一頁(yè)一頁(yè)翻的感覺管嬉。(2)小點(diǎn)移動(dòng)的是currentPage這個(gè)屬性來決定的,就是這個(gè)屬性對(duì)應(yīng)的小圓點(diǎn)的顏色跟其他的不一樣朗鸠,讓感覺是小點(diǎn)在移動(dòng)蚯撩。不過這個(gè)屬性值,往往就是用UIScrollView的contentOffset和frame做簡(jiǎn)單除法烛占,取整數(shù)部分而得到的胎挎。(3)UIPageControl自定義的空間比較小,一般市面上能看到的選中點(diǎn)是長(zhǎng)方形的那種忆家,一般都是用自定義View重新實(shí)現(xiàn)了這個(gè)控件犹菇。一般是用貝塞爾曲線重新畫,讓其行為看起來像UIPageControl而已芽卿。

  • 自動(dòng)輪播的效果揭芍,一般是加一個(gè)定時(shí)器,隔固定時(shí)間卸例,設(shè)置UIScrollView的contentOffset和UIPageControl的currentPage称杨。還要考慮的事一旦檢測(cè)有手勢(shì)左右橫掃操作肌毅,這是人在手動(dòng)滑動(dòng),要把計(jì)時(shí)器停止姑原。這個(gè)功能實(shí)現(xiàn)稍微有點(diǎn)麻煩悬而,需要考慮的地方也有點(diǎn)多。

  • 如何響應(yīng)點(diǎn)擊事件:方法1是將一個(gè)UIImageView和UIButton放在一個(gè)view容器中锭汛,UIImageView負(fù)責(zé)顯示圖片笨奠,UIButton負(fù)責(zé)響應(yīng)跳轉(zhuǎn)。方法2是在UIImageView上加一個(gè)tap手勢(shì)唤殴,來負(fù)責(zé)點(diǎn)擊的響應(yīng)艰躺。

基本思路

  • 從UIView繼承一個(gè)自定義的BannerView,作為容器眨八,也作為使用的整體腺兴,通用做法

  • 對(duì)外的接口是一更新函數(shù),參數(shù)是一個(gè)數(shù)組廉侧,數(shù)組成員是一個(gè)自定義的結(jié)構(gòu)页响,包含圖片url和跳轉(zhuǎn)url。

  • 內(nèi)部提供三個(gè)固定的本地圖片和三個(gè)固定的跳轉(zhuǎn)url段誊,后臺(tái)沒有配置也能用闰蚕。這個(gè)只要使用上面那個(gè)對(duì)外接口就可以了,只是本地資源圖片加載方式不一樣连舍,需要增加一個(gè)變量没陡,指明是否來字本地。

  • UIScrollView的高度固定索赏,寬度和屏幕一樣寬盼玄,平鋪的格式。要留白潜腻,加一個(gè)UIEdgeInset的padding就可以了埃儿。這樣就是橫向滑動(dòng)了。

  • 加一個(gè)輔助的containerView融涣,是UIScrollView的子view童番,4邊和UIScrollView完全重合。在確定UIScrollView的contentSize的時(shí)候威鹿,containerView和UIScrollView是兄弟view的關(guān)系剃斧,不是父子關(guān)系。containerView的高度和UIScrollView的高度一致忽你。containerView的寬度由里面包含的子ImageView來決定幼东。每個(gè)ImageView的高度和containerView一致,寬度和UIScrollView的寬度一致,每個(gè)挨個(gè)相連筋粗。最后一個(gè)ImageView的右邊界和containerView的右邊界一致。這樣炸渡,這些子ImageView就確定了containerView的寬度娜亿。而containerView的高度和寬度,就決定了UIScrollView的contentSize蚌堵。這點(diǎn)是用UIScrollView實(shí)現(xiàn)Banner中用AutoLayout來做最難理解的一點(diǎn)买决。

  • 加一個(gè)UIPagecontrol,他直接加在最外面的BannerView上吼畏,x方向居中督赤,寬度由內(nèi)容決定。有自己的高度泻蚊,估計(jì)有40點(diǎn)左右躲舌。根據(jù)點(diǎn)的大小,可以加一個(gè)10點(diǎn)左右的高度限制性雄,這樣就平整了没卸。離底部設(shè)一個(gè)偏移量,比如10點(diǎn)左右秒旋,這樣做位置設(shè)定就相對(duì)靈活一點(diǎn)约计。UIPagecontrol和UIScrollView平級(jí)比較好,兩者本來也沒什么關(guān)系迁筛。用系統(tǒng)自帶的煤蚌,自定義沒有必要,重新設(shè)計(jì)得并不見得好看细卧。

  • 自動(dòng)輪播功能不添加尉桩,像廣告,不好贪庙。引入計(jì)時(shí)器魄健,就要考慮run mode和多線程的事情,麻煩插勤。自己動(dòng)沽瘦,讓人煩,一看就知道廣告农尖。不動(dòng)析恋,又有小點(diǎn)提示,反而能吸引人去操作盛卡。

代碼示例

BannerViewModel.h

@interface BannerViewModel : NSObject

@property (nonatomic, strong) NSString *imageUrl;
@property (nonatomic, strong) NSString *redirctionUrl;
@property (nonatomic, assign) BOOL isLocalImage;   // 這里為YES時(shí)助隧,imageUrl放圖片的文件名,使用[UIImage ImagenNamed:]加載圖片

@end

BannerView.h

#import <UIKit/UIKit.h>
#import "BannerViewModel.h"

@interface BannerView : UIView

- (void)updateWithViewModelArray:(NSArray<BannerViewModel *> *)viewModelArray;

@end

BannerView.m

#import "BannerView.h"

#define kScrollViewEdgeInsetTop            0
#define kScrollViewEdgeInsetLeft           0
#define kScrollViewEdgeInsetBottom         0
#define kScrollViewEdgeInsetRight          0

#define kPageControlSpaceBottom            10
#define kPageControlHegith                 10

#define kPlaceHoldImageFileName            @"place_hold"

#define kTagBase                           100

@interface BannerView () <UIScrollViewDelegate>

@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIView *containerView;
@property (nonatomic, strong) UIPageControl *pageControl;

@property (nonatomic, strong) NSArray <BannerViewModel *> *viewModelArray;

@end

@implementation BannerView

#pragma mark - interface

- (void)updateWithViewModelArray:(NSArray<BannerViewModel *> *)viewModelArray {
    if (nil == viewModelArray) {
        return;
    }
    if (viewModelArray.count < 1) {
        return;
    }
    self.viewModelArray = viewModelArray;
    
    for (UIView *view in self.containerView.subviews) {
        // 這里是否可以將對(duì)應(yīng)的限制刪除?在Masonry沒找到相應(yīng)的接口
        [view removeFromSuperview];
    }
    
    BannerViewModel *viewModel = nil;
    UIImageView *lastView = nil;
    for (NSInteger i = 0; i < viewModelArray.count; i++) {
        viewModel = viewModelArray[i];
        UIImageView *subView = [[UIImageView alloc] init];
        subView.contentMode = UIViewContentModeScaleAspectFill;
        subView.clipsToBounds = YES;  // 防止圖片越界顯示
        subView.tag = kTagBase + i;
        subView.userInteractionEnabled = YES; // 讓ImageView可以響應(yīng)事件
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(imageTouched:)];
        [subView addGestureRecognizer:tap];
        if (viewModel.isLocalImage) {
            subView.image = [UIImage imageNamed:viewModel.imageUrl];
        } else {
            [subView sd_setImageWithPreviousCachedImageWithURL:[NSURL URLWithString:viewModel.imageUrl] andPlaceholderImage:[UIImage imageNamed:kPlaceHoldImageFileName] options:SDWebImageRetryFailed progress:nil completed:nil];
        }
        [self.containerView addSubview:subView];
        
        [subView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.bottom.mas_equalTo(0);                    // 上下緊挨著并村,高度都一樣
            make.width.mas_equalTo(self.scrollView.mas_width); // 寬度都一樣巍实,都是“一屏”
            if (lastView) {
                make.left.mas_equalTo(lastView.mas_right); // 緊挨著前一個(gè)
            } else {
                make.left.mas_equalTo(0); // 第一個(gè)靠左
            }
        }];
        
        lastView = subView;
    }
    // 最后一個(gè)靠右
    [lastView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.mas_equalTo(0);
    }];
    
    self.pageControl.numberOfPages = viewModelArray.count;
    self.pageControl.currentPage = 0;
    
    // 重新布局
    [self setNeedsUpdateConstraints];
}

#pragma mark - life cycle

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.scrollView = [[UIScrollView alloc] init];
        self.scrollView.pagingEnabled = YES; // 有一頁(yè)一頁(yè)翻的感覺;默認(rèn)是NO哩牍,普通的滑動(dòng)
        self.scrollView.delegate = self;
        self.scrollView.showsHorizontalScrollIndicator = NO; // 有小圓點(diǎn)提示棚潦,不需要滑動(dòng)指示。默認(rèn)是YES膝昆,有滑動(dòng)條
        [self addSubview:self.scrollView];
        
        self.containerView = [[UIView alloc] init];
        [self.scrollView addSubview:self.containerView]; // 輔助視圖丸边,用來確定contentSize
        
        self.pageControl = [[UIPageControl alloc] init];
        self.pageControl.pageIndicatorTintColor = [UIColor whiteColor]; // 普通小圓點(diǎn)的顏色
        self.pageControl.currentPageIndicatorTintColor = [UIColor cyanColor]; // 當(dāng)前小圓點(diǎn)的顏色
        self.pageControl.hidesForSinglePage = YES; // 只有一張圖片,就沒有必要顯示了
        [self addSubview:self.pageControl];
    }
    return self;
}

// tell UIKit that you are using AutoLayout
+ (BOOL)requiresConstraintBasedLayout {
    return YES;
}

// this is Apple's recommended place for adding/updating constraints
- (void)updateConstraints {
    [self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.mas_equalTo(0).insets(UIEdgeInsetsMake(kScrollViewEdgeInsetTop, kScrollViewEdgeInsetLeft, kScrollViewEdgeInsetBottom, kScrollViewEdgeInsetRight)); // 確定scrollView的位置.
    }];
    
    [self.containerView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.mas_equalTo(0);   // 這個(gè)已經(jīng)確定了containerView的frame荚孵,和scrollView是父子關(guān)系妹窖。這是輔助view,不要考慮四周的留白.
        make.height.mas_equalTo(self.scrollView.mas_height); // 這里通過containerView來確定scrollView的contentSize收叶,這里是平級(jí)的兄弟關(guān)系骄呼。如果寫成make.height.mas_equalTo(0),則不起效果
    }];
    
    [self.pageControl mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerX.mas_equalTo(0);
        make.height.mas_equalTo(kPageControlHegith); // 這里可以不加判没,不過pageControl自身的高度太大谒麦,不利于定位
        make.bottom.mas_equalTo(-kPageControlSpaceBottom);
    }];
    
    //according to apple super should be called at end of method
    [super updateConstraints];
}

#pragma mark - UIScrollViewDelegate

// 這里就是處理小圓點(diǎn)滑動(dòng)的地方。UIScrollViewDelegate沒有停止的函數(shù)哆致,只有這個(gè)停止減速的函數(shù)
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    NSInteger page = self.scrollView.contentOffset.x / self.scrollView.frame.size.width;
    self.pageControl.currentPage = page;
}

#pragma mark - action

- (void)imageTouched:(UITapGestureRecognizer *)gestture {
    if (nil == self.viewModelArray) {
        return;
    }
    if (self.viewModelArray.count < 1) {
        return;
    }
    UIView *view = gestture.view;
    NSInteger tag = view.tag;
    NSInteger index = tag - kTagBase;
    BannerViewModel *viewModel = self.viewModelArray[index];
    if (nil != viewModel.redirctionUrl) {
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:viewModel.redirctionUrl]];
    }
}

@end

參考文章

Masonry介紹與使用實(shí)踐(快速上手Autolayout)

UIScrollview與Autolayout的那點(diǎn)事

iOS開發(fā)-UIImageView響應(yīng)點(diǎn)擊事件

UIImageView響應(yīng)點(diǎn)擊事件

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绕德,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子摊阀,更是在濱河造成了極大的恐慌耻蛇,老刑警劉巖,帶你破解...
    沈念sama閱讀 223,126評(píng)論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胞此,死亡現(xiàn)場(chǎng)離奇詭異臣咖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)漱牵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門夺蛇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人酣胀,你說我怎么就攤上這事刁赦。” “怎么了闻镶?”我有些...
    開封第一講書人閱讀 169,941評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵甚脉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我铆农,道長(zhǎng)牺氨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,294評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮猴凹,結(jié)果婚禮上夷狰,老公的妹妹穿的比我還像新娘。我一直安慰自己郊霎,他們只是感情好沼头,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,295評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著歹篓,像睡著了一般瘫证。 火紅的嫁衣襯著肌膚如雪揉阎。 梳的紋絲不亂的頭發(fā)上庄撮,一...
    開封第一講書人閱讀 52,874評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音毙籽,去河邊找鬼洞斯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛坑赡,可吹牛的內(nèi)容都是我干的烙如。 我是一名探鬼主播,決...
    沈念sama閱讀 41,285評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼毅否,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼亚铁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起螟加,我...
    開封第一講書人閱讀 40,249評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤徘溢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后捆探,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體然爆,經(jīng)...
    沈念sama閱讀 46,760評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,840評(píng)論 3 343
  • 正文 我和宋清朗相戀三年黍图,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了曾雕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,973評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡助被,死狀恐怖剖张,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情揩环,我是刑警寧澤修械,帶...
    沈念sama閱讀 36,631評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站检盼,受9級(jí)特大地震影響肯污,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,315評(píng)論 3 336
  • 文/蒙蒙 一蹦渣、第九天 我趴在偏房一處隱蔽的房頂上張望哄芜。 院中可真熱鬧,春花似錦柬唯、人聲如沸认臊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,797評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)失晴。三九已至,卻和暖如春拘央,著一層夾襖步出監(jiān)牢的瞬間涂屁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,926評(píng)論 1 275
  • 我被黑心中介騙來泰國(guó)打工灰伟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拆又,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,431評(píng)論 3 379
  • 正文 我出身青樓栏账,卻偏偏與公主長(zhǎng)得像帖族,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挡爵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,982評(píng)論 2 361

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

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)竖般、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,131評(píng)論 4 61
  • 通過一個(gè)關(guān)于相冊(cè)瀏覽的簡(jiǎn)單應(yīng)用--圖片縮放茶鹃,垂直滑動(dòng)圖片涣雕,翻頁(yè)效果,大家將學(xué)到UIScrollView相關(guān)的知識(shí)前计。...
    Magenta_she閱讀 1,257評(píng)論 2 9
  • 兒子回來了胞谭,回來后依然不肯動(dòng)書包,手機(jī)不離手男杈。我似乎也能理解丈屹,在校整整五天的時(shí)間不能碰手機(jī),回來后似乎就要放縱一下...
    旦子閱讀 142評(píng)論 0 2
  • 序 2017,過去了90天肤无,3個(gè)月先蒋,1個(gè)季。每年伊始宛渐,都有自己的小目標(biāo)竞漾。但常常半路流產(chǎn)眯搭。若再這樣聽之任之,...
    一個(gè)教書匠的自白閱讀 276評(píng)論 0 1
  • 所有的痛苦业岁,都必須面對(duì)鳞仙,而且要相處,你選擇逃避笔时,它們會(huì)永遠(yuǎn)在那里棍好,在那里嘲笑得看著你,看著你有一天多么不情愿允耿,囧頓...
    姜與小島閱讀 220評(píng)論 0 2