iOS_21團購_頂部菜單和彈出菜單聯動 – iPhone手機開發技術文章 iPhone軟體開發教學課程

最終效果圖:

vcD4KPHA+PGJyPgo8L3A+CjxwPjxicj4KPC9wPgo8cD6497/YvP652M+1zbwxOjwvcD4KPHA+PGltZyBzcmM9″https://www.aiwalls.com/uploadfile/Collfiles/20140818/20140818084919103.jpg” alt=”\”>\

各控件關系圖2:

點擊Dock上面的按鈕DockItem,

創建經導航控制器包裝的DealListController,

並且添加到主控制器的右側空間

//
//  DealListController.m
//  帥哥_團購
//
//  Created by beyond on 14-8-14.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  點擊dock上面的【團購】按鈕對應的控制器,上面是導航欄,導航欄右邊是searchBar,導航欄左邊是一個大按鈕(TopMenu)(內部由三個小按鈕組成)

#import "DealListController.h"
// 導航欄左邊是一個大按鈕(頂部菜單)
#import "TopMenu.h"

@interface DealListController ()

@end

@implementation DealListController


- (void)viewDidLoad
{
    [super viewDidLoad];
    // 1,設置上方的導航欄,右邊是搜索bar,左邊是一個大的VIEW(內有三個按鈕),即TopMenu,內部的按鈕是TopMenuItem
    [self addNaviBarBtn];
    
}
// 1,設置上方的導航欄,右邊是搜索bar,左邊是一個大的VIEW(內有三個按鈕),即TopMenu,內部的按鈕是TopMenuItem
- (void)addNaviBarBtn
{

    
    // 1.右邊的搜索框
    UISearchBar *s = [[UISearchBar alloc] init];
    s.frame = CGRectMake(0, 0, 210, 35);
    s.placeholder = @"請輸入商品名、地址等";
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:s];
    
    // 2.左邊的菜單欄,導航欄左邊是一個大按鈕(頂部菜單)
    TopMenu *topMenu = [[TopMenu alloc] init];
    // 3.用於點擊頂部按鈕時,容納創建出來的底部彈出菜單(包括一個contentView和cover,contentView又包括scrollView和subTitleImgView),本成員是由創建此TopMenu的外部賦值傳入, 這裡是控制器的view,就是導航欄下面的所有區域
    // 重要~~~~~~~~~~
    topMenu.controllerView = self.view;

    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:topMenu];
}
@end

TopMenu.h

//
//  TopMenu.h
//  帥哥_團購
//
//  Created by beyond on 14-8-15.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  點擊dock上面的【團購】按鈕時,創建出對應的經導航包裝後的子控制器,子控制器的上面是導航欄,導航欄右邊是searchBar,導航欄左邊是一個大按鈕(TopMenu)(內部隻由三個小按鈕組成它們分別是:全部頻道,全部商區,默認排序),點擊TopMenu中的某一個按鈕,會在其下方,彈出一個PopMenu,PopMenu包括二個部分(上面是一個contentView:包括:scrollView和subTitleImgView,下:蒙板)

#import 

@interface TopMenu : UIView


//  用於點擊頂部菜單項時,容納創建出來的底部彈出菜單(包括一個contentView和cover,contentView又包括scrollView和subTitleImgView),本成員是由創建此TopMenu的控制器賦值傳入, 本成員屬性是用來接收控制器的view,就是導航欄下面的所有區域,目的是用於添加並展示PopMenu
@property (nonatomic, weak) UIView *controllerView;
@end

TopMenu.m

負責創建和添加3個TopMenuItem,

監聽其內部三個TopMenuItem的點擊事件,

並且根據點擊的按鈕的tag不同,創建出不同的PopMenu,並控制PopMenu的出現和隱藏,最後一個是註冊到通知中心,監聽所有通知,並且設置三個TopMenuItem的顯示文字

//
//  TopMenu.m
//  帥哥_團購
//
//  Created by beyond on 14-8-15.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  點擊dock上面的【團購】按鈕對應的控制器,上面是導航欄,導航欄右邊是searchBar,導航欄左邊是一個大按鈕(TopMenu)(內部隻由三個小按鈕組成它們分別是:全部頻道,全部商區,默認排序),點擊TopMenu中的某一個按鈕,會在其下方,彈出一個PopMenu,PopMenu包括二個部分(上面是一個contentView:包括:scrollView和subTitleImgView,下:蒙板)

#import "TopMenu.h"


#import "TopMenuItem.h"
#import "CategoryPopMenu.h"
#import "DistrictPopMenu.h"
#import "OrderPopMenu.h"
#import "MetaDataTool.h"
#import "Order.h"

@interface TopMenu()
{
    // 三個頂部菜單項中當前選中的那一個,三個按鈕:全部分類,全部商區,默認排序
    TopMenuItem *_currentTopMenuItem;
    
    
    
// 在點擊不同的頂部菜單項的時候,因為要控制其對應的底部彈出菜單的出現和隱藏,因此,在創建它時,就要用成員變量,記住 彈出的分類菜單
    CategoryPopMenu *_categoryPopMenu;
// 在點擊不同的頂部菜單項的時候,因為要控制其對應的底部彈出菜單的出現和隱藏,因此,在創建它時,就要用成員變量,記住 彈出的區域菜單
    DistrictPopMenu *_districtPopMenu;
// 在點擊不同的頂部菜單項的時候,因為要控制其對應的底部彈出菜單的出現和隱藏,因此,在創建它時,就要用成員變量,記住 彈出的排序菜單
    OrderPopMenu *_orderPopMenu;
    
    
// 正在展示的底部彈出菜單,是個父類
    PopMenu *_showingPopMenu;
    
    
    

    // 因為要更改其顯示文字,所以要成員變量記住創建出來的 分類菜單項
    TopMenuItem *_categoryTopMenuItem;
    // 因為要更改其顯示文字,所以要成員變量記住創建出來的 區域菜單項
    TopMenuItem *_districtTopMenuItem;
    // 因為要更改其顯示文字,所以要成員變量記住創建出來的 排序菜單項
    TopMenuItem *_orderTopMenuItem;
}
@end


@implementation TopMenu


- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        
        // 1.添加一個按鈕:全部分類 (因為要更改其顯示文字,所以要成員變量記住)
        _categoryTopMenuItem = [self addMenuItem:kAllCategory index:0];
        
        // 2.添加一個按鈕:全部商區 (因為要更改其顯示文字,所以要成員變量記住)
        _districtTopMenuItem = [self addMenuItem:kAllDistrict index:1];
        
        // 3.添加一個按鈕:默認排序 (因為要更改其顯示文字,所以要成員變量記住)
        _orderTopMenuItem = [self addMenuItem:@"默認排序" index:2];
        
        // 4.頂部菜單需要註冊並監聽所有通知,目的是更改其裡面菜單項的文字,並且控制彈出菜單的顯示和隱藏
        kAddAllNotes(dataChange)
    }
    return self;
}
// 5,抽取的方法,添加一個頂部菜單項(TopMenuItem),三個按鈕:全部分類,全部商區,默認排序
- (TopMenuItem *)addMenuItem:(NSString *)title index:(int)index
{
    TopMenuItem *item = [[TopMenuItem alloc] init];
    item.title = title;
    item.tag = index;
    // 三個按鈕:全部分類,全部商區,默認排序  水平排列
    item.frame = CGRectMake(kTopMenuItemW * index, 0, 0, 0);
    // 重要~~~頂部按鈕(三個按鈕:全部分類,全部商區,默認排序)被點擊之後,都會調用此方法,根據tag進行區分,以便彈出不同的PopMenu
    [item addTarget:self action:@selector(topMenuItemClick:) forControlEvents:UIControlEventTouchUpInside];
    [self addSubview:item];
    return item;
}

// 4.註冊並監聽所有通知時調用此方法,更改按鈕的文字,控制彈出菜單顯示和隱藏
- (void)dataChange
{
    // 0.取消當前TopMenuItem的選中狀態,並且置空_currentTopMenuItem ,因為監聽到通知時,肯定是用戶點擊瞭一個子標題按鈕,或者一個沒有子標題的PopMenuItem
    _currentTopMenuItem.selected = NO;
    _currentTopMenuItem = nil;
    
    // 1.設置 分類按鈕 要顯示的文字,從工具中獲得
    NSString *c = [MetaDataTool sharedMetaDataTool].currentCategoryName;
    if (c) {
        _categoryTopMenuItem.title = c;
    }
    
    // 2.設置 商區按鈕 要顯示的文字,從工具中獲得
    NSString *d = [MetaDataTool sharedMetaDataTool].currentDistrictName;
    if (d) {
        _districtTopMenuItem.title = d;
    }
    
    // 3.設置 排序按鈕 要顯示的文字,從工具中獲得
    NSString *o = [MetaDataTool sharedMetaDataTool].currentOrder.name;
    if (o) {
        _orderTopMenuItem.title = o;
    }
    
    // 4.最後,調用正在顯示的彈出菜單的方法:隱藏底部彈出菜單,並置空正在顯示的彈出菜單
    [_showingPopMenu hidePopMenu];
    _showingPopMenu = nil;
}



