iOS仿淘寶類電商秒殺的分頁控件

前言

最近公司一個電商應(yīng)用要實現(xiàn)一個類似淘寶淘搶購頁面邏輯的功能森枪,起初本來想找個第三方的組件,后面發(fā)現(xiàn)網(wǎng)上并沒有類似的實現(xiàn)审孽。所以后面決定自己封裝一個县袱,效果如下所示:

演示.gif

功能特點

  • 實現(xiàn)了菜單切換的視覺差,效果棒棒噠佑力;
  • 使用簡單式散,創(chuàng)建一個控制器直接繼承GFPageViewController,設(shè)置需要添加的子控制器打颤、標(biāo)題暴拄、副標(biāo)題就搞定漓滔;
  • 菜單大部分的樣式都可進(jìn)行自定義;
  • 菜單遮罩的顏色乖篷、大小和箭頭的大小也可以設(shè)置參數(shù)來控制响驴;
  • 菜單實現(xiàn)了防止用戶連續(xù)點擊功能;
  • 支持pod導(dǎo)入.

組件導(dǎo)入

組件支持直接將組件文件夾拖入工程和使用Pods管理兩種方式導(dǎo)入:

1撕蔼、直接將組件文件夾拖入工程方式

把GFPageControler文件夾拖到工程中豁鲤,選擇copy


組件相關(guān)文件夾.png

2、Pods導(dǎo)入方式

pod 'GFPageController'

組件使用

1鲸沮、基本使用方式

創(chuàng)建一個控制器繼承自GFPageViewController琳骡,創(chuàng)建完之后給控制器設(shè)置需要添加的子控制器(Array)、標(biāo)題(Array)讼溺、副標(biāo)題(Array):

#import <UIKit/UIKit.h>
#import "GFPageViewController.h"
@interface PageViewController : GFPageViewController
@end
@implementation PageViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self configureContentView];
}

