您的位置:首页 > 其它

团购_顶部菜单和弹出菜单联动

2016-05-14 00:00 375 查看
iOS_21团购_顶部菜单和弹出菜单联动

最终效果图:



各控件关系图2:



点击Dock上面的按钮DockItem,

创建经导航控制器包装的DealListController,

并且添加到主控制器的右侧空间

点击dock上面的【团购】按钮对应的控制器,上面是导航栏,导航栏右边是searchBar,导航栏左边是一个大按钮(TopMenu)(内部由三个小按钮组成) #import "DealListController.h" // 导航栏左边是一个大按钮(顶部菜单) #import "TopMenu.h" @interface DealListController () @end @implementation DealListController - (void)viewDidLoad { [super viewDidLoad]; // 1,设置上方的导航栏,右边是搜索bar,左边是一个大的VIEW(内有三个按钮),即TopMenu,内部的按钮是TopMenuItem [self addNaviBarBtn]; } // 1,设置上方的导航栏,右边是搜索bar,左边是一个大的VIEW(内有三个按钮),即TopMenu,内部的按钮是TopMenuItem - (void)addNaviBarBtn { // 1.右边的搜索框 UISearchBar *s = [[UISearchBar alloc] init]; s.frame = CGRectMake(0, 0, 210, 35); s.placeholder = @"请输入商品名、地址等"; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:s]; // 2.左边的菜单栏,导航栏左边是一个大按钮(顶部菜单) TopMenu *topMenu = [[TopMenu alloc] init]; // 3.用于点击顶部按钮时,容纳创建出来的底部弹出菜单(包括一个contentView和cover,contentView又包括scrollView和subTitleImgView),本成员是由创建此TopMenu的外部赋值传入, 这里是控制器的view,就是导航栏下面的所有区域 // 重要~~~~~~~~~~ topMenu.controllerView = self.view; self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:topMenu]; } @end

TopMenu.h

//
//  TopMenu.h
//  帅哥_团购
//
//  Created by beyond on 14-8-15.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  点击dock上面的【团购】按钮时,创建出对应的经导航包装后的子控制器,子控制器的上面是导航栏,导航栏右边是searchBar,导航栏左边是一个大按钮(TopMenu)(内部只由三个小按钮组成它们分别是:全部频道,全部商区,默认排序),点击TopMenu中的某一个按钮,会在其下方,弹出一个PopMenu,PopMenu包括二个部分(上面是一个contentView:包括:scrollView和subTitleImgView,下:蒙板)

#import

@interface TopMenu : UIView

//  用于点击顶部菜单项时,容纳创建出来的底部弹出菜单(包括一个contentView和cover,contentView又包括scrollView和subTitleImgView),本成员是由创建此TopMenu的控制器赋值传入, 本成员属性是用来接收控制器的view,就是导航栏下面的所有区域,目的是用于添加并展示PopMenu
@property (nonatomic, weak) UIView *controllerView;
@end


TopMenu.m

负责创建和添加3个TopMenuItem,

监听其内部三个TopMenuItem的点击事件,

并且根据点击的按钮的tag不同,创建出不同的PopMenu,并控制PopMenu的出现和隐藏,最后一个是注册到通知中心,监听所有通知,并且设置三个TopMenuItem的显示文字

//
//  TopMenu.m
//  帅哥_团购
//
//  Created by beyond on 14-8-15.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  点击dock上面的【团购】按钮对应的控制器,上面是导航栏,导航栏右边是searchBar,导航栏左边是一个大按钮(TopMenu)(内部只由三个小按钮组成它们分别是:全部频道,全部商区,默认排序),点击TopMenu中的某一个按钮,会在其下方,弹出一个PopMenu,PopMenu包括二个部分(上面是一个contentView:包括:scrollView和subTitleImgView,下:蒙板)

#import "TopMenu.h"

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

@interface TopMenu()
{
// 三个顶部菜单项中当前选中的那一个,三个按钮:全部分类,全部商区,默认排序
TopMenuItem *_currentTopMenuItem;

// 在点击不同的顶部菜单项的时候,因为要控制其对应的底部弹出菜单的出现和隐藏,因此,在创建它时,就要用成员变量,记住 弹出的分类菜单
CategoryPopMenu *_categoryPopMenu;
// 在点击不同的顶部菜单项的时候,因为要控制其对应的底部弹出菜单的出现和隐藏,因此,在创建它时,就要用成员变量,记住 弹出的区域菜单
DistrictPopMenu *_districtPopMenu;
// 在点击不同的顶部菜单项的时候,因为要控制其对应的底部弹出菜单的出现和隐藏,因此,在创建它时,就要用成员变量,记住 弹出的排序菜单
OrderPopMenu *_orderPopMenu;

// 正在展示的底部弹出菜单,是个父类
PopMenu *_showingPopMenu;

// 因为要更改其显示文字,所以要成员变量记住创建出来的 分类菜单项
TopMenuItem *_categoryTopMenuItem;
// 因为要更改其显示文字,所以要成员变量记住创建出来的 区域菜单项
TopMenuItem *_districtTopMenuItem;
// 因为要更改其显示文字,所以要成员变量记住创建出来的 排序菜单项
TopMenuItem *_orderTopMenuItem;
}
@end