// 5-1,重要~~~監聽頂部菜單項的點擊,
// 頂部按鈕(三個按鈕:全部分類,全部商區,默認排序)被點擊之後,都會調用此方法,根據tag進行區分,以便彈出不同的PopMenu
- (void)topMenuItemClick:(TopMenuItem *)item
{
    // 0.如果還沒有選擇好城市,則不允許點擊頂部菜單按鈕
    if ([MetaDataTool sharedMetaDataTool].currentCity == nil) return;
    // 1.控制選中狀態的切換,先把前面記住的當前頂部菜單項取消選中
    _currentTopMenuItem.selected = NO;
    // 如果兩次點擊的是同一個頂部菜單項,則隱藏掉彈出的菜單,並且置空當前的選中TopMenuItem
    if (_currentTopMenuItem == item) {
        _currentTopMenuItem = nil;
        // 隱藏底部菜單
        [self hidePopMenu];
    } else {
        // 如果兩次點擊的是是不同的頂部菜單項,將TopMenuItem置為選中狀態,且用成員變量記住,並且展示相應的底部彈出菜單
        item.selected = YES;
        _currentTopMenuItem = item;
        // 顯示與頂部菜單項,對應的底部彈出菜單
        [self showPopMenu:item];
    }
}
// 5-2,點擊瞭相同的TopMenuItem,要隱藏底部已經彈出的菜單,並且置其為空
- (void)hidePopMenu
{
    // 調用_showingPopMenu其自己的方法,隱藏並移除其內部的contentView(包括scrollView和subTitleImgView,並置cover透明),並置空_showingPopMenu
    [_showingPopMenu hidePopMenu];
    _showingPopMenu = nil;
}

// 5-3,點擊瞭不同的TopMenuItem,要顯示相應的底部彈出菜單
- (void)showPopMenu:(TopMenuItem *)item
{
    // 1,先判斷 是否需要讓彈出菜單執行出場動畫(沒有正在展示的彈出菜單時,才需要執行出場動畫)
    BOOL animted;
    // 如果有正在顯示的彈出菜單,如切換按鈕點擊的時候
    if (_showingPopMenu) {
        // 1-1,先移除當前正在顯示的彈出菜單
        [_showingPopMenu removeFromSuperview];
        // 1-2,不要動畫出場
        animted = NO;
    }else{
        // 沒有正在展示的彈出菜單時,才需要執行出場動畫
        animted = YES;
    }
    
    // 2,根據點擊的頂部菜單項的tag,創建並顯示不同的底部彈出菜單(三個按鈕:全部分類,全部商區,默認排序)
    if (item.tag == 0) {
        // 創建分類彈出菜單,並且用成員記住,且置其為正在展示的PopMenu
        if (_categoryPopMenu == nil) {
            _categoryPopMenu = [[CategoryPopMenu alloc] init];
        }
        _showingPopMenu = _categoryPopMenu;
    } else if (item.tag == 1) { // 區域
        // 創建商區彈出菜單,並且用成員記住,且置其為正在展示的PopMenu
        if (_districtPopMenu == nil) {
            _districtPopMenu = [[DistrictPopMenu alloc] init];
        }
        _showingPopMenu = _districtPopMenu;
    } else {
        // 創建 排序彈出菜單,並且用成員記住,且置其為正在展示的PopMenu
        if (_orderPopMenu == nil) {
            _orderPopMenu = [[OrderPopMenu alloc] init];
        }
        _showingPopMenu = _orderPopMenu;
    }
    
    // 創建出來相應的底部彈出菜單後,就要設置_showingPopMenu   的frame
    // _showingPopMenu.frame = _controllerView.bounds;
    
    // PopMenu 占據導航欄以下所有的空間
    _showingPopMenu.frame = (CGRect){0,kTopMenuItemH-1,_controllerView.bounds.size.width,_controllerView.bounds.size.height- kTopMenuItemH};
    
    
    
    // 設置創建出來的PopMenu的block回調,傳遞的是XXXPopMenu隱藏後,頂部菜單要做的事情如更改頂部的TopMenu的按鈕選中狀態
    __unsafe_unretained TopMenu *menu = self;
    _showingPopMenu.hideBlock = ^{
        // 重要~~~當_showingPopMenu隱藏後,要更改頂部的TopMenu的按鈕選中狀態
        
        // 1.取消當前的TopMenuItem的選中狀態,並置空
        menu->_currentTopMenuItem.selected = NO;
        menu->_currentTopMenuItem = nil;
        
        // 2._showingPopMenu隱藏後,就要清空_showingPopMenu
        menu->_showingPopMenu = nil;
    };
    
    // 添加創建出來的即將要顯示的彈出菜單 到_controllerView(即導航欄下面的所有空間都是彈出菜單的)
    [_controllerView addSubview:_showingPopMenu];
    
    // 執行剛才創建出來的底部彈出菜單的 出場動畫,註意:隻有沒有正在展示的彈出菜單時,才需要執行出場動畫)
    if (animted) {
        [_showingPopMenu showPopMenu];
    }
}



// 頂部菜單寬度固定是三個按鈕的寬高,因為隻有三個按鈕:全部分類,全部商區,默認排序
- (void)setFrame:(CGRect)frame
{
    frame.size = CGSizeMake(3 * kTopMenuItemW, kTopMenuItemH);
    [super setFrame:frame];
}
// 頂部菜單因為要改變其三個按鈕的文字,因此在通知中心註冊成為瞭監聽者,因此dealloc時要在通知中心,移除掉監聽者
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end

TopMenuItem 頂部菜單項按鈕

//
//  TopMenuItem.m
//  帥哥_團購
//
//  Created by beyond on 14-8-15.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  點擊dock上面的【團購】按鈕對應的控制器,上面是導航欄,導航欄右邊是searchBar,導航欄左邊是一個大按鈕(TopMenu)(內部隻由三個小按鈕組成它們分別是:全部頻道,全部商區,默認排序),點擊TopMenu中的某一個按鈕,會在其下方,彈出一個PopMenu,PopMenu包括二個部分(上面是一個contentView:包括:scrollView和subTitleImgView,下:蒙板)






#import 

@interface TopMenuItem : UIButton
// 設置按鈕TopMenuItem顯示的文字
@property (nonatomic, copy) NSString *title;
@end


// 左文字 如全部分類 、全部商區、默認排序
#define kTitleScale 0.8
@implementation TopMenuItem


- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // 1.文字顏色
        [self setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        self.titleLabel.textAlignment = NSTextAlignmentCenter;
        self.titleLabel.font = [UIFont systemFontOfSize:15];
        
        // 2.設置按鈕右邊的箭頭
        [self setImage:[UIImage imageNamed:@"ic_arrow_down.png"] forState:UIControlStateNormal];
        self.imageView.contentMode = UIViewContentModeCenter;
        
        // 3.設置按鈕右邊的分割線
        UIImage *img = [UIImage imageNamed:@"separator_topbar_item.png"];
        UIImageView *pider = [[UIImageView alloc] initWithImage:img];
        pider.bounds = CGRectMake(0, 0, 2, kTopMenuItemH * 0.7);
        pider.center = CGPointMake(kTopMenuItemW, kTopMenuItemH * 0.5);
        [self addSubview:pider];
        
        // 4.設置按鈕選中時的背景
        [self setBackgroundImage:[UIImage imageStretchedWithName:@"slider_filter_bg_normal.png"] forState:UIControlStateSelected];
    }
    return self;
}

- (void)setTitle:(NSString *)title
{
    _title = title;
    
    [self setTitle:title forState:UIControlStateNormal];
}
// 自己固定頂部菜單項按鈕的好寬高
- (void)setFrame:(CGRect)frame
{
    frame.size = CGSizeMake(kTopMenuItemW, kTopMenuItemH);
    [super setFrame:frame];
}
// 左文字的frame
- (CGRect)titleRectForContentRect:(CGRect)contentRect
{
    CGFloat h = contentRect.size.height;
    CGFloat w = contentRect.size.width * kTitleScale;
    return CGRectMake(0, 0, w, h);
}
// 右圖片的frame
- (CGRect)imageRectForContentRect:(CGRect)contentRect
{
    CGFloat h = contentRect.size.height;
    CGFloat x = contentRect.size.width * kTitleScale;
    CGFloat w = contentRect.size.width - x;
    return CGRectMake(x, 0, w, h);
}



@end

PopMenu.h

