您的位置:首页 > 移动开发 > IOS开发

iOS_21团购_控制器继承关系图

2014-08-25 17:42 441 查看
最终效果图:



控制器继承关系图:

说明:

点击主控制器左侧的Dock上的按钮,

比如【团购】、【收藏】、【地图】时,

实现的功能有许多相同之处。

具体说明如下:

点击【团购】,以九宫格的形式显示一个个团购,

并且,点击一个Cell时,展示该Cell对应的团购详情

点击【收藏】,以九宫格的形式显示一个个已经归档的团购模型,

并且,点击一个Cell时,展示该Cell对应的团购详情

点击【地图】,以MapView上一个个大头针的形式显示团购模型(通过城市名+经度+纬度+半径作参数发送请求)

并且,点击一个大头针时,展示该Cell对应的团购详情



上述各控制器的主要功能和方法声明如下:



抽取的父类如下:

父类ShowDealDetailController
负责创建并展示和隐藏封装的【团购详情控制器】
私有成员:Cover

//
//  ShowDealDetailController.h
//  帅哥_团购
//
//  Created by beyond on 14-8-20.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  基类,父类,展示 订单详情控制器的控制器,当点击XXX时,需要展示团购详情的时候,就继承这个控制器即可,内部实现了创建并显示订单订单详情控制器,以及隐藏订单详情控制器

#import <UIKit/UIKit.h>
@class Deal;
@interface ShowDealDetailController : UIViewController

// 显示订单详情的控制器所依赖的数据模型(数据源)
- (void)showDetail:(Deal *)deal;
@end

//
//  ShowDealDetailController.m
//  帅哥_团购
//
//  Created by beyond on 14-8-20.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  基类,父类,展示 订单详情控制器的控制器,当点击XXX时,需要展示团购详情的时候,就继承这个控制器即可,内部实现了创建并显示订单订单详情控制器,以及隐藏订单详情控制器

#import "ShowDealDetailController.h"
#import "Cover.h"

// 真正的用xib封装的详情控制器,创建时,要传入deal数据源
#import "DealDetailController.h"
// 自己封装的全局统一样式的导航控制器
#import "BeyondNavigationController.h"
// 真正的用xib封装的详情控制器 其高度可变,但是宽度一般是要固定的
#define kDealDetailVCWidth 600
@interface ShowDealDetailController ()
{
// 遮盖
Cover *_cover;
}
@end

@implementation ShowDealDetailController

#pragma mark 显示详情控制器
- (void)showDetail:(Deal *)deal
{
// 1.显示遮盖
if (_cover == nil) {
_cover = [Cover coverWithTarget:self action:@selector(hideDetail)];
}
// self只是子(根)控制器,self的上方是导航控制器的导航栏,因此self.navigationController是拿到整个导航控制器的view(包括导航栏和它的根(子)控制器)
// ????导航控制器的view的宽高(包括导航栏)????因为self本控制器,每次出场,都是先经过导航控制器包装过的,因为self的顶部还有导航栏
_cover.frame = self.navigationController.view.bounds;
// 创建时透明,之后动画变黑
_cover.alpha = 0;
[UIView animateWithDuration:kDefaultAnimDuration animations:^{
[_cover alphaReset];
}];
// ???? 添加到导航控制器的view最上面,(盖住导航栏和其根控制器)
[self.navigationController.view addSubview:_cover];

// 2.创建并展示团购详情控制器
DealDetailController *detailVC = [[DealDetailController alloc] init];
// 重要~~~其导航条左边是关闭,点击后调用本控制器的方法执行动画关闭,创建出来的DealDetailController,之所以不到DealDetailController内部去设置这个按钮,是因为在内部无法方便地调用外部的这个隐藏DealDetailController的方法
detailVC.navigationItem.leftBarButtonItem = [UIBarButtonItem itemWithIcon:@"btn_nav_close.png" highlightedIcon:@"btn_nav_close_hl.png" target:self action:@selector(hideDetail)];
// 需提供数据源,供其内部的从xib生成的子控件显示数据
detailVC.deal = deal;
// 用导航控制器包装 真正的团购详情控制器
BeyondNavigationController *nav = [[BeyondNavigationController alloc] initWithRootViewController:detailVC];
// 为实现详情控制器的抽屉效果,监听pan手势拖拽
[nav.view addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(drag:)]];
// 详情控制器始终靠右边,所以左边距伸缩,高度也伸缩
nav.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin;
// ????在遮罩的右边,意思是先完全看不见,然后动画慢慢向左移动出场???
// 真正的用xib封装的详情控制器 其高度可变,但是宽度一般是要固定的
nav.view.frame = CGRectMake(_cover.frame.size.width, 0, kDealDetailVCWidth, _cover.frame.size.height);
// 当2个控制器互为父子关系时,它们的view也是互为父子关系,因为如果只加view,不加控制器,那么控制器在本方法调用完毕之后就会被销毁,因为其为局部变量
[self.navigationController.view addSubview:nav.view];
[self.navigationController addChildViewController:nav];

