iOS-封裝 Demo--滑動切換多標(biāo)題菜單

<big><b><i>十年生死兩茫茫,細(xì)思量汁讼,自難忘浓若!縱使相逢應(yīng)不識考榨,塵滿面号显,鬢如霜臭猜!<伊布家族></i></b></big>

好多地方都有滑動切換多個標(biāo)題樣式的菜單!今天自己嘗試封裝了一個押蚤,考慮欠缺的地方還望您指正蔑歌!GitHub

先上圖
滑動切換多標(biāo)題菜單.gif

簡單說說我的思路

1: 將需要的關(guān)鍵數(shù)據(jù)初始化的時候獲取(需要頂部每個頁面對應(yīng)的標(biāo)題揽碘,還要知道要展示的頁面丐膝,各自作為一個數(shù)組存入)。
2: 在自定義的 View 上添加平移手勢钾菊,當(dāng)手指滑動的時候根據(jù)平移量,去具體判斷要展示的視圖是哪一個偎肃。

2.1 左(右)滑動的時候煞烫,從數(shù)組中找到對應(yīng)的下(上)一個展示的頁面添加到自定義視圖上(位置在當(dāng)前頁面的右(左)側(cè)),并隨著滑動一起滑動展現(xiàn)出來累颂。
2.2 停止滑動的時候判斷是要展示哪一個頁面滞详,并將不展示的那一個移除掉。(使用動畫)紊馏。
2.3 這樣的話最多的時候料饥,我們需要兩個展示頁面!最后是只留一個我們看的那個頁面朱监。

3:頂部標(biāo)題我是用了一個比較笨拙的方法岸啡,根據(jù)展示的頁面數(shù),創(chuàng)建對應(yīng)的 Button 根據(jù)標(biāo)題數(shù)組對應(yīng)的給 Title赫编,然后放在一個 ScrollView 上面巡蘸。(有時間可以思考一下奋隶,可以對應(yīng)的去改變展示 Button 名字不必創(chuàng)建那么多,好像一般也不會太多T没摹)

3.1 Button 點擊時候操作邏輯不難唯欣,就是移除當(dāng)前展示的頁面,添加 Button 對應(yīng)的頁面即可搬味。
3.2 滑動手勢結(jié)束的時候根據(jù)展示的頁面境氢,把對應(yīng)的 Button 字體顏色和大小改掉即可(遍歷 ScrollView 的子視圖找到 Button 選中的特殊對待,其他另做對待就好)碰纬。

4:滑塊是個 View萍聊,利用動畫對應(yīng)改變 Frame 就好了!


廢話盡嘀趟,代碼上脐区!
PP_SlipMenuView.h中
  • 聲明供給外界的初始化方法
# 初始話方法:
/*
 ParentVc:       你在哪個試圖控制中使用
 childrenVc:     你要展示那些控制器的 View
 childrenTitles: 每一個展示的視圖的標(biāo)題(Button 的 Title)
 frame:          展示的位置
 numTitleOnePage:每一頁顯示幾個標(biāo)題(頂部Button)
 */
- (instancetype)initWithParentVc:(UIViewController *)parentVc
                      childrenVc:(NSArray<UIViewController *>*)childrenVc
                  childrenTitles:(NSArray<NSString *>*)titles
                           frame:(CGRect)frame
                  numTitleOnePage:(NSInteger)numTitleOnePage;```

-------
######PP_SlipMenuView.m中
- 聲明屬性

```code
{
NSInteger numOnePage; // 頂部展示的標(biāo)題數(shù)量
}

@property (assign, nonatomic) NSInteger currentIndex;// 當(dāng)前展示視圖對應(yīng)的下標(biāo)
@property (strong, nonatomic) NSArray<UIViewController *> *childrenVc;// 子控制器
@property (strong, nonatomic) NSArray<NSString *> *titles; // 標(biāo)題數(shù)組
@property (strong, nonatomic) UIViewController *parentVc;// 父控制器
@property (strong, nonatomic) UIPanGestureRecognizer *pan;// 手勢
@property (strong, nonatomic) UIScrollView *titlesView;// 頂部承載標(biāo)題 Button 容器
@property (strong, nonatomic) UIView *lineView; // 標(biāo)題的下劃線