//
//  PopMenu.h
//  帥哥_團購
//
//  Created by beyond on 14-8-15.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  PopMenu是點擊頂部按鈕項,在其下方彈出的菜單的父類,成員有:下面是一個cover蒙板,上面是一個contentView(包含著scrollView和subTitleImgView,其中scrollView裡面放的全是PopMenuItem,如美食,如海淀區.....subTitleImgView裡面放的全是美食下面的所有子標題,如川菜 湘菜 粵菜)



//  點擊dock上面的【團購】按鈕,創建一個經過導航包裝的DealList控制器,控制器的上面是導航欄,導航欄右邊是searchBar,導航欄左邊是一個大按鈕(TopMenu)(內部由三個小按鈕組成),點擊TopMenu中的某一個按鈕,會在其下方,彈出一個PopMenu,PopMenu包括二個部分,見上面

#import 
#import "SubTitleImgViewDelegate.h"
@class SubTitleImgView, PopMenuItem;

@interface PopMenu : UIView 
{
    // 以下成員是開放給子類訪問和修改的
    
    // 容納所有的分類或商區,如美食,如海淀區
    UIScrollView *_scrollView;
    
    // 容納所有子標題的ImgView,裡面全是一個個按鈕,如美食下面的川菜、湘菜、粵菜等
    SubTitleImgView *_subTitleImgView;
    
    // item的父類,彈出菜單項:記錄當前選中的菜單項,如美食,如海淀區(此是父類)
    PopMenuItem *_selectedPopMenuItem;
}

// 彈出菜單隱藏完畢之後,要通知頂部菜單
@property (nonatomic, copy) void (^hideBlock)();

// 供外部調用,通過動畫顯示 PopMenu
- (void)showPopMenu;
// 供外部調用,通過動畫隱藏 PopMenu
- (void)hidePopMenu;


@end

PopMenu.m


創建並添加一個cover,一個ContentView,並向ContentView添加一個ScrollView


//
//  PopMenu.m
//  帥哥_團購
//
//  Created by beyond on 14-8-15.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  PopMenu是點擊頂部按鈕項,在其下方彈出的菜單的父類,成員有:下面是一個cover蒙板,上面是一個contentView(包含著scrollView和subTitleImgView,其中scrollView裡面放的全是PopMenuItem,如美食,如海淀區.....subTitleImgView裡面放的全是美食下面的所有子標題,如川菜 湘菜 粵菜)



//  點擊dock上面的【團購】按鈕,創建一個經過導航包裝的DealList控制器,控制器的上面是導航欄,導航欄右邊是searchBar,導航欄左邊是一個大按鈕(TopMenu)(內部由三個小按鈕組成),點擊TopMenu中的某一個按鈕,會在其下方,彈出一個PopMenu,PopMenu包括二個部分,見上面

#import "PopMenu.h"
// 遮罩
#import "Cover.h"


// subTitleImgView裡面放的全是美食下面的所有子標題,如川菜湘菜粵菜
#import "SubTitleImgView.h"
// scrollView裡面放的全是PopMenuItem,如美食,如海淀區
#import "PopMenuItem.h"
#import "MetaDataTool.h"




#import "CategoryPopMenuItem.h"
#import "DistrictPopMenuItem.h"
#import "OrderPopMenuItem.h"




@interface PopMenu()
{
    // 上面是_contentView,包括scrollView和subTitleImgView,其中scrollView裡面放的全是PopMenuItem,如美食,如海淀區.....subTitleImgView裡面放的全是美食下面的所有子標題,如川菜湘菜粵菜
    UIView *_contentView;
    // 遮罩
    Cover *_cover;
    
}
@end

@implementation PopMenu

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // 0.為適應橫豎屏的變化,設置self PopMenu寬高自動伸縮
        self.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
        
        // 1.添加蒙板(遮蓋),並且點擊蒙板後,隱藏並移除popMenu的contentView(內部是scrollView和subTitleImgView),並置cover透明
        [self addCover];
        
        // 2.添加內容view(內部是scrollView和subTitleImgView)
        [self addContentView];
        
        // 3.添加ScrollView到contentView
        [self addScrollView];
        
    }
    return self;
}


// 1.添加蒙板(遮蓋),並且點擊蒙板後,隱藏並移除popMenu的contentView(內部是scrollView和subTitleImgView),並置cover透明
- (void)addCover
{
    _cover = [Cover coverWithTarget:self action:@selector(hideContentView)];
    _cover.frame = self.bounds;
    [self addSubview:_cover];
}

// 2.添加內容view(內部是scrollView和subTitleImgView)
- (void)addContentView
{
    
    _contentView = [[UIView alloc] init];
    // 默認高度是一個popMenuItem高度
    _contentView.frame = CGRectMake(0, 0, self.frame.size.width, kBottomMenuItemH);
    // 寬度伸縮,但是高度其內部通過數據源的多少自動計算行數及總高度
    _contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    [self addSubview:_contentView];
}

// 3.添加ScrollView到contentView
-(void)addScrollView
{
    _scrollView = [[UIScrollView alloc] init];
    _scrollView.showsHorizontalScrollIndicator = NO;
    // 寬高伸縮,高度固定死為一個popMenuItem高度
    _scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    _scrollView.frame = CGRectMake(0, 0, self.frame.size.width, kBottomMenuItemH);
    _scrollView.backgroundColor = [UIColor whiteColor];
    [_contentView addSubview:_scrollView];
}

#pragma mark - 父類接口方法
// 本父類方法的作用:控制popMenuItem的狀態切換,如果popMenuItem有子標題(如美食),顯示子標題showSubTitleImgView,如果沒有子標題(如電影),就可隱藏掉彈出按鈕瞭
- (void)popMenuItemClicked:(PopMenuItem *)item
{
    // 父類提供的一個接口方法,當它子類中的MenuItem addTarget為 popMenuItemClicked時,如果子類 沒有實現popMenuItemClicked方法,就會到父類這兒來找popMenuItemClicked方法,
    // 因此,本方法的目的是:監聽所有它的子類(如CategoryPopMenu,DistrictPopMenu)的菜單項的點擊
    
    
    // 1.控制item的狀態切換
    _selectedPopMenuItem.selected = NO;
    item.selected = YES;
    _selectedPopMenuItem = item;
    
    // 2.查看是菜單項,如美食,如海淀區  否有子分類,如果有子分類才要顯示SubTitleImgView,並為其提供數據源,子標題文字組成的數組
    if (item.subTitlesArr.count) {
        // 有子標題,才要動畫顯示所有的子標題
        [self showSubTitleImgView:item];
    } else { // 因為沒有子標題,所以隱藏所有的子標題,並且就可以直接設置當前Category或District或Order為剛才點擊的PopMenuItem
        [self hideSubTitleImgView:item];
    }
}

// 如果被點擊的popMenuItem有子標題,才要創建並且動畫顯示SubTitleImgView,並為其提供數據源,即子標題文字組成的數組
- (void)showSubTitleImgView:(PopMenuItem *)item
{
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:kDefaultAnimDuration];
    // 所有的PopMenu子類(如CategoryPopMenu和DistrictPopMenu和OrderPopMenu)共用一個_subTitleImgView,展示子分類的所有子標題
    if (_subTitleImgView == nil) {
        _subTitleImgView = [[SubTitleImgView alloc] init];
        // 設置self PopMenu為_subTitleImgView的代理目的是:兩個,當點擊_subTitleImgView裡面的按鈕時,獲知按鈕的標題,另外一個就是告訴_subTitleImgView當前選中的PopMenu是哪一個類別,是美食?還是海淀區???
        // 並且代理方法,由相應的子類(如CategoryPopMenu和DistrictPopMenu和OrderPopMenu)去實現
        _subTitleImgView.delegate = self;
    }
    
    // 設置子標題的frame,y位於scrollView的下方,高度?????
    _subTitleImgView.frame = CGRectMake(0, kBottomMenuItemH, self.frame.size.width, _subTitleImgView.frame.size.height);
    // 設置子標題的主標題 ????
    _subTitleImgView.mainTitle = [item titleForState:UIControlStateNormal];
    // 設置子標題需要顯示的內容(帶去要顯示的數據源,即所有的子標題組成的數組)
    // 重要~~~供子類 去實現的,內部會攔截此方法,添加所有的子標題按鈕,並設置文字
    _subTitleImgView.titleArr = item.subTitlesArr;
    
    // 當前子標題沒有正在展示的時候,就需要執行動畫顯示 _subTitleImgView
    if (_subTitleImgView.superview == nil) {
        [_subTitleImgView showSubTitleImgViewWithAnimation];
    }
    
    // 添加子標題到內容view-scrollView底部
    [_contentView insertSubview:_subTitleImgView belowSubview:_scrollView];
    
    // 重要~根據_subTitleImgView不同的高度,調整整個contentView的高度~~~
    CGRect cf = _contentView.frame;
    cf.size.height = CGRectGetMaxY(_subTitleImgView.frame);
    _contentView.frame = cf;
    
    [UIView commitAnimations];
}


// 因為如果被點擊的popMenuItem沒有子標題,所以隱藏所有的子標題,並且就可以直接設置當前Category或District或Order為剛才點擊的PopMenuItem
- (void)hideSubTitleImgView:(PopMenuItem *)item
{
    // 1.通過動畫隱藏子標題
    if (_subTitleImgView) {
        [_subTitleImgView hideSubTitleImgViewWithAnimation];
    }
    
    // 2.移除後,必須重新調整contentView的高度為默認的一個PopMenuItem的高度
    CGRect cf = _contentView.frame;
    cf.size.height = kBottomMenuItemH;
    _contentView.frame = cf;
    
    // 3.因為沒有子標題,所以就可以直接設置工具類中的當前Category或District或Order為剛才點擊的PopMenuItem,工具類內部會攔截,並發出通知,通知給TopMenu等
    NSString *title = [item titleForState:UIControlStateNormal];
    if ([item isKindOfClass:[CategoryPopMenuItem class]]) {
        // 如果點擊的PopMenuItem是 分類PopMenuItem
        [MetaDataTool sharedMetaDataTool].currentCategoryName = title;
    } else if ([item isKindOfClass:[DistrictPopMenuItem class]]) {
        // 如果點擊的PopMenuItem是 商區PopMenuItem
        [MetaDataTool sharedMetaDataTool].currentDistrictName = title;
    } else {
        // 如果點擊的PopMenuItem是 排序PopMenuItem
        [MetaDataTool sharedMetaDataTool].currentOrder = [[MetaDataTool sharedMetaDataTool] orderWithName:title];
    }
}



#pragma mark 顯示ContentView,供外部調用,如點擊瞭TopMenu時調用,且當前沒有PopMenu正在顯示
- (void)showPopMenu
{
    _contentView.transform = CGAffineTransformMakeTranslation(0, -_contentView.frame.size.height);
    _contentView.alpha = 0;
    _cover.alpha = 0;
    [UIView animateWithDuration:kDefaultAnimDuration animations:^{
        // 1.scrollView從上面 -> 下面
        _contentView.transform = CGAffineTransformIdentity;
        _contentView.alpha = 1;
        
        // 2.遮蓋(0 -> 0.4)
        [_cover alphaReset];
    }];
}
#pragma mark 隱藏ContentView,供外部調用,如點擊瞭Cover或同一個TopMenuItem時調用,且當前沒有PopMenu正在顯示
// 如點擊遮蓋時,隱藏並移除popMenu的contentView(內部是scrollView和subTitleImgView),並置cover透明
- (void)hidePopMenu
{
    // 如果隱藏完畢彈出菜單的 _contentView之後,要通知調用者(頂部菜單)更改頂部菜單項文字
    if (_hideBlock) {
        _hideBlock();
    }
    [UIView animateWithDuration:kDefaultAnimDuration animations:^{
        // _contentView向上消失,即移動一個自身的高度
        _contentView.transform = CGAffineTransformMakeTranslation(0, -_contentView.frame.size.height);
        _contentView.alpha = 0;

        // 2.遮蓋(0.4 -> 0)
        _cover.alpha = 0;
    } completion:^(BOOL finished) {
        // _contentView完全看不見瞭之後,就將彈出菜單從父控件中移除
        [self removeFromSuperview];
        
        // 並且恢復_contentView的屬性
        _contentView.transform = CGAffineTransformIdentity;
        _contentView.alpha  = 1;
        [_cover alphaReset];
    }];
}

@end

PopMenuItem.h

//
//  PopMenuItem.h
//  帥哥_團購
//
//  Created by beyond on 14-8-16.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  底部彈出菜單的菜單項 (是一個父類)  抽取的特征:1,右邊有分隔線,2.寬高全統一,3.選中時,背景圖片統一,4文字顏色

#import 

@interface PopMenuItem : UIButton


// 本接口,專門交給子類實現
// 數據源,子標題數組,所有子標題的名字組成的數組,
// 比如 美食 下面有多少個category
// 比如 海淀區 下面有多少個place
- (NSArray *)subTitlesArr;
@end

PopMenuItem.m

//
//  PopMenuItem.m
//  帥哥_團購
//
//  Created by beyond on 14-8-16.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  底部彈出菜單的菜單項 (是一個父類)  抽取的特征:1,右邊有分隔線,2.寬高全統一,3.選中時,背景圖片統一,4文字顏色

#import "PopMenuItem.h"

@implementation PopMenuItem



- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // 1.右邊的分割線
        UIImage *img = [UIImage imageNamed:@"separator_filter_item.png"];
        UIImageView *pider = [[UIImageView alloc] initWithImage:img];
        pider.bounds = CGRectMake(0, 0, 2, kBottomMenuItemH * 0.7);
        pider.center = CGPointMake(kBottomMenuItemW, kBottomMenuItemH * 0.5);
        [self addSubview:pider];
        
        // 2.文字顏色
        [self setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        self.titleLabel.font = [UIFont systemFontOfSize:16];
        
        // 3.設置被選中時的背景
        [self setBackgroundImage:[UIImage imageStretchedWithName:@"bg_filter_toggle_hl.png"] forState:UIControlStateSelected];
    }
    return self;
}

// 菜單項的寬高固定為一個按鈕的寬和高
- (void)setFrame:(CGRect)frame
{
    frame.size = CGSizeMake(kBottomMenuItemW, kBottomMenuItemH);
    [super setFrame:frame];
}

// 取消高亮顯示狀態
- (void)setHighlighted:(BOOL)highlighted {}

// 本接口,專門交給子類實現
// 數據源,子標題數組,所有子標題的名字組成的數組
// 比如 美食 下面有多少個category
// 比如 海淀區 下面有多少個place
- (NSArray *)subTitlesArr
{
    return nil;
}

@end

Cover.h

//
//  Cover.h
//  帥哥_團購
//
//  Created by beyond on 14-8-16.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  PopMenu是點擊頂部按鈕項(TopMenuItem),在其下方彈出的菜單(XXXPopMenu)的父類,成員有:下面是一個cover蒙板,上面是一個contentView(包含著scrollView和subTitleImgView,其中scrollView裡面放的全是PopMenuItem,如美食,如海淀區.....subTitleImgView裡面放的全是美食下面的所有子標題,如川菜湘菜粵菜)

#import 

@interface Cover : UIView

+ (id)cover;
// 綁定tap手勢
+ (id)coverWithTarget:(id)target action:(SEL)action;

- (void)alphaReset;
@end

Cover.m

//
//  Cover.m
//  帥哥_團購
//
//  Created by beyond on 14-8-16.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  PopMenu是點擊頂部按鈕項(TopMenuItem),在其下方彈出的菜單(XXXPopMenu)的父類,成員有:下面是一個cover蒙板,上面是一個contentView(包含著scrollView和subTitleImgView,其中scrollView裡面放的全是PopMenuItem,如美食,如海淀區.....subTitleImgView裡面放的全是美食下面的所有子標題,如川菜湘菜粵菜)

#import "Cover.h"


// 1為全黑
#define kAlpha 0.7
@implementation Cover


- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // 1.背景色
        self.backgroundColor = [UIColor blackColor];
        
        // 2.它是蒙在tableView上面,所以要同tableView一樣,寬高自動伸縮
        self.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
        
        // 3.透明度
        self.alpha = kAlpha;
    }
    return self;
}

- (void)alphaReset
{
    self.alpha = kAlpha;
}

+ (id)cover
{
    return [[self alloc] init];
}

+ (id)coverWithTarget:(id)target action:(SEL)action
{
    Cover *cover = [self cover];
    // 綁定一個,tap手勢監聽器
    [cover addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:target action:action]];
    return cover;
}


@end

SubTitleImgView.h

容納所有子標題的ImgView,裡面全是一個個按鈕,

如美食下面的川菜、湘菜、粵菜等…

如海淀區下面的中關村、五棵松、香山等…

並且所有的彈出菜單PopMenu都共用此一個SubTitleImgView

//
//  SubTitleImgView.h
//  帥哥_團購
//
//  Created by beyond on 14-8-16.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
// 容納所有子標題的ImgView,裡面全是一個個按鈕,如美食下面的川菜、湘菜、粵菜等...如海淀區下面的中關村、五棵松、香山等,註意所有的彈出菜單PopMenu共用此一個SubTitleImgView

#import 

@protocol SubTitleImgViewDelegate;


@interface SubTitleImgView : UIImageView

// 數據源,主標題,每一個子標題數組都有,且是在第一個位置---> 【全部】
@property (nonatomic, copy) NSString *mainTitle;

// 數據源,需要顯示的所有的子標題按鈕的文字組成的數組,外部傳入,如美食下面的川菜、湘菜、粵菜等...如海淀區下面的中關村、五棵松、香山等
@property (nonatomic, strong) NSMutableArray *subTitlesArr;

@property (nonatomic, weak) id delegate;
// 代理和block的效果等價
//@property (nonatomic, copy) void (^setBtnTitleBlock)(NSString *title);
//@property (nonatomic, copy) NSString *(^getBtnTitleBlock)();

// 通過動畫顯示出來SubTitleImgView,供創建者調用
- (void)showSubTitleImgViewWithAnimation;
// 通過動畫隱藏SubTitleImgView,供創建者調用
- (void)hideSubTitleImgViewWithAnimation;
@end

SubTitleImgView.h

容納所有子標題的ImgView,裡面全是一個個按鈕,

如美食下面的川菜、湘菜、粵菜等…

如海淀區下面的中關村、五棵松、香山等…

並且所有的彈出菜單PopMenu都共用此一個SubTitleImgView

//
//  SubTitleImgView.m
//  帥哥_團購
//
//  Created by beyond on 14-8-16.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
// 容納所有子標題的ImgView,裡面全是一個個按鈕,如美食下面的川菜、湘菜、粵菜等...如海淀區下面的中關村、五棵松、香山等,註意所有的彈出菜單PopMenu共用此一個SubTitleImgView

#import "SubTitleImgView.h"
#import "MetaDataTool.h"
#import "SubTitleImgViewDelegate.h"

#define kSubTitleBtnW 100
#define kSubTitleBtnH 40

// 裡面用到的所有按鈕,因樣式統一,所以抽取一個基類
@interface SubTitleBtn : UIButton
@end

@implementation SubTitleBtn
- (void)drawRect:(CGRect)rect
{
    // 設置選中狀態下,SubTitleBtn的frame和背景
    if (self.selected) {
        CGRect frame = self.titleLabel.frame;
        frame.origin.x -= 5;
        frame.size.width += 10;
        frame.origin.y -= 5;
        frame.size.height += 10;
        [[UIImage imageStretchedWithName:@"slider_filter_bg_active.png"] drawInRect:frame];
    }
}
@end

@interface SubTitleImgView()
{
    // 記住當前選中的SubTitleBtn
    UIButton *_selectedSubTitleBtn;
}
@end

@implementation SubTitleImgView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // SubTitleImgView的寬度自由伸縮
        self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
        // SubTitleImgView的背景圖片
        self.image = [UIImage imageStretchedWithName:@"bg_subfilter_other.png"];
        
        // 重要~~~~裁剪掉超出父控件范圍內的子控件(超出父控件范圍內的子控件不顯示)
        self.clipsToBounds = YES;
        // 讓imageView裡面的一個個按鈕可以點擊,如美食(PopMenuItem)下面的川菜、湘菜、粵菜等
        self.userInteractionEnabled = YES;
    }
    return self;
}
#pragma mark - 攔截setter數據源數組方法,創建所有對應個數的按鈕
// 數據源,需要顯示的所有的子標題按鈕的文字組成的數組,外部傳入,攔截setter方法
- (void)setTitleArr:(NSArray *)titleArr
{
    // 1.用成員變量,記住所有的子標題,如美食(PopMenuItem)下面的川菜、湘菜、粵菜等
    // 所有子標題中,排在首位的都是:固定字符串--->【全部】
    [_subTitlesArr addObject:kAll];
    // 將其他子標題加到後面,如美食(PopMenuItem)下面的川菜、湘菜、粵菜等
    [_subTitlesArr addObjectsFromArray:titleArr];
    
    
    // 2.遍歷子標題數組,懶加載創建可重用的子標題按鈕
    [self addAllSubTitlesBtn];
    
    
    // 3.每當setter數據源改變之後,按鈕的位置和個數都要重新排佈,所以手動調用一次 layoutSubviews方法
    [self layoutSubviews];
    
    /*
     layoutSubviews在以下情況下會被調用:
     1、init初始化不會觸發layoutSubviews
     2、addSubview會觸發layoutSubviews
     3、設置view的Frame會觸發layoutSubviews,當然前提是frame的值設置前後發生瞭變化
     4、滾動一個UIScrollView會觸發layoutSubviews
     5、旋轉Screen會觸發父UIView上的layoutSubviews事件
     6、改變一個UIView大小的時候也會觸發父UIView上的layoutSubviews事件
     */
}


// 2.遍歷子標題數組,懶加載創建可重用的子標題按鈕
- (void)addAllSubTitlesBtn
{
    
    int count = _subTitlesArr.count;
    // 遍歷子標題數組,懶加載創建按鈕,並設置按鈕的文字
    for (int i = 0; i= self.subviews.count) {
            // 創建一個新的子標題按鈕
            btn = [SubTitleBtn buttonWithType:UIButtonTypeCustom];
            // 綁定監聽事件
            [btn addTarget:self action:@selector(subTitleBtnClicked:) forControlEvents:UIControlEventTouchUpInside];
            // 設置文字顏色
            [btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
            // 設置文字字體
            btn.titleLabel.font = [UIFont systemFontOfSize:13];
            // 添加到SubTitleImgView
            [self addSubview:btn];
        } else {
            // 如果子控件數組中有足量的按鈕,就直接取出來,重用
            btn = self.subviews[i];
        }
        
        // 2.設置按鈕獨一無二的文字(並將按鈕顯示)
        [btn setTitle:_subTitlesArr[i] forState:UIControlStateNormal];
        btn.hidden = NO;
        
        // 3.判斷該按鈕要不要默認選中,根據是:該按鈕文字是不是和當前選中的分類或商區名字一樣,代理會負責告訴我subTitleImgView 當前的分類名或者商區名字
        if ([_delegate respondsToSelector:@selector(subTitleImgViewGetCurrentBtnTitle:)]) {
            // 代理會負責告訴我subTitleImgView 當前的分類名或者商區名字
            NSString *currentBtnTitle = [_delegate subTitleImgViewGetCurrentBtnTitle:self];
            
            // 選中瞭主標題,選中第0個按鈕(“全部”)
            if ([currentBtnTitle isEqualToString:_mainTitle] && i == 0) {
                btn.selected = YES;
                _selectedSubTitleBtn = btn;
            } else {
                btn.selected = [_subTitlesArr[i] isEqualToString:currentBtnTitle];
                // 重要細節 ~~~~如果在不同的類別或商區,發現瞭同名的,則也視為選中瞭
                if (btn.selected) {
                    _selectedSubTitleBtn = btn;
                }
            }
        } else {
            btn.selected = NO;
        }
    }
    
    
    // 3.重要~~~隱藏子控件數組中多餘的按鈕,如子標題文字數組有8項,而子控件數組有10個,那麼多餘的兩個按鈕就要隱藏起來
    for (int i = count; i 大標題
            title = _mainTitle;
        }
        // 告訴代理(調用者),當前被點擊的按鈕的文字...
        [_delegate subTitleImgView:self btnClicked:title];
    }
}

#pragma mark - 覆蓋UIView的方法,重新佈局SubTitleImgView所有子控件的位置
// 控件SubTitleImgView本身的寬高發生改變等情況下就會自動觸發layoutSubviews方法
- (void)layoutSubviews
{
    // 1.一定要調用super
    [super layoutSubviews];
    
    // 2.根據屏幕寬,算出總的列數,並對所有子標題按鈕設置九宮格frame
    int columns = self.frame.size.width / kSubTitleBtnW;
    // 根據數據源的個數,遍歷對應數目的按鈕,根據i設置其frame
    for (int i = 0; i<_subTitlesArr.count; i++) {
        
        UIButton *btn = self.subviews[i];
        // 設置位置
        // 所在的列
        CGFloat x = i % columns * kSubTitleBtnW;
        // 所在的行
        CGFloat y = i / columns * kSubTitleBtnH;
        // 設置獨一無二的frame
        btn.frame = CGRectMake(x, y, kSubTitleBtnW, kSubTitleBtnH);
    }
    
    
    // 3.重要~~~計算出子標題的行數以後,必須要設置SubTitleImgView的總高度,三步曲
    // 小算法,求出總的行數,以確定SubTitleImgView的總高度
    int rows = (_subTitlesArr.count + columns - 1) / columns;
    CGRect frame = self.frame;
    frame.size.height = rows * kSubTitleBtnH;
    self.frame = frame;
}