// 动画向左慢慢移动出场,显示出详情控制器
[UIView animateWithDuration:kDefaultAnimDuration animations:^{
CGRect f = nav.view.frame;
f.origin.x -= kDealDetailVCWidth;
nav.view.frame = f;
}];
}

#pragma mark 隐藏详情控制器
- (void)hideDetail
{
// 取得包装了详情控制器的导航控制器,动画隐藏其view
UIViewController *nav = [self.navigationController.childViewControllers lastObject];
[UIView animateWithDuration:0.3 animations:^{
// 1.隐藏遮盖
_cover.alpha = 0;

// 2.隐藏控制器
CGRect f = nav.view.frame;
f.origin.x += kDealDetailVCWidth;
nav.view.frame = f;
} completion:^(BOOL finished) {
[_cover removeFromSuperview];

[nav.view removeFromSuperview];
[nav removeFromParentViewController];
}];
}
#pragma mark - 让详情控制器有抽屉效果,外加弹簧效果
- (void)drag:(UIPanGestureRecognizer *)pan
{
// 向左走为负
CGFloat tx = [pan translationInView:pan.view].x;
// 手势结束时,即松手
if (pan.state == UIGestureRecognizerStateEnded) {
CGFloat halfW = pan.view.frame.size.width * 0.5;
if (tx >= halfW) { // 已经往右边挪动超过一半了
[self hideDetail];
} else {
[UIView animateWithDuration:kDefaultAnimDuration animations:^{
pan.view.transform = CGAffineTransformIdentity;
}];
}
} else { // 移动控制器的view
if (tx < 0) { // 向左边拽
tx *= 0.4;
}
pan.view.transform = CGAffineTransformMakeTranslation(tx, 0);
}
}
@end


父类BaseDealListController
负责创建并维护一个CollectionView,并且向子类要数据源totalDealsArr

//
//  BaseDealListController.h
//  帅哥_团购
//
//  Created by beyond on 14-8-21.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  位于中间层的父类,继承自ShowtDealDetailController,自动拥有了点击了XXX,只要供给数据源,就动画展示团购详情的功能,并自动拥有了点击遮盖,动画隐藏掉团购详情控制器的功能....自己只负责建立维护一个CollectionView(九宫格),并且九宫格的数据源(团购对象数组)由子类 自己提供

#import "ShowDealDetailController.h"

@interface BaseDealListController : ShowDealDetailController
{
UICollectionView *_collectionView;
}
// 九宫格的数据源(团购对象数组)由子类 自己提供
- (NSArray *)totalDeals; // 所有的团购数据
@end

//
//  BaseDealListController.m
//  帅哥_团购
//
//  Created by beyond on 14-8-21.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  位于中间层的父类,继承自ShowtDealDetailController,自动拥有了点击了XXX,只要供给数据源,就动画展示团购详情的功能,并自动拥有了点击遮盖,动画隐藏掉团购详情控制器的功能....自己只负责建立维护一个CollectionView(九宫格),并且九宫格的数据源(团购对象数组)由子类 自己提供

#import "BaseDealListController.h"
#import "Deal.h"
#import "DealCell.h"
// 每一个格子的宽和高
#define kItemW 250
#define kItemH 250
@interface BaseDealListController ()<UICollectionViewDataSource, UICollectionViewDelegate>
// 自己创建并维护一个九宫格,数据源(团购对象数组)由子类提供
@property (nonatomic, strong) UICollectionView *collectionView;
@end