- (void)configureContentView {
    NSArray *titles             = @[@"8:00",@"10:00",@"12:00",@"14:00",@"16:00",@"18:00",@"20:00"];
    NSArray *subTitles          = @[@"已結(jié)束",@"已結(jié)束",@"已結(jié)束",@"瘋搶中",@"即將開始",@"即將開始",@"即將開始"];
    NSMutableArray *controllers = [NSMutableArray new];
    for (int i = 0; i < 7; i ++) {
        UIViewController *vc    = [[UIViewController alloc] init];
        vc.view.backgroundColor = [UIColor colorWithRed:arc4random_uniform(255)/255.0 green:arc4random_uniform(255)/255.0 blue:arc4random_uniform(255)/255.0 alpha:1.0f];
        [controllers addObject:vc];
    }
    // 設(shè)置控制器數(shù)組
    self.controllers = controllers;
    // 設(shè)置標(biāo)題數(shù)組
    self.titles      = titles;
    // 設(shè)置副標(biāo)題數(shù)組
    self.subTitles   = subTitles;
    // 設(shè)置初始下標(biāo)
    self.selectIndex = 1;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

@end

OK楣号,搞定,運行就可以看到效果怒坯,是不是很簡單竖席。

2、自定義菜單樣式

可以看到上面沒有一行設(shè)置菜單樣式的代碼敬肚,那是因為不設(shè)置菜單使用的是默認(rèn)的樣式,除此之外束析,菜單的樣式還是可以自定義的艳馒, GFPageController為大家提供了下面14個參數(shù)來控制菜單的樣式顯示:

/** MenuItem 的寬度 */
@property (nonatomic, assign) CGFloat itemWidth;
/** Menu 的高度 */
@property (nonatomic, assign) CGFloat menuHeight;
/** Menu 背景顏色 */
@property (nonatomic, strong) UIColor *menuBackgroundColor;
/** Menu mask的填充顏色 */
@property (nonatomic, strong) UIColor *maskFillColor;
/** Menu mask三角形的寬度 */
@property (nonatomic, assign) CGFloat triangleWidth;
/** Menu mask三角形的高度 */
@property (nonatomic, assign) CGFloat triangleHeight;

/** 標(biāo)題未選中時的顏色 */
@property (nonatomic, strong) UIColor *normalTitleColor;
/** 標(biāo)題選中時的顏色 */
@property (nonatomic, strong) UIColor *selectedTitleColor;
/** 標(biāo)題文字字體 */
@property (nonatomic, strong) UIFont  *titleTextFont;
/** 標(biāo)題文字高度 */
@property (nonatomic, assign) CGFloat titleTextHeight;

/** 副標(biāo)題未選中時的顏色 */
@property (nonatomic, strong) UIColor *normalSubTitleColor;
/** 副標(biāo)題選中時的顏色 */
@property (nonatomic, strong) UIColor *selectedSubTitleColor;
/** 副標(biāo)題文字字體 */
@property (nonatomic, strong) UIFont  *subTitleTextFont;
/** 副標(biāo)題文字高度 */
@property (nonatomic, assign) CGFloat subTitleTextHeight;

大家可以自行嘗試!


組件講解

1员寇、菜單視覺差實現(xiàn)

效果:

開始看淘寶里面的淘搶購頁面時弄慰,發(fā)現(xiàn)了一個細(xì)節(jié),如下:

淘搶購頁面.png

可以發(fā)現(xiàn)蝶锋,只要滾動到了中間紅色那塊區(qū)域的文字陆爽,顏色都會變成白色。扳缕。慌闭。
腦洞了很久也沒有想到思路!后來網(wǎng)上查找躯舔,從一篇文章中得到了靈感 視錯覺結(jié)合UI驴剔。

原理:

原理其實很簡單:就是弄兩個視圖,內(nèi)容和位置一樣粥庄,只是他們的文字顏色不一樣而已丧失!

實現(xiàn):

知道了原理,那就開始構(gòu)思:
1惜互、我的實現(xiàn)思路是用UICollectionView來實現(xiàn)滾動菜單布讹;
2琳拭、需要兩個UICollectionViewUICollectionViewCell的文字內(nèi)容一樣描验,文字顏色區(qū)分白嘁;

#pragma mark - 創(chuàng)建兩個UICollectionView
// collectionViewTop
- (UICollectionView *)collectionViewTop {
    if (!_collectionViewTop) {
        _collectionViewTop                                = [[UICollectionView alloc] initWithFrame:CGRectMake(-self.collectionViewEdge, 0, self.bounds.size.width, self.itemHeight) collectionViewLayout:self.flowLayout];
        [_collectionViewTop registerClass:[GFMenuItem class] forCellWithReuseIdentifier:GFMENUITEM_NIBNAME];
        _collectionViewTop.tag                            = TOP_COLLECTIONVIEW_TAG;
        _collectionViewTop.backgroundColor                = [UIColor clearColor];
        _collectionViewTop.showsHorizontalScrollIndicator = NO;
        _collectionViewTop.decelerationRate               = 0;//設(shè)置手指放開后的減速率(值域 0~1 值越小減速停止的時間越短),默認(rèn)為1
        _collectionViewTop.delegate                       = self;
        _collectionViewTop.dataSource                     = self;
    }
    return _collectionViewTop;
}

// collectionViewBottom
- (UICollectionView *)collectionViewBottom {
    if (!_collectionViewBottom) {
        _collectionViewBottom                                = [[UICollectionView alloc] initWithFrame:self.bounds collectionViewLayout:self.flowLayout];
        [_collectionViewBottom registerClass:[GFMenuItem class] forCellWithReuseIdentifier:GFMENUITEM_NIBNAME];
        _collectionViewBottom.tag                            = BOTTOM_COLLECTIONVIEW_TAG;
        _collectionViewBottom.backgroundColor                = [UIColor clearColor];
        _collectionViewBottom.showsHorizontalScrollIndicator = NO;
        _collectionViewBottom.decelerationRate               = 0;//設(shè)置手指放開后的減速率(值域 0~1 值越小減速停止的時間越短),默認(rèn)為1
        _collectionViewBottom.delegate                       = self;
        _collectionViewBottom.dataSource                     = self;
    }
    return _collectionViewBottom;
}

// flowLayout
- (UICollectionViewFlowLayout *)flowLayout {
    if (!_flowLayout) {
        _flowLayout                    = [[UICollectionViewFlowLayout alloc] init];
        _flowLayout.itemSize           = CGSizeMake(self.itemWidth, self.itemHeight);
        _flowLayout.sectionInset       = UIEdgeInsetsMake(0, self.collectionViewEdge, 0, self.collectionViewEdge);
        _flowLayout.minimumLineSpacing = 0;
        _flowLayout.scrollDirection    = UICollectionViewScrollDirectionHorizontal;
    }
    return _flowLayout;
}

#pragma mark - UICollectionViewDataSource
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    GFMenuItem *cell = [collectionView dequeueReusableCellWithReuseIdentifier:GFMENUITEM_NIBNAME forIndexPath:indexPath];
    NSString *title    = self.titles[indexPath.row];
    NSString *subTitle = self.subTitles[indexPath.row];
    // 區(qū)分顏色
    if (collectionView.tag == BOTTOM_COLLECTIONVIEW_TAG) {
        cell.titleColor    = self.normalTitleColor;
        cell.subTitleColor = self.normalSubTitleColor;
    } else {
        cell.titleColor    = self.selectedTitleColor;
        cell.subTitleColor = self.selectedSubTitleColor;
    }
    cell.titleText           = title;
    cell.subTitleText        = subTitle;
    return cell;
}