@implementation TopMenu

- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {

// 1.添加一个按钮:全部分类 (因为要更改其显示文字,所以要成员变量记住)
_categoryTopMenuItem = [self addMenuItem:kAllCategory index:0];

// 2.添加一个按钮:全部商区 (因为要更改其显示文字,所以要成员变量记住)
_districtTopMenuItem = [self addMenuItem:kAllDistrict index:1];

// 3.添加一个按钮:默认排序 (因为要更改其显示文字,所以要成员变量记住)
_orderTopMenuItem = [self addMenuItem:@"默认排序" index:2];

// 4.顶部菜单需要注册并监听所有通知,目的是更改其里面菜单项的文字,并且控制弹出菜单的显示和隐藏
kAddAllNotes(dataChange)
}
return self;
}
// 5,抽取的方法,添加一个顶部菜单项(TopMenuItem),三个按钮:全部分类,全部商区,默认排序
- (TopMenuItem *)addMenuItem:(NSString *)title index:(int)index
{
TopMenuItem *item = [[TopMenuItem alloc] init];
item.title = title;
item.tag = index;
// 三个按钮:全部分类,全部商区,默认排序  水平排列
item.frame = CGRectMake(kTopMenuItemW * index, 0, 0, 0);
// 重要~~~顶部按钮(三个按钮:全部分类,全部商区,默认排序)被点击之后,都会调用此方法,根据tag进行区分,以便弹出不同的PopMenu
[item addTarget:self action:@selector(topMenuItemClick:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:item];
return item;
}

// 4.注册并监听所有通知时调用此方法,更改按钮的文字,控制弹出菜单显示和隐藏
- (void)dataChange
{
// 0.取消当前TopMenuItem的选中状态,并且置空_currentTopMenuItem ,因为监听到通知时,肯定是用户点击了一个子标题按钮,或者一个没有子标题的PopMenuItem
_currentTopMenuItem.selected = NO;
_currentTopMenuItem = nil;

// 1.设置 分类按钮 要显示的文字,从工具中获得
NSString *c = [MetaDataTool sharedMetaDataTool].currentCategoryName;
if (c) {
_categoryTopMenuItem.title = c;
}

// 2.设置 商区按钮 要显示的文字,从工具中获得
NSString *d = [MetaDataTool sharedMetaDataTool].currentDistrictName;
if (d) {
_districtTopMenuItem.title = d;
}

// 3.设置 排序按钮 要显示的文字,从工具中获得
NSString *o = [MetaDataTool sharedMetaDataTool].currentOrder.name;
if (o) {
_orderTopMenuItem.title = o;
}

// 4.最后,调用正在显示的弹出菜单的方法:隐藏底部弹出菜单,并置空正在显示的弹出菜单
[_showingPopMenu hidePopMenu];
_showingPopMenu = nil;
}

// 5-1,重要~~~监听顶部菜单项的点击,
// 顶部按钮(三个按钮:全部分类,全部商区,默认排序)被点击之后,都会调用此方法,根据tag进行区分,以便弹出不同的PopMenu
- (void)topMenuItemClick:(TopMenuItem *)item
{
// 0.如果还没有选择好城市,则不允许点击顶部菜单按钮
if ([MetaDataTool sharedMetaDataTool].currentCity == nil) return;
// 1.控制选中状态的切换,先把前面记住的当前顶部菜单项取消选中
_currentTopMenuItem.selected = NO;
// 如果两次点击的是同一个顶部菜单项,则隐藏掉弹出的菜单,并且置空当前的选中TopMenuItem
if (_currentTopMenuItem == item) {
_currentTopMenuItem = nil;
// 隐藏底部菜单
[self hidePopMenu];
} else {
// 如果两次点击的是是不同的顶部菜单项,将TopMenuItem置为选中状态,且用成员变量记住,并且展示相应的底部弹出菜单
item.selected = YES;
_currentTopMenuItem = item;
// 显示与顶部菜单项,对应的底部弹出菜单
[self showPopMenu:item];
}
}
// 5-2,点击了相同的TopMenuItem,要隐藏底部已经弹出的菜单,并且置其为空
- (void)hidePopMenu
{
// 调用_showingPopMenu其自己的方法,隐藏并移除其内部的contentView(包括scrollView和subTitleImgView,并置cover透明),并置空_showingPopMenu
[_showingPopMenu hidePopMenu];
_showingPopMenu = nil;
}

// 5-3,点击了不同的TopMenuItem,要显示相应的底部弹出菜单
- (void)showPopMenu:(TopMenuItem *)item
{
// 1,先判断 是否需要让弹出菜单执行出场动画(没有正在展示的弹出菜单时,才需要执行出场动画)
BOOL animted;
// 如果有正在显示的弹出菜单,如切换按钮点击的时候
if (_showingPopMenu) {
// 1-1,先移除当前正在显示的弹出菜单
[_showingPopMenu removeFromSuperview];
// 1-2,不要动画出场
animted = NO;
}else{
// 没有正在展示的弹出菜单时,才需要执行出场动画
animted = YES;
}

// 2,根据点击的顶部菜单项的tag,创建并显示不同的底部弹出菜单(三个按钮:全部分类,全部商区,默认排序)
if (item.tag == 0) {
// 创建分类弹出菜单,并且用成员记住,且置其为正在展示的PopMenu
if (_categoryPopMenu == nil) {
_categoryPopMenu = [[CategoryPopMenu alloc] init];
}
_showingPopMenu = _categoryPopMenu;
} else if (item.tag == 1) { // 区域
// 创建商区弹出菜单,并且用成员记住,且置其为正在展示的PopMenu
if (_districtPopMenu == nil) {
_districtPopMenu = [[DistrictPopMenu alloc] init];
}
_showingPopMenu = _districtPopMenu;
} else {
// 创建 排序弹出菜单,并且用成员记住,且置其为正在展示的PopMenu
if (_orderPopMenu == nil) {
_orderPopMenu = [[OrderPopMenu alloc] init];
}
_showingPopMenu = _orderPopMenu;
}

// 创建出来相应的底部弹出菜单后,就要设置_showingPopMenu   的frame
// _showingPopMenu.frame = _controllerView.bounds;

// PopMenu 占据导航栏以下所有的空间
_showingPopMenu.frame = (CGRect){0,kTopMenuItemH-1,_controllerView.bounds.size.width,_controllerView.bounds.size.height- kTopMenuItemH};

// 设置创建出来的PopMenu的block回调,传递的是XXXPopMenu隐藏后,顶部菜单要做的事情如更改顶部的TopMenu的按钮选中状态
__unsafe_unretained TopMenu *menu = self;
_showingPopMenu.hideBlock = ^{
// 重要~~~当_showingPopMenu隐藏后,要更改顶部的TopMenu的按钮选中状态

// 1.取消当前的TopMenuItem的选中状态,并置空
menu->_currentTopMenuItem.selected = NO;
menu->_currentTopMenuItem = nil;

// 2._showingPopMenu隐藏后,就要清空_showingPopMenu
menu->_showingPopMenu = nil;
};

// 添加创建出来的即将要显示的弹出菜单 到_controllerView(即导航栏下面的所有空间都是弹出菜单的)
[_controllerView addSubview:_showingPopMenu];

// 执行刚才创建出来的底部弹出菜单的 出场动画,注意:只有没有正在展示的弹出菜单时,才需要执行出场动画)
if (animted) {
[_showingPopMenu showPopMenu];
}
}

// 顶部菜单宽度固定是三个按钮的宽高,因为只有三个按钮:全部分类,全部商区,默认排序
- (void)setFrame:(CGRect)frame
{
frame.size = CGSizeMake(3 * kTopMenuItemW, kTopMenuItemH);
[super setFrame:frame];
}
// 顶部菜单因为要改变其三个按钮的文字,因此在通知中心注册成为了监听者,因此dealloc时要在通知中心,移除掉监听者
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end


TopMenuItem 顶部菜单项按钮

//
//  TopMenuItem.m
//  帅哥_团购
//
//  Created by beyond on 14-8-15.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  点击dock上面的【团购】按钮对应的控制器,上面是导航栏,导航栏右边是searchBar,导航栏左边是一个大按钮(TopMenu)(内部只由三个小按钮组成它们分别是:全部频道,全部商区,默认排序),点击TopMenu中的某一个按钮,会在其下方,弹出一个PopMenu,PopMenu包括二个部分(上面是一个contentView:包括:scrollView和subTitleImgView,下:蒙板)

#import

@interface TopMenuItem : UIButton
// 设置按钮TopMenuItem显示的文字
@property (nonatomic, copy) NSString *title;
@end

// 左文字 如全部分类 、全部商区、默认排序
#define kTitleScale 0.8
@implementation TopMenuItem

- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// 1.文字颜色
[self setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
self.titleLabel.textAlignment = NSTextAlignmentCenter;
self.titleLabel.font = [UIFont systemFontOfSize:15];

// 2.设置按钮右边的箭头
[self setImage:[UIImage imageNamed:@"ic_arrow_down.png"] forState:UIControlStateNormal];
self.imageView.contentMode = UIViewContentModeCenter;

// 3.设置按钮右边的分割线
UIImage *img = [UIImage imageNamed:@"separator_topbar_item.png"];
UIImageView *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

//
//  PopMenuItem.h
//  帅哥_团购
//
//  Created by beyond on 14-8-16.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  底部弹出菜单的菜单项 (是一个父类)  抽取的特征:1,右边有分隔线,2.宽高全统一,3.选中时,背景图片统一,4文字颜色

#import

@interface PopMenuItem : UIButton

// 本接口,专门交给子类实现
// 数据源,子标题数组,所有子标题的名字组成的数组,
// 比如 美食 下面有多少个category
// 比如 海淀区 下面有多少个place
- (NSArray *)subTitlesArr;
@end

PopMenuItem.m

//
//  PopMenuItem.m
//  帅哥_团购
//
//  Created by beyond on 14-8-16.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  底部弹出菜单的菜单项 (是一个父类)  抽取的特征:1,右边有分隔线,2.宽高全统一,3.选中时,背景图片统一,4文字颜色

#import "PopMenuItem.h"

@implementation PopMenuItem

- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// 1.右边的分割线
UIImage *img = [UIImage imageNamed:@"separator_filter_item.png"];
UIImageView *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


SubTitleImgView定义的协议

SubTitleImgViewDelegate.h

//
//  SubTitleViewDelegate.h
//  帅哥_团购
//
//  Created by beyond on 14-8-16.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  分类或商区的子标题的代理方法,当点击了【分类或商区的子标题按钮】时,通知代理

#import

@class SubTitleImgView;
@protocol SubTitleImgViewDelegate

@optional

// 当SubTitleImgView里面的按钮被点击了的时候调用,告诉其他所有想知道的类(即SubTitleImgView的代理):被点击的按钮的文字【被点击的分类或商区的子标题按钮上的文字】
- (void)subTitleImgView:(SubTitleImgView *)subTitleImgView btnClicked:(NSString *)btnTitle;

// 返回当前选中的文字(比如分类菜单,就返回当前选中的分类名称;区域菜单,就返回当前选中的区域名称),目的是子标题按钮出现前,将选中的那个高亮(回显)~~~
// 得到当前选中的分类或商区按钮上的文字,用于与新出现的按钮文字进行判断,如果相同,则在SubTitleImgView出现之前,将SubTitleImgView上面的该按钮置为高亮,其他全为普通
// 如果SubTileImgView的代理是CategoryPopMenu,说明应该从工具类返回currentCategoryName给SubTileImgView
- (NSString *)subTitleImgViewGetCurrentBtnTitle:(SubTitleImgView *)subTitleImgView;
@end


View的层级关系示意图:

父类:PopMenu

其子类:CategoryPopMenu、DistrictPopMenu、OrderPopMenu

父类:PopMenuItem

其子类:CategoryPopMenuItem、DistrictPopMenuItem、OrderPopMenuItem



子类:CategoryPopMenu

//
//  CategoryPopMenu.h
//  帅哥_团购
//
//  Created by beyond on 14-8-15.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  点击顶部菜单中的分类频道 按钮(顶部菜单项),弹出的菜单,继承自PopMenu,包括二个部分(上:contentView,包括scrollView和SubTitleImgView,下:蒙板)

#import "PopMenu.h"

@interface CategoryPopMenu : PopMenu

@end

//
//  CategoryPopMenu.m
//  帅哥_团购
//
//  Created by beyond on 14-8-15.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  点击顶部菜单中的分类频道 按钮(顶部菜单项),弹出的菜单,继承自PopMenu,包括二个部分(上:contentView,包括scrollView和SubTitleImgView,下:蒙板)

#import "CategoryPopMenu.h"
// 分类菜单项如:美食
#import "CategoryPopMenuItem.h"
#import "MetaDataTool.h"

@implementation CategoryPopMenu

- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// 1.往scrollView里面添加内容(CategoryPopMenuItem)
[self addCategoryPopMenuItem];
}
return self;
}
// 1.往scrollView里面添加内容(CategoryPopMenuItem)
- (void)addCategoryPopMenuItem
{
// 获取数据源,工具类提供allCategoriesArr对象数组
NSArray *categories = [MetaDataTool sharedMetaDataTool].allCategoriesArr;

// 1.往scrollView里面添加内容(CategoryPopMenuItem)
int count = categories.count;
for (int i = 0; i


子类: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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: