最終效果圖:
<喎?/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; } @endCover.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