3、兩個UICollectionView的滾動需要同步挠乳;

#pragma makr - 同步滾動
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    UICollectionView *collectionView = (UICollectionView *)scrollView;
    //同步兩個collectionView的滾動
    if (collectionView.tag == BOTTOM_COLLECTIONVIEW_TAG) {
        [_collectionViewTop setContentOffset:collectionView.contentOffset];
    } else {
        [_collectionViewBottom setContentOffset:collectionView.contentOffset];
    }
}

3权薯、需要一個遮罩,一個UICollectionView在遮罩下面睡扬,一個在遮罩上面盟蚣;

[self addSubview:self.collectionViewBottom];
[self addSubview:self.maskView];
[self.maskView addSubview:self.collectionViewTop];

4、在遮罩上面的UICollectionView超出遮罩的部分的內(nèi)容不顯示出來卖怜;

self.maskView.clipsToBounds = YES;

2屎开、使用UIBezierPath繪制遮罩

大家會發(fā)現(xiàn)這個遮罩是多邊形的。
起初我的想法是用兩種圖片拼接起來马靠,一張長方形奄抽,一張三角形,后來為了自定義性更高一點甩鳄,改成了用UIBezierPath來進(jìn)行繪制逞度,代碼如下:

自定義一個View繼承自UIView

#import "GFMaskView.h"

@implementation GFMaskView
- (void)drawRect:(CGRect)rect {
    UIBezierPath *path = [UIBezierPath bezierPath];
    // 畫矩形
    path = [self drawReact:CGRectMake(0, 0, rect.size.width, rect.size.height - self.triangleHeight) fillColor:self.fillColor];
    // 畫三角形
    CGPoint pointOne   = CGPointMake(rect.size.width/2 - self.triangleWidth/2, rect.size.height - self.triangleHeight);
    CGPoint pointTwo   = CGPointMake(rect.size.width/2, rect.size.height);
    CGPoint pointThree = CGPointMake(rect.size.width/2 + self.triangleWidth/2, rect.size.height - self.triangleHeight);
    path = [self drawTrianglePointOne:pointOne pointTwo:pointTwo pointThree:pointThree fillColor:self.fillColor];
}

// 畫矩形
- (UIBezierPath *)drawReact:(CGRect)rect fillColor:(UIColor *)fillColor {
    UIBezierPath *rectPath = [UIBezierPath bezierPathWithRect:rect];
    [fillColor setFill];
    [rectPath fill];
    return rectPath;
}

// 畫三角形
- (UIBezierPath *)drawTrianglePointOne:(CGPoint)pointOne pointTwo:(CGPoint)pointTwo pointThree:(CGPoint)pointThree fillColor:(UIColor *)fillColor {
    UIBezierPath *trianglePath = [UIBezierPath bezierPath];
    // 起點
    [trianglePath moveToPoint:pointOne];
    // draw the lines
    [trianglePath addLineToPoint:pointTwo];
    [trianglePath addLineToPoint:pointThree];
    [trianglePath closePath];
    [fillColor set];
    [trianglePath fill];
    return trianglePath;
}