const float titleHH = 50.0f; // 定義頂部 Button 的高
// 定義一個滑動方向枚舉  具體有使用的地方 定義只為可讀性
typedef enum : NSUInteger
{
    MoveDirectionLeft,
    MoveDirectionRight,
} MoveDirection;

  • 初始化方法實現(xiàn)
- (instancetype)initWithParentVc:(UIViewController *)parentVc childrenVc:(NSArray<UIViewController *> *)childrenVc childrenTitles:(NSArray<NSString *> *)titles frame:(CGRect)frame numTitleOnePage:(NSInteger)numTitleOnePage
{
    // 給對應(yīng)的屬性賦值
    _childrenVc = childrenVc;
    _parentVc = parentVc;
    _titles = titles;
    numOnePage = numTitleOnePage;
    
    if (self = [super initWithFrame:frame])
    {
    // 處理視圖控制器
        // 默認(rèn)顯示第一個控制器的內(nèi)容
        _currentIndex = 0;
        [self insertSubview:_childrenVc[0].view atIndex:0];
       
    // pan手勢處理切換
        _pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panChangeVc:)];
        [self addGestureRecognizer:_pan];
        
    // 頂部控制菜單
        _titlesView = [[UIScrollView alloc] initWithFrame:(CGRectMake(0, 0, kScreenW, titleHH + 20))];
        _titlesView.contentSize = CGSizeMake(kScreenW / numOnePage * _childrenVc.count, titleHH);
        _titlesView.backgroundColor = [UIColor whiteColor];
        _titlesView.bounces = NO;
        _titlesView.showsHorizontalScrollIndicator = NO;
        [self insertSubview:_titlesView atIndex:1];
        
    // 添加標(biāo)題 Button
        [self addTittleButtons];
        // 對應(yīng)的 Button 下面的劃線
         _lineView = [[UIView alloc] initWithFrame:CGRectMake(_currentIndex * kScreenW / numOnePage, titleHH, kScreenW / numOnePage, 20)];
        _lineView.backgroundColor = [UIColor blackColor];
        [_titlesView addSubview:_lineView];
         
    }
    return self;
}
  • 布局 Title Button 就是頂部菜單點擊可以切換
/*
 1: 根據(jù)頁面的多少逐一創(chuàng)建 Button,
 2: tag 值為對應(yīng)的下標(biāo)加1000她按,這樣便于我們根據(jù)對應(yīng)的下標(biāo)去找到對應(yīng)的 Button
 3: 依次設(shè)置在 ScroView 上的位置牛隅,并添加_titlesView(ScrollView 上面)
 */
