首页 > 代码库 > iOS_21团购_控制器继承关系图

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

最终效果图:





控制器继承关系图:

说明:

点击主控制器左侧的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



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