3、GFPageViewController

到這里滾動菜單的實現(xiàn)就完成了妙啃。我的初衷其實就是把這個滾動菜單封裝出來档泽,后來發(fā)現(xiàn)使用這個菜單的大部分情況都是和多個子控制器一起使用,所以就再進(jìn)行了一步封裝揖赴,把控制器的邏輯都封裝到了GFPageViewController控制器中馆匿。
這樣使用起來就很方便,直接創(chuàng)建一個控制器繼承GFPageViewController燥滑,再給他設(shè)置需要添加的子控制器渐北、標(biāo)題和副標(biāo)題就OK了。
GFPageViewController的實現(xiàn)主要是讓菜單和添加的子控制器能夠聯(lián)動铭拧,核心代碼如下:

//  添加視圖
- (void)setupContentView {
    [self.view addSubview:self.scrollView];
    [self.view addSubview:self.gfSegmentedControl];
}

// 滾動到指定下標(biāo)的控制器
- (void)scrollControllerAtIndex:(int)index {
    CGFloat offsetX = index * GF_SCREEN_WIDTH;
    CGPoint offset = CGPointMake(offsetX, 0);
    if (fabs(self.scrollView.contentOffset.x - offset.x) > GF_SCREEN_WIDTH) {
        [self.scrollView setContentOffset:offset animated:NO];
        // 獲得索引
        int index = (int)self.scrollView.contentOffset.x / self.scrollView.frame.size.width;
        [self addChildViewAtIndex:index];
    } else {
        [self.scrollView setContentOffset:offset animated:YES];
    }
}

// 添加子控制器的View到ScrollView上
- (void)addChildViewAtIndex:(int)index {
    // 設(shè)置選中的下標(biāo)
    self.menuView.selectIndex = index;
    UIViewController *vc = self.childViewControllers[index];
    vc.view.frame = self.scrollView.bounds;
    [self.scrollView addSubview:vc.view];
}

#pragma mark - UIScrollViewDelegate
// 滾動動畫結(jié)束后調(diào)用(代碼導(dǎo)致)
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView*)scrollView {
    // 添加控制器
    if (self.controllers) {
        // 獲得索引
        int index = (int)self.scrollView.contentOffset.x / self.scrollView.frame.size.width;
        [self addChildViewAtIndex:index];
    }
}

// 滾動結(jié)束(手勢導(dǎo)致)
- (void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView {
    [self scrollViewDidEndScrollingAnimation:scrollView];
}

#pragma mark - getter
- (UIScrollView *)scrollView {
    if (!_scrollView) {
        CGFloat scrollViewY = _menuHeight;
        if (self.navigationController && !self.navigationController.navigationBar.hidden) {
            scrollViewY = _menuHeight + 64;
        }
        _scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, scrollViewY, GF_SCREEN_WIDTH, GF_SCREEN_HEIGHT - _menuHeight)];
        _scrollView.contentSize                    = CGSizeMake(_controllers.count * GF_SCREEN_WIDTH, 0);
        _scrollView.pagingEnabled                  = YES;
        _scrollView.bounces                        = NO;
        _scrollView.showsHorizontalScrollIndicator = NO;
        _scrollView.delegate                       = self;
    }
    return _scrollView;
}

- (GFMenuView *)gfSegmentedControl {
    if (!_menuView) {
        CGFloat segmentedControlY = 0;
        if (self.navigationController && !self.navigationController.navigationBar.hidden) {
            segmentedControlY = 64;
        }
        _menuView = [GFMenuView gfMenuViewWithFrame:CGRectMake(0, segmentedControlY, GF_SCREEN_WIDTH, _menuHeight) titles:_titles subTitles:_subTitles];
        gfWeakSelf(weakSelf);
        _menuView.clickIndexBlock = ^(int clickIndex) {
            [weakSelf scrollControllerAtIndex:clickIndex];
        };
    }
    return _menuView;
}