- (void)addTittleButtons
{
    for (int i = 0; i < _childrenVc.count; i++)
    {
        UIButton *title = [UIButton buttonWithType:(UIButtonTypeCustom)];
        title.backgroundColor = [UIColor grayColor];
        [title setTitle:_titles[i] forState:(UIControlStateNormal)];
# 設(shè)置展示的當(dāng)前頁面和未展示頁面的對應(yīng) Button 字體大小顏色的區(qū)別
        if (i == _currentIndex)
        {
            [title.titleLabel setFont:[UIFont systemFontOfSize:24]];
            [title setTitleColor:[UIColor purpleColor ] forState:(UIControlStateNormal)];
            
        }else{
            [title.titleLabel setFont:[UIFont systemFontOfSize:17]];
            [title setTitleColor:[UIColor whiteColor] forState:(UIControlStateNormal)];
        }
        [title setTitleColor:[UIColor redColor] forState:(UIControlStateHighlighted)];
        [title addTarget:self action:@selector(chooseTitle:) forControlEvents:(UIControlEventTouchUpInside)];
        title.frame = CGRectMake(i * kScreenW / 3.0, 0, kScreenW / 3.0, titleHH);
        title.tag = 1000 + i;
        [_titlesView addSubview:title];
    }
}```

- 點擊標(biāo)題的事件,切換頁面到對應(yīng)的頁面
```code
 - (void)chooseTitle:(UIButton *)sender
{
    // 點擊不是當(dāng)前的標(biāo)題才會去切換
    if (_currentIndex != sender.tag - 1000)
    {
       // 移除當(dāng)前的
        [_childrenVc[_currentIndex].view removeFromSuperview];
    
        //  展示點擊的 設(shè)置 Frame 很關(guān)鍵要  因為手勢的時候回去修改  這里讓其展示在整個屏幕上
        _childrenVc[sender.tag - 1000].view.frame = self.bounds;
        [self insertSubview:_childrenVc[sender.tag - 1000].view atIndex:0];
        _currentIndex = sender.tag - 1000;
    }
    // 移動下劃線
    [self moveLineView];
}
  • 一定下劃線 滑動到當(dāng)前選中的 Button 下面
 - (void)moveLineView
{
    # 用手勢滑動時候 我們既要移動下劃線  也要把對應(yīng)的 Button 的字體顏色大小改了
    for (UIView *view in _titlesView.subviews)
    {
        // 遍歷找到 Button
        if ([view isKindOfClass:[UIButton class]])
        {
            UIButton *button = (UIButton *)view;
            if (button.tag == _currentIndex + 1000)
            {
                [button.titleLabel setFont:[UIFont systemFontOfSize:24]];
                [button setTitleColor:[UIColor purpleColor ] forState:(UIControlStateNormal)];
                
            }else{
                [button.titleLabel setFont:[UIFont systemFontOfSize:17]];
                [button setTitleColor:[UIColor whiteColor] forState:(UIControlStateNormal)];
            }
        }
    }
    // 算好位置 動畫調(diào)整到相應(yīng)的位置
    [UIView animateWithDuration:0.1f animations:^{
        /*
          頁面的寬度 / 一個頁面展示的 Button 數(shù) = Button 的寬度
         當(dāng)前下劃線的 x = 當(dāng)前的下標(biāo) * Button 的寬度
         */
        _lineView.frame = CGRectMake(_currentIndex * kScreenW / numOnePage, titleHH, kScreenW / numOnePage, 20);
    }];
   #下面的兩個判斷是一個展示的關(guān)鍵部分酌泰,我們可以根據(jù)自己的想法調(diào)整媒佣!
     #1、當(dāng)下劃線的x 超過一個屏幕寬的時候陵刹,那么就是說明已經(jīng)到了我們視線之外了默伍,我們要調(diào)整_titlesView的偏移量使之能看見,我這里是讓她保持在最右面衰琐!
     #2也糊、這個判斷是當(dāng)要展示的 Button 在左側(cè)我們視線之外的時候,我們讓_titlesView偏移量為 0讓我們能看見(因為這里只有第一個會出現(xiàn)這樣的情況)
   
 if (_lineView.frame.origin.x >= kScreenW )
    {
        NSLog(@"走這個方法當(dāng)前的下標(biāo)%ld",_currentIndex);
        [_titlesView setContentOffset:CGPointMake( kScreenW / numOnePage * (_currentIndex +1 - numOnePage), 0) animated:YES];
    }
    if (_titlesView.contentOffset.x >= CGRectGetMaxX(_lineView.frame))
    {
        NSLog(@"左滑動 走這個方法當(dāng)前的下標(biāo)%ld",_currentIndex);
        [_titlesView setContentOffset:CGPointMake( kScreenW *0, 0) animated:YES];
    }
}```

- 添加的手勢事件
```code
 - (void)panChangeVc:(UIPanGestureRecognizer *)pan
{
   // 向左滑 x 為負(fù) 向右滑 x 為正  (末位置 減 初始位置)
    CGPoint panOffSet = [pan translationInView:self];
    float changeX = panOffSet.x;
    // 獲取 當(dāng)前的視圖位置
    CGRect frame = _childrenVc[_currentIndex].view.frame;
    // 清空手勢的偏移量
    [_pan setTranslation:(CGPointZero) inView:self];
    // 處理左右視圖
# 這里只是為了臨界點時候任然可以處理所以相應(yīng)  增加減少了一個極小的數(shù)
    float resulet = frame.origin.x + (changeX < 0 ? - DBL_EPSILON : DBL_EPSILON);
# 小于0說明我們左滑了 大于0說明我們右滑了 調(diào)用不同的自定義方法
  ( resulet <=0 ) ?  [self leftScroll:changeX frame:frame] : [self rightScroll:changeX frame:frame] ;
   }```
- 左滑視圖  出現(xiàn)右側(cè)視圖
```code
 - (void)leftScroll:(float)offX frame:(CGRect)frame
{
  
    if (_currentIndex == _childrenVc.count - 1)
    {
        NSLog(@"最后一個左滑 不處理");
        return;
    }
   
    float tempX = frame.origin.x + offX;
    if (_currentIndex == 0)
    {
# 左滑動的時候不松手又往右滑動 在第一個視圖的時候不允許 當(dāng)然也可以設(shè)置成循環(huán)滾動  到最后一個
        tempX = tempX >=0 ? 0 :tempX;
    }
    
    // 移動當(dāng)前的視圖
    _childrenVc[_currentIndex].view.frame = CGRectMake(tempX, frame.origin.y, frame.size.width, frame.size.height);
    
    // 找到下一個頁面
    UIView *subView = _childrenVc[_currentIndex + 1].view;
    
    # 設(shè)置 Frame 很關(guān)鍵  讓他出現(xiàn)當(dāng)前頁面的右側(cè)
    subView.frame = CGRectMake(tempX + frame.size.width, frame.origin.y, frame.size.width, frame.size.height);
    
    if (![self.subviews containsObject:subView])
    {
        [self insertSubview:subView atIndex:0];
    }
    if (_pan.state == UIGestureRecognizerStateEnded)
    {
# 手勢停止的時候確定一下到底要展示哪一個頁面
        MoveDirection result = tempX <= - kScreenW / 2 ? [self leftOut:_childrenVc[_currentIndex].view rightIn:subView] : [self leftIn:_childrenVc[_currentIndex].view  rightOut:subView];
        _currentIndex = result == MoveDirectionLeft ? _currentIndex + 1 : _currentIndex;
        [self moveLineView];// 調(diào)整 Button 文字和下劃線
    }
}
  • 自定義方法判斷手勢結(jié)束的時候要把誰移除
 - (MoveDirection)leftOut:(UIView *)leftView rightIn:(UIView *)rightView
{   /*
    當(dāng)手勢結(jié)束的時候  左邊的視圖移除  右側(cè)的視圖占據(jù)整個屏幕
    */
    [UIView animateWithDuration:0.3f animations:^{
        leftView.frame = CGRectOffset(self.bounds, - kScreenW, 0);
        rightView.frame = self.bounds;
    } completion:^(BOOL finished) {
        [leftView removeFromSuperview];
    }];
    return MoveDirectionLeft;
}
 - (MoveDirection)leftIn:(UIView *)leftView rightOut:(UIView *)rightView
{
    /*
     當(dāng)手勢結(jié)束的時候  左邊的視圖移除  右側(cè)的視圖占據(jù)整個屏幕
     */
    [UIView animateWithDuration:0.3f animations:^{
        rightView.frame = CGRectOffset(self.bounds, kScreenW, 0);
        leftView.frame = self.bounds;
    } completion:^(BOOL finished) {
       [rightView removeFromSuperview];
    }];
    return MoveDirectionRight;
}
  • 右滑視圖 出現(xiàn)左側(cè)視圖 思路和上面一樣
 - (void)rightScroll:(float)offX frame:(CGRect)frame
{
  if (_currentIndex == 0)
    {
        NSLog(@"第一個右滑 不處理");
        return;
    }
    float tempX = frame.origin.x + offX;
    if (_currentIndex == _childrenVc.count - 1)
    {
        tempX = tempX <=0 ? 0 :tempX;
    }
    // 移動當(dāng)前的視圖
    _childrenVc[_currentIndex].view.frame = CGRectMake(tempX, frame.origin.y, frame.size.width, frame.size.height);
    UIView *subView = _childrenVc[_currentIndex - 1].view;
    subView.frame = CGRectMake(tempX - frame.size.width, frame.origin.y, frame.size.width, frame.size.height);  
    if (![self.subviews containsObject:subView])
    {
         [self insertSubview:subView atIndex:0];
    }
    if (_pan.state == UIGestureRecognizerStateEnded)
    {
        MoveDirection result = tempX <=  kScreenW / 2 ? [self leftOut:subView rightIn:_childrenVc[_currentIndex].view] : [self leftIn:subView  rightOut:_childrenVc[_currentIndex].view];
        _currentIndex = result == MoveDirectionLeft ? _currentIndex : _currentIndex - 1;
       [self moveLineView];
    }
}```
----------------
####ViewController.m 引入使用 
```code
NSMutableArray *arraVc = [NSMutableArray new];
    NSMutableArray *arraTitle = [NSMutableArray new];
    for (int i = 0 ; i <= 10 ; i++)
    {
        UIViewController *vc = [UIViewController new];
        vc.view.backgroundColor = [UIColor colorWithRed:(arc4random()%173)/346.0 + 0.5 green:(arc4random()%173)/346.0 + 0.5  blue:(arc4random()%173)/346.0 + 0.5  alpha: 1];
        [arraVc addObject:vc];
        [arraTitle addObject:[NSString stringWithFormat:@"第%d頁",i]];
    }
     PP_SlipMenuView *pp_View = [[PP_SlipMenuView alloc] initWithParentVc:self childrenVc:arraVc childrenTitles:arraTitle frame:self.view.bounds numTitleOnePage:3];
    
    [self.view addSubview:pp_View];
  • 補(bǔ)充一句 子控制器View 添加到自定義 View 上時候羡宙,frame 要注意我這隨便設(shè)置一樣 Bounds 的狸剃,真正使用的時候要考慮顯示問題,會不會被頂部擋坠啡取钞馁!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市匿刮,隨后出現(xiàn)的幾起案子僧凰,更是在濱河造成了極大的恐慌,老刑警劉巖熟丸,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件训措,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)隙弛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進(jìn)店門架馋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人全闷,你說我怎么就攤上這事叉寂。” “怎么了总珠?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵屏鳍,是天一觀的道長。 經(jīng)常有香客問我局服,道長钓瞭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任淫奔,我火速辦了婚禮山涡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘唆迁。我一直安慰自己鸭丛,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布唐责。 她就那樣靜靜地躺著鳞溉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鼠哥。 梳的紋絲不亂的頭發(fā)上熟菲,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天,我揣著相機(jī)與錄音朴恳,去河邊找鬼抄罕。 笑死,一個胖子當(dāng)著我的面吹牛于颖,可吹牛的內(nèi)容都是我干的贞绵。 我是一名探鬼主播,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼恍飘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了谴垫?” 一聲冷哼從身側(cè)響起章母,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎翩剪,沒想到半個月后乳怎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡前弯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年蚪缀,在試婚紗的時候發(fā)現(xiàn)自己被綠了秫逝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡询枚,死狀恐怖违帆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情金蜀,我是刑警寧澤刷后,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站渊抄,受9級特大地震影響尝胆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜护桦,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一含衔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧二庵,春花似錦贪染、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至睡陪,卻和暖如春寺渗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背兰迫。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工信殊, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人汁果。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓涡拘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親据德。 傳聞我的和親對象是個殘疾皇子鳄乏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,860評論 2 361

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