最終效果圖:

<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+PGJyPgo8L3A+CjxwPjxicj4KPC9wPgo8cD6497/YvP652M+1zbwxOjwvcD4KPHA+PGltZyBzcmM9"/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.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 *divider = [[UIImageView alloc] initWithImage:img]; divider.bounds = CGRectMake(0, 0, 2, kTopMenuItemH * 0.7); divider.center = CGPointMake(kTopMenuItemW, kTopMenuItemH * 0.5); [self addSubview:divider]; // 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 // 帥哥_團購 // // Created by beyond on 14-8-16. // Copyright (c) 2014年 com.beyond. All rights reserved. // 底部彈出菜單的菜單項 (是一個父類) 抽取的特征:1,右邊有分隔線,2.寬高全統一,3.選中時,背景圖片統一,4文字顏色 #importPopMenuItem.m@interface PopMenuItem : UIButton // 本接口,專門交給子類實現 // 數據源,子標題數組,所有子標題的名字組成的數組, // 比如 美食 下面有多少個category // 比如 海澱區 下面有多少個place - (NSArray *)subTitlesArr; @end
//
// 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 *divider = [[UIImageView alloc] initWithImage:img];
divider.bounds = CGRectMake(0, 0, 2, kBottomMenuItemH * 0.7);
divider.center = CGPointMake(kBottomMenuItemW, kBottomMenuItemH * 0.5);
[self addSubview:divider];
// 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
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
子類: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
子類: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