最后效果图:
各控件关系图1:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcHJlX2VtaW5lbnQ=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >\
各控件关系图2:
点击Dock上面的buttonDockItem,
创建经导航控制器包装的DealListController,
而且加入到主控制器的右側空间
// // DealListController.m // 帅哥_团购 // // Created by beyond on 14-8-14. // Copyright (c) 2014年 com.beyond. All rights reserved. // 点击dock上面的【团购】button相应的控制器,上面是导航栏,导航栏右边是searchBar,导航栏左边是一个大button(TopMenu)(内部由三个小button组成<TopMenuItem>) #import "DealListController.h" // 导航栏左边是一个大button(顶部菜单) #import "TopMenu.h" @interface DealListController () @end @implementation DealListController - (void)viewDidLoad { [super viewDidLoad]; // 1,设置上方的导航栏,右边是搜索bar,左边是一个大的VIEW(内有三个button),即TopMenu,内部的button是TopMenuItem [self addNaviBarBtn]; } // 1,设置上方的导航栏,右边是搜索bar,左边是一个大的VIEW(内有三个button),<span style="font-family: Arial, Helvetica, sans-serif;">即TopMenu,内部的button是TopMenuItem</span> - (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.左边的菜单条,导航栏左边是一个大button(顶部菜单) TopMenu *topMenu = [[TopMenu alloc] init]; // 3.用于点击顶部button时,容纳创建出来的底部弹出菜单(包含一个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上面的【团购】button时,创建出相应的经导航包装后的子控制器,子控制器的上面是导航栏,导航栏右边是searchBar,导航栏左边是一个大button(TopMenu)(内部仅仅由三个小button组成<TopMenuItem>它们各自是:所有频道,所有商区,默认排序),点击TopMenu中的某一个button<TopMenuItem>,会在其下方,弹出一个PopMenu,PopMenu包含二个部分(上面是一个contentView:包含:scrollView和subTitleImgView,下:蒙板) #import <UIKit/UIKit.h> @interface TopMenu : UIView // 用于点击顶部菜单项时,容纳创建出来的底部弹出菜单(包含一个contentView和cover,contentView又包含scrollView和subTitleImgView),本成员是由创建此TopMenu的控制器赋值传入, 本成员属性是用来接收控制器的view,就是导航栏以下的所有区域,目的是用于加入并展示PopMenu @property (nonatomic, weak) UIView *controllerView; @end
TopMenu.m
负责创建和加入3个TopMenuItem,
监听其内部三个TopMenuItem的点击事件,
而且依据点击的button的tag不同,创建出不同的PopMenu,并控制PopMenu的出现和隐藏,最后一个是注冊到通知中心,监听全部通知,而且设置三个TopMenuItem的显示文字
// // TopMenu.m // 帅哥_团购 // // Created by beyond on 14-8-15. // Copyright (c) 2014年 com.beyond. All rights reserved. // 点击dock上面的【团购】button相应的控制器,上面是导航栏,导航栏右边是searchBar,导航栏左边是一个大button(TopMenu)(内部仅仅由三个小button组成<TopMenuItem>它们各自是:所有频道,所有商区,默认排序),点击TopMenu中的某一个button<TopMenuItem>,会在其下方,弹出一个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() { // 三个顶部菜单项中当前选中的那一个,三个button:所有分类,所有商区,默认排序 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.加入一个button:所有分类 (由于要更改其显示文字,所以要成员变量记住) _categoryTopMenuItem = [self addMenuItem:kAllCategory index:0]; // 2.加入一个button:所有商区 (由于要更改其显示文字,所以要成员变量记住) _districtTopMenuItem = [self addMenuItem:kAllDistrict index:1]; // 3.加入一个button:默认排序 (由于要更改其显示文字,所以要成员变量记住) _orderTopMenuItem = [self addMenuItem:@"默认排序" index:2]; // 4.顶部菜单须要注冊并监听所有通知,目的是更改其里面菜单项的文字,而且控制弹出菜单的显示和隐藏 kAddAllNotes(dataChange) } return self; } // 5,抽取的方法,加入一个顶部菜单项(TopMenuItem),三个button:所有分类,所有商区,默认排序 - (TopMenuItem *)addMenuItem:(NSString *)title index:(int)index { TopMenuItem *item = [[TopMenuItem alloc] init]; item.title = title; item.tag = index; // 三个button:所有分类,所有商区,默认排序 水平排列 item.frame = CGRectMake(kTopMenuItemW * index, 0, 0, 0); // 重要~~~顶部button(三个button:所有分类,所有商区,默认排序)被点击之后,都会调用此方法,依据tag进行区分,以便弹出不同的PopMenu [item addTarget:self action:@selector(topMenuItemClick:) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:item]; return item; } // 4.注冊并监听所有通知时调用此方法,更改button的文字,控制弹出菜单显示和隐藏 - (void)dataChange { // 0.取消当前TopMenuItem的选中状态,而且置空_currentTopMenuItem ,由于监听到通知时,肯定是用户点击了一个子标题button,或者一个没有子标题的PopMenuItem _currentTopMenuItem.selected = NO; _currentTopMenuItem = nil; // 1.设置 分类button 要显示的文字,从工具中获得 NSString *c = [MetaDataTool sharedMetaDataTool].currentCategoryName; if (c) { _categoryTopMenuItem.title = c; } // 2.设置 商区button 要显示的文字,从工具中获得 NSString *d = [MetaDataTool sharedMetaDataTool].currentDistrictName; if (d) { _districtTopMenuItem.title = d; } // 3.设置 排序button 要显示的文字,从工具中获得 NSString *o = [MetaDataTool sharedMetaDataTool].currentOrder.name; if (o) { _orderTopMenuItem.title = o; } // 4.最后,调用正在显示的弹出菜单的方法:隐藏底部弹出菜单,并置空正在显示的弹出菜单 [_showingPopMenu hidePopMenu]; _showingPopMenu = nil; } // 5-1,重要~~~监听顶部菜单项的点击, // 顶部button(三个button:所有分类,所有商区,默认排序)被点击之后,都会调用此方法,依据tag进行区分,以便弹出不同的PopMenu - (void)topMenuItemClick:(TopMenuItem *)item { // 0.假设还没有选择好城市,则不同意点击顶部菜单button 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; // 假设有正在显示的弹出菜单,如切换button点击的时候 if (_showingPopMenu) { // 1-1,先移除当前正在显示的弹出菜单 [_showingPopMenu removeFromSuperview]; // 1-2,不要动画出场 animted = NO; }else{ // 没有正在展示的弹出菜单时,才须要运行出场动画 animted = YES; } // 2,依据点击的顶部菜单项的tag,创建并显示不同的底部弹出菜单(三个button:所有分类,所有商区,默认排序) 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的button选中状态 __unsafe_unretained TopMenu *menu = self; _showingPopMenu.hideBlock = ^{ // 重要~~~当_showingPopMenu隐藏后,要更改顶部的TopMenu的button选中状态 // 1.取消当前的TopMenuItem的选中状态,并置空 menu->_currentTopMenuItem.selected = NO; menu->_currentTopMenuItem = nil; // 2._showingPopMenu隐藏后,就要清空_showingPopMenu menu->_showingPopMenu = nil; }; // 加入创建出来的即将要显示的弹出菜单 到_controllerView(即导航栏以下的所有空间都是弹出菜单的) [_controllerView addSubview:_showingPopMenu]; // 运行刚才创建出来的底部弹出菜单的 出场动画,注意:仅仅有没有正在展示的弹出菜单时,才须要运行出场动画) if (animted) { [_showingPopMenu showPopMenu]; } } // 顶部菜单宽度固定是三个button的宽高,由于仅仅有三个button:所有分类,所有商区,默认排序 - (void)setFrame:(CGRect)frame { frame.size = CGSizeMake(3 * kTopMenuItemW, kTopMenuItemH); [super setFrame:frame]; } // 顶部菜单由于要改变其三个button的文字,因此在通知中心注冊成为了监听者,因此dealloc时要在通知中心,移除掉监听者 - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } @end
TopMenuItem 顶部菜单项button
// // TopMenuItem.m // 帅哥_团购 // // Created by beyond on 14-8-15. // Copyright (c) 2014年 com.beyond. All rights reserved. // 点击dock上面的【团购】button相应的控制器,上面是导航栏,导航栏右边是searchBar,导航栏左边是一个大button(TopMenu)(内部仅仅由三个小button组成<TopMenuItem>它们各自是:所有频道,所有商区,默认排序),点击TopMenu中的某一个button<TopMenuItem>,会在其下方,弹出一个PopMenu,PopMenu包含二个部分(上面是一个contentView:包含:scrollView和subTitleImgView,下:蒙板) #import <UIKit/UIKit.h> @interface TopMenuItem : UIButton // 设置buttonTopMenuItem显示的文字 @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.设置button右边的箭头 [self setImage:[UIImage imageNamed:@"ic_arrow_down.png"] forState:UIControlStateNormal]; self.imageView.contentMode = UIViewContentModeCenter; // 3.设置button右边的切割线 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.设置button选中时的背景 [self setBackgroundImage:[UIImage imageStretchedWithName:@"slider_filter_bg_normal.png"] forState:UIControlStateSelected]; } return self; } - (void)setTitle:(NSString *)title { _title = title; [self setTitle:title forState:UIControlStateNormal]; } // 自己固定顶部菜单项button的好宽高 - (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是点击顶部button项,在其下方弹出的菜单的父类,成员有:以下是一个cover蒙板,上面是一个contentView(包括着scrollView和subTitleImgView,当中scrollView里面放的全是PopMenuItem,如美食,如海淀区.....subTitleImgView里面放的全是美食以下的全部子标题,如川菜 湘菜 粤菜) // 点击dock上面的【团购】button,创建一个经过导航包装的DealList控制器,控制器的上面是导航栏,导航栏右边是searchBar,导航栏左边是一个大button(TopMenu)(内部由三个小button组成<TopMenuItem>),点击TopMenu中的某一个button<TopMenuItem>,会在其下方,弹出一个PopMenu,PopMenu包括二个部分,见上面 #import <UIKit/UIKit.h> #import "SubTitleImgViewDelegate.h" @class SubTitleImgView, PopMenuItem; @interface PopMenu : UIView <SubTitleImgViewDelegate> { // 以下成员是开放给子类訪问和改动的 // 容纳全部的分类或商区,如美食,如海淀区 UIScrollView *_scrollView; // 容纳全部子标题的ImgView,里面全是一个个button,如美食以下的川菜、湘菜、粤菜等 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是点击顶部button项,在其下方弹出的菜单的父类,成员有:以下是一个cover蒙板,上面是一个contentView(包含着scrollView和subTitleImgView,当中scrollView里面放的全是PopMenuItem,如美食,如海淀区.....subTitleImgView里面放的全是美食以下的全部子标题,如川菜 湘菜 粤菜) // 点击dock上面的【团购】button,创建一个经过导航包装的DealList控制器,控制器的上面是导航栏,导航栏右边是searchBar,导航栏左边是一个大button(TopMenu)(内部由三个小button组成<TopMenuItem>),点击TopMenu中的某一个button<TopMenuItem>,会在其下方,弹出一个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,假设没有子标题(如电影),就可隐藏掉弹出button了 - (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里面的button时,获知button的标题,另外一个就是告诉_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]; // 设置子标题须要显示的内容(带去要显示的数据源,即全部的子标题组成的数组) // 重要~~~供子类 去实现的,内部会拦截此方法,加入全部的子标题button,并设置文字 _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 <UIKit/UIKit.h> @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 *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; } // 菜单项的宽高固定为一个button的宽和高 - (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是点击顶部button项(TopMenuItem),在其下方弹出的菜单(XXXPopMenu)的父类,成员有:以下是一个cover蒙板,上面是一个contentView(包括着scrollView和subTitleImgView,当中scrollView里面放的全是PopMenuItem,如美食,如海淀区.....subTitleImgView里面放的全是美食以下的全部子标题,如川菜湘菜粤菜) #import <UIKit/UIKit.h> @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是点击顶部button项(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,里面全是一个个button,
如美食以下的川菜、湘菜、粤菜等...
如海淀区以下的中关村、五棵松、香山等...
而且全部的弹出菜单PopMenu都共用此一个SubTitleImgView
// // SubTitleImgView.h // 帅哥_团购 // // Created by beyond on 14-8-16. // Copyright (c) 2014年 com.beyond. All rights reserved. // 容纳全部子标题的ImgView,里面全是一个个button,如美食以下的川菜、湘菜、粤菜等...如海淀区以下的中关村、五棵松、香山等,注意全部的弹出菜单PopMenu共用此一个SubTitleImgView #import <UIKit/UIKit.h> @protocol SubTitleImgViewDelegate; @interface SubTitleImgView : UIImageView // 数据源,主标题,每个子标题数组都有,且是在第一个位置---> 【全部】 @property (nonatomic, copy) NSString *mainTitle; // 数据源,须要显示的全部的子标题button的文字组成的数组,外部传入,如美食以下的川菜、湘菜、粤菜等...如海淀区以下的中关村、五棵松、香山等 @property (nonatomic, strong) NSMutableArray *subTitlesArr; @property (nonatomic, weak) id<SubTitleImgViewDelegate> delegate; // 代理和block的效果等价 //@property (nonatomic, copy) void (^setBtnTitleBlock)(NSString *title); //@property (nonatomic, copy) NSString *(^getBtnTitleBlock)(); // 通过动画显示出来SubTitleImgView,供创建者调用 - (void)showSubTitleImgViewWithAnimation; // 通过动画隐藏SubTitleImgView,供创建者调用 - (void)hideSubTitleImgViewWithAnimation; @end
SubTitleImgView.h
容纳全部子标题的ImgView,里面全是一个个button,
如美食以下的川菜、湘菜、粤菜等...
如海淀区以下的中关村、五棵松、香山等...
而且全部的弹出菜单PopMenu都共用此一个SubTitleImgView
// // SubTitleImgView.m // 帅哥_团购 // // Created by beyond on 14-8-16. // Copyright (c) 2014年 com.beyond. All rights reserved. // 容纳全部子标题的ImgView,里面全是一个个button,如美食以下的川菜、湘菜、粤菜等...如海淀区以下的中关村、五棵松、香山等,注意全部的弹出菜单PopMenu共用此一个SubTitleImgView #import "SubTitleImgView.h" #import "MetaDataTool.h" #import "SubTitleImgViewDelegate.h" #define kSubTitleBtnW 100 #define kSubTitleBtnH 40 // 里面用到的全部button,因样式统一,所以抽取一个基类 @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里面的一个个button能够点击,如美食(PopMenuItem)以下的川菜、湘菜、粤菜等 self.userInteractionEnabled = YES; } return self; } #pragma mark - 拦截setter数据源数组方法,创建全部相应个数的button // 数据源,需要显示的全部的子标题button的文字组成的数组,外部传入,拦截setter方法 - (void)setTitleArr:(NSArray *)titleArr { // 1.用成员变量,记住全部的子标题,如美食(PopMenuItem)以下的川菜、湘菜、粤菜等 // 全部子标题中,排在首位的都是:固定字符串--->【全部】 [_subTitlesArr addObject:kAll]; // 将其它子标题加到后面,如美食(PopMenuItem)以下的川菜、湘菜、粤菜等 [_subTitlesArr addObjectsFromArray:titleArr]; // 2.遍历子标题数组,懒载入创建可重用的子标题button [self addAllSubTitlesBtn]; // 3.每当setter数据源改变之后,button的位置和个数都要又一次排布,所以手动调用一次 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.遍历子标题数组,懒载入创建可重用的子标题button - (void)addAllSubTitlesBtn { int count = _subTitlesArr.count; // 遍历子标题数组,懒载入创建button,并设置button的文字 for (int i = 0; i<count; i++) { // 1.重要~~~如i位置没有button与之相应,则创建一个新的button,显示i位置的子标题文字 UIButton *btn = nil; // self是SubTitleImgView,里面的子控件全是一个个代表子标题的button // button个数不够,创建SubTitleBtn,而且绑定监听事件 if (i >= self.subviews.count) { // 创建一个新的子标题button 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 { // 假设子控件数组中有足量的button,就直接取出来,重用 btn = self.subviews[i]; } // 2.设置button独一无二的文字(并将button显示) [btn setTitle:_subTitlesArr[i] forState:UIControlStateNormal]; btn.hidden = NO; // 3.推断该button要不要默认选中,依据是:该button文字是不是和当前选中的分类或商区名字一样,代理会负责告诉我subTitleImgView 当前的分类名或者商区名字 if ([_delegate respondsToSelector:@selector(subTitleImgViewGetCurrentBtnTitle:)]) { // 代理会负责告诉我subTitleImgView 当前的分类名或者商区名字 NSString *currentBtnTitle = [_delegate subTitleImgViewGetCurrentBtnTitle:self]; // 选中了主标题,选中第0个button(“全部”) 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.重要~~~隐藏子控件数组中多余的button,如子标题文字数组有8项,而子控件数组有10个,那么多余的两个button就要隐藏起来 for (int i = count; i<self.subviews.count; i++) { UIButton *btn = self.subviews[i]; btn.hidden = YES; } } // 监听小标题(button)点击,如美食(PopMenuItem)以下的川菜、湘菜、粤菜等 - (void)subTitleBtnClicked:(UIButton *)btn { // 1.三步曲,控制button状态切换 _selectedSubTitleBtn.selected = NO; btn.selected = YES; _selectedSubTitleBtn = btn; // 2.告诉代理,我点击选中了谁 if ([_delegate respondsToSelector:@selector(subTitleImgView:btnClicked:)]) { // 取出当前被点的button的文字 NSString *title = [btn titleForState:UIControlStateNormal]; // 假设 是 【全部】 if ([title isEqualToString:kAll]) { // 全部 --> 大标题 title = _mainTitle; } // 告诉代理(调用者),当前被点击的button的文字... [_delegate subTitleImgView:self btnClicked:title]; } } #pragma mark - 覆盖UIView的方法,又一次布局SubTitleImgView全部子控件的位置 // 控件SubTitleImgView本身的宽高发生改变等情况下就会自己主动触发layoutSubviews方法 - (void)layoutSubviews { // 1.一定要调用super [super layoutSubviews]; // 2.依据屏幕宽,算出总的列数,并对全部子标题button设置九宫格frame int columns = self.frame.size.width / kSubTitleBtnW; // 依据数据源的个数,遍历相应数目的button,依据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. // 分类或商区的子标题的代理方法,当点击了【分类或商区的子标题button】时,通知代理 #import <Foundation/Foundation.h> @class SubTitleImgView; @protocol SubTitleImgViewDelegate <NSObject> @optional // 当SubTitleImgView里面的button被点击了的时候调用,告诉其它全部想知道的类(即SubTitleImgView的代理):被点击的button的文字【被点击的分类或商区的子标题button上的文字】 - (void)subTitleImgView:(SubTitleImgView *)subTitleImgView btnClicked:(NSString *)btnTitle; // 返回当前选中的文字(比方分类菜单。就返回当前选中的分类名称。区域菜单。就返回当前选中的区域名称),目的是子标题button出现前,将选中的那个高亮(回显)~~~ // 得到当前选中的分类或商区button上的文字,用于与新出现的button文字进行推断,假设同样,则在SubTitleImgView出现之前,将SubTitleImgView上面的该button置为高亮,其它全为普通 // 假设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. // 点击顶部菜单中的分类频道 button(顶部菜单项),弹出的菜单,继承自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. // 点击顶部菜单中的分类频道 button(顶部菜单项),弹出的菜单,继承自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里面的子标题button点击时,会调用此方法,目的是 传递点击的【分类或商区的子标题button】文字 - (void)subTitleImgView:(SubTitleImgView *)subTitleImgView btnClicked:(NSString *)btnTitle { [MetaDataTool sharedMetaDataTool].currentCategoryName = btnTitle; } // 难点??? 得到并推断当前button是否选中的文字(比方分类菜单,就返回当前选中的分类名称;区域菜单,就返回当前选中的区域名称) - (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)里面的一个button,如美食,button图片在上面,文字在以下,且button右边是一根竖线 #import "PopMenuItem.h" @class MyCategory; @interface CategoryPopMenuItem : PopMenuItem // 数据源,本button,须要显示的分类对象模型,一个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)里面的一个button,button图片在上面,文字在以下,且button右边是一根竖线 #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方法,设置button的图片和文字 - (void)setCategory:(MyCategory *)category { _category = category; // 1.图标 [self setImage:[UIImage imageNamed:category.icon] forState:UIControlStateNormal]; // 2.标题 [self setTitle:category.name forState:UIControlStateNormal]; } #pragma mark 设置button上面的图片的frame - (CGRect)imageRectForContentRect:(CGRect)contentRect { return CGRectMake(0, 0, contentRect.size.width, contentRect.size.height * (1 - kTitleHeightRatio)); } #pragma mark 设置button以下的标题的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. // 点击顶部菜单中的 所有商区 button(顶部菜单项),弹出的菜单,继承自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. // 点击顶部菜单中的 所有商区 button(顶部菜单项),弹出的菜单,继承自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.遍历所有的商区对象,创建并设置button标题 int count = districts.count; for (int i = 0; i<count; i++) { DistrictPopMenuItem *item = nil; if (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里面的子标题button点击时,会调用此方法,目的是 传递点击的【分类或商区的子标题button】文字 - (void)subTitleImgView:(SubTitleImgView *)subTitleImgView btnClicked:(NSString *)btnTitle { [MetaDataTool sharedMetaDataTool].currentDistrictName = btnTitle; } // 难点?? ? 得到并推断当前button是否选中的文字(比方分类菜单,就返回当前选中的分类名称。区域菜单,就返回当前选中的区域名称) - (NSString *)subTitleImgViewGetCurrentBtnTitle:(SubTitleImgView *)subTitleImgView { // 假设SubTileImgView的代理是DistrictPopMenu,说明应该从工具类返回currentDistrictName给SubTileImgView return [MetaDataTool sharedMetaDataTool].currentDistrictName; } // 顶部菜单由于要改变其三个button的文字,因此在通知中心注冊成为了监听者,因此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 <Foundation/Foundation.h> @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; // 当前选中的排序对象 // 通过button上面的名字如(价格最高),到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时,可通过button上面的名字如(价格最高),到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
版权声明:本文博客原创文章。博客,未经同意,不得转载。