@implementation BaseDealListController

#pragma mark - 生命周期方法

- (void)viewDidLoad
{
[super viewDidLoad];

// 1.创建自己的collectionView
[self addCollectionView];

// 2.注册cell格子要用到的xib文件
[self.collectionView registerNib:[UINib nibWithNibName:@"DealCell" bundle:nil] forCellWithReuseIdentifier:@"DealCell"];

// 3.设置collectionView永远支持垂直滚动,为下拉刷新准备(弹簧)
self.collectionView.alwaysBounceVertical = YES;

// 4.设置collectionView的背景色
self.collectionView.backgroundColor = kGlobalBg;
}

// 1.创建自己的collectionView
- (void)addCollectionView
{
// 创建一个流布局,必须指定
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
// 设置流布局里面的每一个格子宽和高,即每一个网格的尺寸
layout.itemSize = CGSizeMake(kItemW, kItemH);
// 每一行之间的间距
layout.minimumLineSpacing = 20;
// 指定的流布局创建一个collectionView,并且用成员变量记住
self.collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
// 高度和宽度自动伸缩
self.collectionView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
[self.view addSubview:self.collectionView];
}
#pragma mark 在viewWillAppear和viewDidAppear中可以取得view最准确的宽高(width和height)
// 重要~~~因为在控制器创建时,宽默认是768,高默认是1024,不管横竖屏
// 只有在viewWillAppear和viewDidAppear方法中,可以取得view最准确的(即实际的)宽和高(width和height)
- (void)viewWillAppear:(BOOL)animated
{
// 默认计算layout
[self didRotateFromInterfaceOrientation:0];
}
#pragma mark - 父类方法

// 拦截,屏幕即将旋转的时候调用(控制器监控屏幕旋转)
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
//log(@"屏幕即将旋转");
}

#pragma mark 屏幕旋转完毕的时候调用
// 拦截,屏幕旋转完毕的时候调用
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
// 1.取出创建CollectionViewController时传入的的UICollectionViewFlowLayout
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;

// 2.计算间距
CGFloat v = 0;
CGFloat h = 0;
CGFloat height = self.view.frame.size.height -44;
CGFloat width = self.view.frame.size.width;
if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)
) {
// 横屏的间距
v = (height - 2 * kItemH) / 3;
h = (width - 3 * kItemW) / 4;

} else {
// 竖屏的间距
v = (height - 3 * kItemH) / 4;
h = (width - 2 * kItemW) / 3;
}
// 3.动画调整格子之间的距离
[UIView animateWithDuration:4.0 animations:^{
// 上 左 下 右 四个方向的margin
layout.sectionInset = UIEdgeInsetsMake(h, h, v, h);
// 每一行之间的间距
layout.minimumLineSpacing = h;
}];
}

#pragma mark - collectionView代理方法
// 共有多少个Item(就是格子Cube),询问子类
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.totalDeals.count;
}
#pragma mark 刷新数据的时候会调用(reloadData)
#pragma mark 每当有一个cell重新进入屏幕视野范围内就会调用
// 生成每一个独一无二的格子,询问子类
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"DealCell";
DealCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:ID forIndexPath:indexPath];

cell.deal = self.totalDeals[indexPath.row];

return cell;
}
// 点击了一个格子时,showDealDetailVC
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
// 再次调用父类的方法,展示dealDetail控制器
[self showDetail:self.totalDeals[indexPath.row]];
}
#pragma mark - 生命周期方法
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end

子类 DealListController

只需继承即可,并向父类提供数据源totalDealsArr

//
//  DealListController.h
//  帅哥_团购
//
//  Created by beyond on 14-8-14.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  点击dock上面的【团购】按钮对应的控制器,上面是导航栏,导航栏右边是searchBar,导航栏左边是一个大按钮(TopMenu)(内部由三个小按钮组成<TopMenuItem>)
// 本控制器继承自BaseDealListController,便自动拥有了九宫格,只需要为其提供数据即可,而BaseDealListController又继承自ShowDealDetailVc,拥有展示闲情的功能
#import "BaseDealListController.h"

