iOS-封裝 Demo--滑動切換多標題菜單

<big><b><i>十年生死兩茫茫,細思量,自難忘!縱使相逢應不識,塵滿面,鬢如霜!<伊布家族></i></b></big>

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

先上圖
滑動切換多標題菜單.gif

簡單說說我的思路

1: 將需要的關鍵數(shù)據(jù)初始化的時候獲?。ㄐ枰敳棵總€頁面對應的標題,還要知道要展示的頁面,各自作為一個數(shù)組存入)。
2: 在自定義的 View 上添加平移手勢,當手指滑動的時候根據(jù)平移量,去具體判斷要展示的視圖是哪一個。

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

3:頂部標題我是用了一個比較笨拙的方法,根據(jù)展示的頁面數(shù),創(chuàng)建對應的 Button 根據(jù)標題數(shù)組對應的給 Title,然后放在一個 ScrollView 上面。(有時間可以思考一下,可以對應的去改變展示 Button 名字不必創(chuàng)建那么多,好像一般也不會太多?。?/p>

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

4:滑塊是個 View,利用動畫對應改變 Frame 就好了!


廢話盡,代碼上!
PP_SlipMenuView.h中
  • 聲明供給外界的初始化方法
# 初始話方法:
/*
 ParentVc:       你在哪個試圖控制中使用
 childrenVc:     你要展示那些控制器的 View
 childrenTitles: 每一個展示的視圖的標題(Button 的 Title)
 frame:          展示的位置
 numTitleOnePage:每一頁顯示幾個標題(頂部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; // 頂部展示的標題數(shù)量
}

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

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
{
    // 給對應的屬性賦值
    _childrenVc = childrenVc;
    _parentVc = parentVc;
    _titles = titles;
    numOnePage = numTitleOnePage;
    
    if (self = [super initWithFrame:frame])
    {
    // 處理視圖控制器
        // 默認顯示第一個控制器的內容
        _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];
        
    // 添加標題 Button
        [self addTittleButtons];
        // 對應的 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 值為對應的下標加1000,這樣便于我們根據(jù)對應的下標去找到對應的 Button
 3: 依次設置在 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)];
# 設置展示的當前頁面和未展示頁面的對應 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];
    }
}```

- 點擊標題的事件,切換頁面到對應的頁面
```code
 - (void)chooseTitle:(UIButton *)sender
{
    // 點擊不是當前的標題才會去切換
    if (_currentIndex != sender.tag - 1000)
    {
       // 移除當前的
        [_childrenVc[_currentIndex].view removeFromSuperview];
    
        //  展示點擊的 設置 Frame 很關鍵要  因為手勢的時候回去修改  這里讓其展示在整個屏幕上
        _childrenVc[sender.tag - 1000].view.frame = self.bounds;
        [self insertSubview:_childrenVc[sender.tag - 1000].view atIndex:0];
        _currentIndex = sender.tag - 1000;
    }
    // 移動下劃線
    [self moveLineView];
}
  • 一定下劃線 滑動到當前選中的 Button 下面
 - (void)moveLineView
{
    # 用手勢滑動時候 我們既要移動下劃線  也要把對應的 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)];
            }
        }
    }
    // 算好位置 動畫調整到相應的位置
    [UIView animateWithDuration:0.1f animations:^{
        /*
          頁面的寬度 / 一個頁面展示的 Button 數(shù) = Button 的寬度
         當前下劃線的 x = 當前的下標 * Button 的寬度
         */
        _lineView.frame = CGRectMake(_currentIndex * kScreenW / numOnePage, titleHH, kScreenW / numOnePage, 20);
    }];
   #下面的兩個判斷是一個展示的關鍵部分,我們可以根據(jù)自己的想法調整!
     #1、當下劃線的x 超過一個屏幕寬的時候,那么就是說明已經到了我們視線之外了,我們要調整_titlesView的偏移量使之能看見,我這里是讓她保持在最右面!
     #2、這個判斷是當要展示的 Button 在左側我們視線之外的時候,我們讓_titlesView偏移量為 0讓我們能看見(因為這里只有第一個會出現(xiàn)這樣的情況)
   
 if (_lineView.frame.origin.x >= kScreenW )
    {
        NSLog(@"走這個方法當前的下標%ld",_currentIndex);
        [_titlesView setContentOffset:CGPointMake( kScreenW / numOnePage * (_currentIndex +1 - numOnePage), 0) animated:YES];
    }
    if (_titlesView.contentOffset.x >= CGRectGetMaxX(_lineView.frame))
    {
        NSLog(@"左滑動 走這個方法當前的下標%ld",_currentIndex);
        [_titlesView setContentOffset:CGPointMake( kScreenW *0, 0) animated:YES];
    }
}```

- 添加的手勢事件
```code
 - (void)panChangeVc:(UIPanGestureRecognizer *)pan
{
   // 向左滑 x 為負 向右滑 x 為正  (末位置 減 初始位置)
    CGPoint panOffSet = [pan translationInView:self];
    float changeX = panOffSet.x;
    // 獲取 當前的視圖位置
    CGRect frame = _childrenVc[_currentIndex].view.frame;
    // 清空手勢的偏移量
    [_pan setTranslation:(CGPointZero) inView:self];
    // 處理左右視圖
# 這里只是為了臨界點時候任然可以處理所以相應  增加減少了一個極小的數(shù)
    float resulet = frame.origin.x + (changeX < 0 ? - DBL_EPSILON : DBL_EPSILON);
# 小于0說明我們左滑了 大于0說明我們右滑了 調用不同的自定義方法
  ( resulet <=0 ) ?  [self leftScroll:changeX frame:frame] : [self rightScroll:changeX frame:frame] ;
   }```
- 左滑視圖  出現(xiàn)右側視圖
```code
 - (void)leftScroll:(float)offX frame:(CGRect)frame
{
  
    if (_currentIndex == _childrenVc.count - 1)
    {
        NSLog(@"最后一個左滑 不處理");
        return;
    }
   
    float tempX = frame.origin.x + offX;
    if (_currentIndex == 0)
    {
# 左滑動的時候不松手又往右滑動 在第一個視圖的時候不允許 當然也可以設置成循環(huán)滾動  到最后一個
        tempX = tempX >=0 ? 0 :tempX;
    }
    
    // 移動當前的視圖
    _childrenVc[_currentIndex].view.frame = CGRectMake(tempX, frame.origin.y, frame.size.width, frame.size.height);
    
    // 找到下一個頁面
    UIView *subView = _childrenVc[_currentIndex + 1].view;
    
    # 設置 Frame 很關鍵  讓他出現(xiàn)當前頁面的右側
    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];// 調整 Button 文字和下劃線
    }
}
  • 自定義方法判斷手勢結束的時候要把誰移除
 - (MoveDirection)leftOut:(UIView *)leftView rightIn:(UIView *)rightView
{   /*
    當手勢結束的時候  左邊的視圖移除  右側的視圖占據(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
{
    /*
     當手勢結束的時候  左邊的視圖移除  右側的視圖占據(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)左側視圖 思路和上面一樣
 - (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;
    }
    // 移動當前的視圖
    _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];
  • 補充一句 子控制器View 添加到自定義 View 上時候,frame 要注意我這隨便設置一樣 Bounds 的,真正使用的時候要考慮顯示問題,會不會被頂部擋住!
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容