#pragma mark - 顯示和隱藏子標題ImgView,供創建者調用
// 動畫顯示self  (SubTitleImgView),供創建者調用
- (void)showSubTitleImgViewWithAnimation
{
    // 1.重要~~~必須要先調用layoutSubviews,先算出在當前數據源titleArr數組個數的情況下,self的總高度~~~~
    [self layoutSubviews];
    
    
    // 2.先設置y為負的self的總高度(方法ayoutSubviewsy已經計算過瞭)
    self.transform = CGAffineTransformMakeTranslation(0, -self.frame.size.height);
    // 先設置為透明
    self.alpha = 0;
    
    // 3.動畫顯示出來
    [UIView animateWithDuration:kDefaultAnimDuration animations:^{
        self.transform = CGAffineTransformIdentity;
        self.alpha = 1;
    }];
}
// 動畫隱藏self  (SubTitleImgView),供創建者調用
- (void)hideSubTitleImgViewWithAnimation
{
    // 動畫設置y為負的self的總高度(慢慢向上消失效果)
    [UIView animateWithDuration:kDefaultAnimDuration animations:^{
        self.transform = CGAffineTransformMakeTranslation(0, -self.frame.size.height);
        self.alpha = 0;
    } completion:^(BOOL finished) {
        // 重要~~~~動畫完成後,將其從父控件中移除,並且將self的高度置0,目的是方便下次動畫出現的時候,可以從0開始向下展開
        [self removeFromSuperview];
        
        CGRect f = self.frame;
        f.size.height = 0;
        self.frame = f;
        // ???會不會與上面這一句功能重復
        self.transform = CGAffineTransformIdentity;
        self.alpha = 1;
    }];
}
@end

SubTitleImgView定義的協議

SubTitleImgViewDelegate.h

//
//  SubTitleViewDelegate.h
//  帥哥_團購
//
//  Created by beyond on 14-8-16.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  分類或商區的子標題的代理方法,當點擊瞭【分類或商區的子標題按鈕】時,通知代理

#import 

@class SubTitleImgView;
@protocol SubTitleImgViewDelegate 

@optional


// 當SubTitleImgView裡面的按鈕被點擊瞭的時候調用,告訴其他所有想知道的類(即SubTitleImgView的代理):被點擊的按鈕的文字【被點擊的分類或商區的子標題按鈕上的文字】
- (void)subTitleImgView:(SubTitleImgView *)subTitleImgView btnClicked:(NSString *)btnTitle;




// 返回當前選中的文字(比如分類菜單,就返回當前選中的分類名稱;區域菜單,就返回當前選中的區域名稱),目的是子標題按鈕出現前,將選中的那個高亮(回顯)~~~
// 得到當前選中的分類或商區按鈕上的文字,用於與新出現的按鈕文字進行判斷,如果相同,則在SubTitleImgView出現之前,將SubTitleImgView上面的該按鈕置為高亮,其他全為普通
// 如果SubTileImgView的代理是CategoryPopMenu,說明應該從工具類返回currentCategoryName給SubTileImgView
- (NSString *)subTitleImgViewGetCurrentBtnTitle:(SubTitleImgView *)subTitleImgView;
@end

View的層級關系示意圖:

父類:PopMenu

其子類:CategoryPopMenu、DistrictPopMenu、OrderPopMenu


父類:PopMenuItem

其子類:CategoryPopMenuItem、DistrictPopMenuItem、OrderPopMenuItem

子類:CategoryPopMenu

//
//  CategoryPopMenu.h
//  帥哥_團購
//
//  Created by beyond on 14-8-15.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  點擊頂部菜單中的分類頻道 按鈕(頂部菜單項),彈出的菜單,繼承自PopMenu,包括二個部分(上:contentView,包括scrollView和SubTitleImgView,下:蒙板)

#import "PopMenu.h"

@interface CategoryPopMenu : PopMenu

@end
//
//  CategoryPopMenu.m
//  帥哥_團購
//
//  Created by beyond on 14-8-15.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  點擊頂部菜單中的分類頻道 按鈕(頂部菜單項),彈出的菜單,繼承自PopMenu,包括二個部分(上:contentView,包括scrollView和SubTitleImgView,下:蒙板)

#import "CategoryPopMenu.h"
// 分類菜單項如:美食
#import "CategoryPopMenuItem.h"
#import "MetaDataTool.h"

@implementation CategoryPopMenu

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // 1.往scrollView裡面添加內容(CategoryPopMenuItem)
        [self addCategoryPopMenuItem];
    }
    return self;
}
// 1.往scrollView裡面添加內容(CategoryPopMenuItem)
- (void)addCategoryPopMenuItem
{
    // 獲取數據源,工具類提供allCategoriesArr對象數組
    NSArray *categories = [MetaDataTool sharedMetaDataTool].allCategoriesArr;
    
    // 1.往scrollView裡面添加內容(CategoryPopMenuItem)
    int count = categories.count;
    for (int i = 0; i<count; i++) {
        // 創建並逐一添加CategoryPopMenuItem, 分類菜單項如:美食
        CategoryPopMenuItem *item = [[CategoryPopMenuItem alloc] init];
        // 設置CategoryPopMenuItem要顯示的數據源
        item.category = categories[i];
        // 當點擊美食時,會到父類中去找方法popMenuItemClicked
        [item addTarget:self action:@selector(popMenuItemClicked:) forControlEvents:UIControlEventTouchUpInside];
        // 因為是在scrollView裡面,所以全部水平排列
        item.frame = CGRectMake(i * kBottomMenuItemW, 0, 0, 0);
        // 加到scrollView
        [_scrollView addSubview:item];
        
        // 默認選中第0個item
        if (i == 0) {
            item.selected = YES;
            _selectedPopMenuItem = item;
        }
    }
    // 2.根據CategoryPopMenuItem的多少,設置_scrollView滾動范圍
    _scrollView.contentSize = CGSizeMake(count * kBottomMenuItemW, 0);
}

#pragma mark - SubTitleImgViewDelegate代理方法
// 當SubTitleImgView裡面的子標題按鈕點擊時,會調用此方法,目的是 傳遞點擊的【分類或商區的子標題按鈕】文字
- (void)subTitleImgView:(SubTitleImgView *)subTitleImgView btnClicked:(NSString *)btnTitle
{
    [MetaDataTool sharedMetaDataTool].currentCategoryName = btnTitle;
}
// 難點??? 得到並判斷當前按鈕是否選中的文字(比如分類菜單,就返回當前選中的分類名稱;區域菜單,就返回當前選中的區域名稱)
- (NSString *)subTitleImgViewGetCurrentBtnTitle:(SubTitleImgView *)subTitleImgView
{
    // 如果SubTileImgView的代理是CategoryPopMenu,說明應該從工具類返回currentCategoryName給SubTileImgView
    return [MetaDataTool sharedMetaDataTool].currentCategoryName;
}
@end

子類:CategoryPopMenuItem

//
//  CategoryPopMenuItem.h
//  帥哥_團購
//
//  Created by beyond on 14-8-15.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  在CategoryPopMenu的第一層(即scrollView)裡面的一個按鈕,如美食,按鈕圖片在上面,文字在下面,且按鈕右邊是一根豎線

#import "PopMenuItem.h"
@class MyCategory;
@interface CategoryPopMenuItem : PopMenuItem

// 數據源,本按鈕,需要顯示的分類對象模型,一個PopMenuItem 對應一個分類,如美食
@property (nonatomic, strong) MyCategory *category;
@end

//
//  CategoryPopMenuItem.m
//  帥哥_團購
//
//  Created by beyond on 14-8-15.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  在CategoryPopMenu的第一層(即scrollView)裡面的一個按鈕,按鈕圖片在上面,文字在下面,且按鈕右邊是一根豎線

#import "CategoryPopMenuItem.h"
#import "MyCategory.h"

// 圖片在上,文字在下
#define kTitleHeightRatio 0.3
@implementation CategoryPopMenuItem




- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // 1.文字居中對齊
        self.titleLabel.textAlignment = NSTextAlignmentCenter;
        
        // 2.圖片中心縮放
        self.imageView.contentMode = UIViewContentModeCenter;
    }
    return self;
}


// 父類的方法,供子類實現
// 數據源,子標題數組,所有子標題的名字組成的數組,  本接口,專門交給子類實現
// 比如 美食 下面有多少個subCategory
// 比如 海淀區 下面有多少個place
- (NSArray *)subTitlesArr
{
    return _category.subcategories;
}

// 攔截數據源的setter方法,設置按鈕的圖片和文字
- (void)setCategory:(MyCategory *)category
{
    _category = category;
    
    // 1.圖標
    [self setImage:[UIImage imageNamed:category.icon] forState:UIControlStateNormal];
    
    // 2.標題
    [self setTitle:category.name forState:UIControlStateNormal];
}


#pragma mark 設置按鈕上面的圖片的frame
- (CGRect)imageRectForContentRect:(CGRect)contentRect {
    return CGRectMake(0, 0, contentRect.size.width, contentRect.size.height * (1 - kTitleHeightRatio));
}

#pragma mark 設置按鈕下面的標題的frame
- (CGRect)titleRectForContentRect:(CGRect)contentRect {
    CGFloat titleHeight = contentRect.size.height * kTitleHeightRatio;
    CGFloat titleY = contentRect.size.height - titleHeight;
    return CGRectMake(0, titleY, contentRect.size.width,  titleHeight);
}