@interface DealListController : BaseDealListController

@end


//
//  DealListController.m
//  帅哥_团购
//
//  Created by beyond on 14-8-14.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//  点击dock上面的【团购】按钮对应的控制器,上面是导航栏,导航栏右边是searchBar,导航栏左边是一个大按钮(TopMenu)(内部由三个小按钮组成<TopMenuItem>)

#import "DealListController.h"
// 导航栏左边是一个大按钮(顶部菜单)
#import "TopMenu.h"
// 封装的自定义cell
#import "DealCell.h"
// 点评提供的封装发送请求的类
#import "DPAPI.h"
// 工具类
#import "MetaDataTool.h"
// 封装请求的工具类
#import "DealRequestTool.h"
// 模型类
#import "City.h"
#import "Deal.h"
// 二次封装的图片下载工具类
#import "ImgDownloadTool.h"

#define kItemW 250
#define kItemH 250
@interface DealListController()<DPRequestDelegate,MJRefreshBaseViewDelegate>
{
// 用于接收服务器返回的字典数组----转化成的对象数组,供格子们显示
NSMutableArray *_deals;
// 下拉刷新
MJRefreshHeaderView *_header;
// 上拉加载下一页
MJRefreshFooterView *_footer;

// 每次加载的页码数
int _pageNo;

}
@end
@implementation DealListController
// 继承BaseDealListController控制器,必须实现的方法,目的是为collectionView提供数据源
- (NSArray *)totalDeals
{
return _deals;
}

- (void)viewDidLoad
{
[super viewDidLoad];
_deals = [NSMutableArray array];

// 1.顶部导航栏的基本设置
[self setNavigationBar];

// 2.添加刷新控件
[self addRefresher];

// 0.监听到城市改变的通知了,就下拉刷新
kAddAllNotes(dataChange)
}

// 0.监听到城市改变的通知了,就下拉刷新
- (void)dataChange
{
[_header beginRefreshing];
}

// 1.顶部导航栏的基本设置
- (void)setNavigationBar
{

// 1.右边的搜索框
UISearchBar *s = [[UISearchBar alloc] init];
s.frame = CGRectMake(0, 0, 210, 35);
s.placeholder = @"请输入商品名、地址等";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:s];

// 2.左边的菜单栏
TopMenu *top = [[TopMenu alloc] init];
// 重要,TopMenu里面的item点击后,创建的PopMenu将要添加到哪儿去???就是本控制器的view
top.controllerView = self.view;
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:top];

}

// 3.添加刷新控件
- (void)addRefresher
{
_header = [MJRefreshHeaderView header];
_header.scrollView = _collectionView;
_header.delegate = self;

_footer = [MJRefreshFooterView footer];
_footer.scrollView = _collectionView;
_footer.delegate = self;

}
#pragma mark 刷新控件的代理方法
- (void)refreshViewBeginRefreshing:(MJRefreshBaseView *)refreshView
{
// 标记一下,是下拉 还是上拉
BOOL isHeader = [refreshView isKindOfClass:[MJRefreshHeaderView class]];
if (isHeader) {
// 下拉刷新
// 建议先,清除前面下载的图片内存缓存
[ImgDownloadTool clear];
// 每次下拉,都是加载第一页
_pageNo = 1;
} else {
// 上拉加载更多,就是加载下一页
_pageNo++;
}

// 加载第第_pageNo页的数据
[[DealRequestTool sharedDealRequestTool] dealRequestWithPageNo:_pageNo success:^(NSArray *deals,int total_count) {
if (isHeader) {
// 如果是下拉加载第一页数据,则先移除旧的
[_deals removeAllObjects];
}
// 1.添加新的返回的对象数组
[_deals addObjectsFromArray:deals];

// 2.刷新表格
[_collectionView reloadData];

// 3.恢复刷新状态
[refreshView endRefreshing];

// 4.根据总数量简单判断是否需要隐藏上拉控件
_footer.hidden = _deals.count >= total_count;

} fail:^(NSError *error) {

log(@"请求失败--%@",error);
// 也要隐藏
[refreshView endRefreshing];
}];
}

@end
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息