#pragma mark - setter
// set dataSource
- (void)setControllers:(NSArray<UIViewController *> *)controllers {
    _controllers            = [controllers copy];
    self.scrollView.contentSize = CGSizeMake(_controllers.count * GF_SCREEN_WIDTH, 0);
    // 添加子控制器
    for (UIViewController *vc in controllers) {
        [self addChildViewController:vc];
        [vc didMoveToParentViewController:self];
    }
    if (self.selectIndex != 0) {
        // 添加指定下標(biāo)控制器
        [self addChildViewAtIndex:self.selectIndex];
    } else {
        // 默認(rèn)添加第一個控制器
        [self addChildViewAtIndex:0];
    }
}

- (void)setTitles:(NSArray<NSString *> *)titles {
    _titles                        = [titles copy];
    self.gfSegmentedControl.titles = titles;
}

- (void)setSubTitles:(NSArray<NSString *> *)subTitles {
    _subTitles                        = [subTitles copy];
    self.gfSegmentedControl.subTitles = subTitles;
}

結(jié)語

哈哈赃蛛,這也算是自己第一次封裝一個完整易用的組件,從中學(xué)習(xí)到了不少東西搀菩。
其中比如自定義View的正確姿勢焊虏;UIScrollView中一些代理使用的細(xì)節(jié)問題;讓自己的組件支持Pods等秕磷。這些問題我會單獨抽出來進(jìn)行總結(jié)诵闭,
最后大家對這個組件遇到的問題可以在簡書wythetan上或者git上gaofengtan給我留言。

最后貼上項目的git地址GFPageController

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市疏尿,隨后出現(xiàn)的幾起案子瘟芝,更是在濱河造成了極大的恐慌,老刑警劉巖褥琐,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锌俱,死亡現(xiàn)場離奇詭異,居然都是意外死亡敌呈,警方通過查閱死者的電腦和手機(jī)贸宏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來磕洪,“玉大人吭练,你說我怎么就攤上這事∥鱿裕” “怎么了鲫咽?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谷异。 經(jīng)常有香客問我分尸,道長,這世上最難降的妖魔是什么歹嘹? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任箩绍,我火速辦了婚禮,結(jié)果婚禮上尺上,老公的妹妹穿的比我還像新娘材蛛。我一直安慰自己,他們只是感情好尖昏,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著构资,像睡著了一般抽诉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吐绵,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天迹淌,我揣著相機(jī)與錄音,去河邊找鬼己单。 笑死唉窃,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的纹笼。 我是一名探鬼主播纹份,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蔓涧?” 一聲冷哼從身側(cè)響起件已,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎元暴,沒想到半個月后篷扩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡茉盏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年鉴未,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸠姨。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡铜秆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出享怀,到底是詐尸還是另有隱情羽峰,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布添瓷,位于F島的核電站梅屉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鳞贷。R本人自食惡果不足惜坯汤,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望搀愧。 院中可真熱鬧惰聂,春花似錦、人聲如沸咱筛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽迅箩。三九已至溉愁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間饲趋,已是汗流浹背拐揭。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留奕塑,地道東北人堂污。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像龄砰,于是被迫代替她去往敵國和親盟猖。 傳聞我的和親對象是個殘疾皇子讨衣,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

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

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件扒披、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,121評論 4 61
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,791評論 22 665
  • 朋友失戀了值依。 當(dāng)我坐在對面聽她哭訴的時候,仿佛感覺到時光倒流碟案,因為上一次的戀愛失敗愿险,和這次的原因大同小異。 她是一...
    慎途閱讀 404評論 0 0
  • 天生麗質(zhì)卻任性虛偽价说,精明能干卻冷酷無情辆亏,不安現(xiàn)狀卻不折手段,堅強自信卻貪婪自私鳖目,不屈不撓卻卑鄙陰險扮叨,渴望愛情卻追求...
    九x葉閱讀 172評論 0 0
  • 為了修滿SHRM-SCP的60個學(xué)分參加了一個分?jǐn)?shù)有效性的項目,因為是global 的會議所以實在半夜0點到4點领迈。...
    深情無用閱讀 170評論 0 0