@end

子類:DistrictPopMenu

//
//  DistrictPopMenu.h
//  帥哥_團購
//
//  Created by beyond on 14-8-15.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  點擊頂部菜單中的 全部商區 按鈕(頂部菜單項),彈出的菜單,繼承自PopMenu,包括二個部分(上:contentView,包括scrollView和SubTitleImgView,下:蒙板)

#import "PopMenu.h"

@interface DistrictPopMenu : PopMenu

@end
//
//  DistrictPopMenu.m
//  帥哥_團購
//
//  Created by beyond on 14-8-15.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  點擊頂部菜單中的 全部商區 按鈕(頂部菜單項),彈出的菜單,繼承自PopMenu,包括二個部分(上:contentView,包括scrollView和SubTitleImgView,下:蒙板)

#import "DistrictPopMenu.h"
#import "DistrictPopMenuItem.h"
#import "MetaDataTool.h"
#import "District.h"
// 商區依賴城市
#import "City.h"

#import "SubTitleImgView.h"

@interface DistrictPopMenu ()
{
    NSMutableArray *_menuItems;
}

@end
@implementation DistrictPopMenu


- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        _menuItems = [NSMutableArray array];
        
        [self cityChange];
        
        // 監聽城市改變
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cityChange) name:kCityChangeNote object:nil];
    }
    return self;
}

- (void)cityChange
{
    // 1.獲取當前選中的城市對象,保存在工具中
    City *city = [MetaDataTool sharedMetaDataTool].currentCity;
    
    // 2.當前城市的所有區域,包括全部商區+下屬商區數組(城市對應的成員)
    NSMutableArray *districts = [NSMutableArray array];
    // 2.1.全部商區
    District *all = [[District alloc] init];
    all.name = kAllDistrict;
    [districts addObject:all];
    // 2.2.其他商區,下屬商區數組(城市對應的成員)
    [districts addObjectsFromArray:city.districts];
    
    // 3.遍歷所有的商區對象,創建並設置按鈕標題
    int count = districts.count;
    for (int i = 0; i= _menuItems.count) { // 不夠
            item = [[DistrictPopMenuItem alloc] init];
            [item addTarget:self action:@selector(popMenuItemClicked:) forControlEvents:UIControlEventTouchUpInside];
            [_menuItems addObject:item];
            [_scrollView addSubview:item];
        } else {
            item = _menuItems[i];
        }
        
        item.hidden = NO;
        item.district = districts[i];
        item.frame = CGRectMake(i * kBottomMenuItemW, 0, 0, 0);
        
        // 默認選中第0個item
        if (i == 0) {
            item.selected = YES;
            _selectedPopMenuItem = item;
        } else {
            item.selected = NO;
        }
    }
    
    // 4.隱藏多餘的item
    for (int i = count; i<_menuItems.count; i++) {
        DistrictPopMenuItem *item = _scrollView.subviews[i];
        item.hidden = YES;
    }
    
    // 5.設置scrollView的內容尺寸
    _scrollView.contentSize = CGSizeMake(count * kBottomMenuItemW, 0);
    
    // 6.隱藏子標題(在父類定義的)
    [_subTitleImgView hideSubTitleImgViewWithAnimation];
}



#pragma mark - SubTitleImgViewDelegate代理方法
// 當SubTitleImgView裡面的子標題按鈕點擊時,會調用此方法,目的是 傳遞點擊的【分類或商區的子標題按鈕】文字
- (void)subTitleImgView:(SubTitleImgView *)subTitleImgView btnClicked:(NSString *)btnTitle
{
    [MetaDataTool sharedMetaDataTool].currentDistrictName = btnTitle;
}
// 難點??? 得到並判斷當前按鈕是否選中的文字(比如分類菜單,就返回當前選中的分類名稱;區域菜單,就返回當前選中的區域名稱)
- (NSString *)subTitleImgViewGetCurrentBtnTitle:(SubTitleImgView *)subTitleImgView
{
    // 如果SubTileImgView的代理是DistrictPopMenu,說明應該從工具類返回currentDistrictName給SubTileImgView
    return [MetaDataTool sharedMetaDataTool].currentDistrictName;
}



// 頂部菜單因為要改變其三個按鈕的文字,因此在通知中心註冊成為瞭監聽者,因此dealloc時要在通知中心,移除掉監聽者
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end

子類:DistrictPopMenuItem

//
//  DistrictPopMenuItem.h
//  帥哥_團購
//
//  Created by beyond on 14-8-16.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  如海淀區

#import "PopMenuItem.h"
@class District;
@interface DistrictPopMenuItem : PopMenuItem

// 數據源  一個PopMenuItem對應一個商區,如海淀區
@property (nonatomic, strong) District *district;
@end

//
//  DistrictPopMenuItem.m
//  帥哥_團購
//
//  Created by beyond on 14-8-16.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  如海淀區

#import "DistrictPopMenuItem.h"
#import "District.h"
@implementation DistrictPopMenuItem

- (void)setDistrict:(District *)district
{
    _district = district;
    
    [self setTitle:district.name forState:UIControlStateNormal];
}

// 父類的方法,供子類實現
// 數據源,子標題數組,所有子標題的名字組成的數組,  本接口,專門交給子類實現
// 比如 美食 下面有多少個subCategory
// 比如 海淀區 下面有多少個place
- (NSArray *)subTitlesArr
{
    return _district.places;
}

@end

子類:OrderPopMenu

//
//  OrderPopMenu.h
//  帥哥_團購
//
//  Created by beyond on 14-8-16.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//

#import "PopMenu.h"

@interface OrderPopMenu : PopMenu

@end

//
//  OrderPopMenu.m
//  帥哥_團購
//
//  Created by beyond on 14-8-16.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//

#import "OrderPopMenu.h"
#import "OrderPopMenuItem.h"
#import "Order.h"
#import "MetaDataTool.h"
@implementation OrderPopMenu

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // 1.往UIScrollView添加內容
        NSArray *orders = [MetaDataTool sharedMetaDataTool].AllOrdersArr;
        int count = orders.count;
        
        for (int i = 0; i<count; i++) {
            // 創建排序item
            OrderPopMenuItem *item = [[OrderPopMenuItem alloc] init];
            item.order = orders[i];
            [item addTarget:self action:@selector(popMenuItemClicked:) forControlEvents:UIControlEventTouchUpInside];
            item.frame = CGRectMake(i * kBottomMenuItemW, 0, 0, 0);
            [_scrollView addSubview:item];
            
            // 默認選中第0個item
            if (i == 0) {
                item.selected = YES;
                _selectedPopMenuItem = item;
            }
        }
        _scrollView.contentSize = CGSizeMake(count * kBottomMenuItemW, 0);
    }
    return self;
}
@end

子類:OrderPopMenuItem

//
//  OrderPopMenuItem.h
//  帥哥_團購
//
//  Created by beyond on 14-8-16.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//

#import "PopMenuItem.h"
@class Order;
@interface OrderPopMenuItem : PopMenuItem


@property (nonatomic, strong) Order *order;
@end

//
//  OrderPopMenuItem.m
//  帥哥_團購
//
//  Created by beyond on 14-8-16.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//

#import "OrderPopMenuItem.h"
#import "Order.h"
@implementation OrderPopMenuItem


- (void)setOrder:(Order *)order
{
    _order = order;
    
    [self setTitle:order.name forState:UIControlStateNormal];
}
@end

最重要的一個工具類

//
//  MetaDataTool.h
//  帥哥_團購
//
//  Created by beyond on 14-8-14.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  元數據管理類
// 1.城市數據
// 2.下屬分區數據
// 3.分類數據

#import 
@class Order;
@class City;
@interface MetaDataTool : NSObject
singleton_interface(MetaDataTool)




// readonly隻可讀,NSArray,不允許外部隨便增刪改
// 所有的城市分組數組,數組中的元素是section對象
@property (nonatomic, strong, readonly) NSMutableArray *allSectionsArr;

// 所有城市字典,Key是城市名,Value是城市對象
@property (nonatomic, strong, readonly) NSMutableDictionary *allCitiesDict;

// 當前選中的城市, 當點擊瞭控制器下方的tableView的某一行時,會設置當前城市,攔截setter操作,更新最近訪問的城市數組
@property (nonatomic, strong) City *currentCity; // 當前選中的城市






// 所有的分類對象組成的數組,一個分類對象包括分類名,圖標,所有子分類名組成的數組
@property (nonatomic, strong, readonly) NSArray *allCategoriesArr;

// 所有的排序對象組成的數組
@property (nonatomic, strong, readonly) NSArray *AllOrdersArr;


@property (nonatomic, strong) NSString *currentCategoryName; // 當前選中的類別的名字
@property (nonatomic, strong) NSString *currentDistrictName; // 當前選中的區域名字
@property (nonatomic, strong) Order *currentOrder; // 當前選中的排序對象


// 通過按鈕上面的名字如(價格最高),到MyOrder對象數組中,遍歷返回MyOder對象
- (Order *)orderWithName:(NSString *)name;

 

@end

//
//  MetaDataTool.m
//  帥哥_團購
//
//  Created by beyond on 14-8-14.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  元數據管理類
// 1.城市數據
// 2.下屬分區數據
// 3.分類數據

#import "MetaDataTool.h"
// 一個分組模型
#import "Section.h"
#import "City.h"

// 一個分類對象模型
#import "MyCategory.h"
#import "Order.h"
// 沙盒裡面放的是所有曾經訪問過的城市名字
#define kFilePath [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"visitedCityNamesArr.data"]
@interface MetaDataTool ()
{
    // 數組,存儲曾經訪問過城市的名稱
    NSMutableArray *_visitedCityNamesArr;
    // 訪問過的組section
    Section *_visitedSection; // 最近訪問的城市組數組
}

@end

@implementation MetaDataTool
singleton_implementation(MetaDataTool)



- (id)init
{
    if (self = [super init]) {
        // 初始化項目中的所有元數據
        
        // 1.初始化城市數據
        
        [self loadCitiesData];
        
        // 2.初始化分類數據
        [self loadCategoryData];
        
        // 3.初始化排序對象數據
        [self loadOrderData];
    }
    return self;
}

// 1.初始化城市數據
- (void)loadCitiesData
{
    // 所有城市對象組成的字典,Key是城市名,Value是城市對象
    _allCitiesDict = [NSMutableDictionary dictionary];
    // 臨時變量,存放所有的section
    NSMutableArray *tempSectionsArr = [NSMutableArray array];
    
    // 1.創建一個熱門城市分組
    Section *hotSection = [[Section alloc] init];
    // 組名是 熱門
    hotSection.name = @"熱門";
    // 分組的成員cities,初始化
    hotSection.cities = [NSMutableArray array];
    // 為瞭將熱門這一組加在分組數組的最前面,準備瞭一個臨時的section數組
    [tempSectionsArr addObject:hotSection];
    
    // 2.添加A-Z分組,到臨時section數組後面
    // 加載plist數據
    NSArray *sectionsArr = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Cities.plist" ofType:nil]];
    for (NSDictionary *dict in sectionsArr) {
        // 創建城市分組對象模型
        Section *section = [[Section alloc] init];
        // 調用分類方法,將字典轉成模型
        [section setValuesWithDict:dict];
        // 將所有的section對象,加到臨時的section對象數組的後面
        [tempSectionsArr addObject:section];
        
        // 遍歷每一組的所有城市,找出熱門的加到hotSection裡面
        for (City *city in section.cities) {
            if (city.hot) {
                // 如果是熱門城市
                [hotSection.cities addObject:city];
            }
            // 並且將所有的城市對象,以城市名作為鍵,存入字典中
            [_allCitiesDict setObject:city forKey:city.name];
        }
    }
    
    // 3.從沙盒中讀取之前訪問過的城市名稱
    _visitedCityNamesArr = [NSKeyedUnarchiver unarchiveObjectWithFile:kFilePath];
    // 如果是首次使用,則沙盒中返回的是空數組,需要懶加載,創建一個數組
    if (_visitedCityNamesArr == nil) {
        _visitedCityNamesArr = [NSMutableArray array];
    }
    
    // 4.創建並添加一個section, 最近訪問城市組(section)
    _visitedSection = [[Section alloc] init];
    _visitedSection.name = @"最近訪問";
    _visitedSection.cities = [NSMutableArray array];
    
    // 5.遍歷沙盒中取出來的城市名組成的數組,轉成一個個城市對象
    for (NSString *name in _visitedCityNamesArr) {
        // 根據城市名,從對象字典中取出城市對象,並添加到最近訪問城市組(section)中的城市對象數組
        City *city = _allCitiesDict[name];
        [_visitedSection.cities addObject:city];
    }
    // 6.如果最近訪問城市組(section)中的城市對象數組中有城市,那麼就可以將最近訪問組插入到sections最前面
    if (_visitedSection.cities.count) {
        [tempSectionsArr insertObject:_visitedSection atIndex:0];
    }
    
    // 將所有的section組成的數組賦值給成員變量供調用者訪問
    _allSectionsArr = tempSectionsArr;
}
// 當點擊瞭控制器下方的tableView的某一行時,會設置當前城市,攔截setter操作,更新最近訪問的城市數組
- (void)setCurrentCity:(City *)currentCity
{
    _currentCity = currentCity;

    // 修改當前選中的區域
//    _currentDistrict = kAllDistrict;

    // 1.先從最近訪問的城市名數組中,移除該的城市名
    [_visitedCityNamesArr removeObject:currentCity.name];

    // 2.再將新的城市名插入到數組的最前面(最近訪問的在最前)
    [_visitedCityNamesArr insertObject:currentCity.name atIndex:0];

    // 3.同時,要將新的城市對象,放到_visitedSection的城市對象數組的最前面
    [_visitedSection.cities removeObject:currentCity];
    [_visitedSection.cities insertObject:currentCity atIndex:0];

    // 4.歸檔最近訪問的城市名組成的數組,以便下次再解檔
    [NSKeyedArchiver archiveRootObject:_visitedCityNamesArr toFile:kFilePath];

    // 5.每一次點擊,攔截setter當前城市之後,都要發出通知,做什麼用???
    [[NSNotificationCenter defaultCenter] postNotificationName:kCityChangeNote object:nil];

    // 6.當用點擊瞭某一行,來到瞭這個setCurrentCity方法時,肯定是要在最前面,添加“最近訪問組”瞭
    // 如果 allSectionsArr 已經有瞭 【最近訪問組】,則不用再添加瞭
    if (![_allSectionsArr containsObject:_visitedSection]) {
        [_allSectionsArr insertObject:_visitedSection atIndex:0];
    }
}

// 3.初始化排序對象數組
- (void)loadOrderData
{
    NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Orders.plist" ofType:nil]];
    int count = array.count;
    NSMutableArray *temp = [NSMutableArray array];
    for (int i = 0; i<count; i++){
        Order *o = [[Order alloc] init];
        // 設置MyOrder對象的名字
        o.name = array[i];
        // 設置MyOrder對象的索引 1 2 3 4 ...
        o.index = i + 1;
        [temp addObject:o];
    }
    // 所有的排序對象組成的數組
    _AllOrdersArr = temp;
}
// 點擊PopMenuItem時,可通過按鈕上面的名字如(價格最高),到MyOrder對象數組中,遍歷返回MyOder對象,以達到為本工具類設置成員currentOrder的目的
- (Order *)orderWithName:(NSString *)name
{
    for (Order *order in _AllOrdersArr) {
        if ([name isEqualToString:order.name]) {
            return order;
        }
    }
    return nil;
}
 

// 2.初始化分類數據
- (void)loadCategoryData
{
    NSMutableArray *tempArr = [NSMutableArray array];
    // 從plist返回的字典數組
    NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Categories.plist" ofType:nil]];
    // 第1個對象是----名字叫:全部分類,無子分類,圖標是:
    MyCategory *all = [[MyCategory alloc] init];
    all.name = kAllCategory;
    all.icon = @"ic_filter_category_-1.png";
    [tempArr addObject:all];
    
    // 1.添加全部分類
    // 遍歷字典,將字典轉成對象模型
    for (NSDictionary *dict in array) {
        MyCategory *c = [[MyCategory alloc] init];
        // 分類方法
        [c setValuesWithDict:dict];
        [tempArr addObject:c];
    }
    
    _allCategoriesArr = tempArr;
}


#pragma mark - 當點擊瞭PopMenuItem的任何子類時,都會來到這個方法
// 設置當前的分類或商區或排序,並發出通知,通知那些需要做出相應的顯示改變的東東,例如TopMenu,例如DealListController發送相應的請求,顯示對應的查詢的內容
- (void)setCurrentCategoryName:(NSString *)currentCategoryName
{
    _currentCategoryName = currentCategoryName;
    // 發出通知
    [[NSNotificationCenter defaultCenter] postNotificationName:kCategoryChangeNote object:nil];
}

- (void)setCurrentDistrictName:(NSString *)currentDistrictName
{
    _currentDistrictName = currentDistrictName;
    // 發出通知
    [[NSNotificationCenter defaultCenter] postNotificationName:kDistrictChangeNote object:nil];
}

- (void)setCurrentOrder:(Order *)currentOrder
{
    _currentOrder = currentOrder;
    // 發出通知
    [[NSNotificationCenter defaultCenter] postNotificationName:kOrderChangeNote object:nil];
}

@end

發佈留言