转载

iOS开发:一个高仿美团的团购ipad客户端的设计和实现(功能:根据拼音进行检索并展示数据,离线缓...

大致花了一个月时间,利用各种空闲时间,将这个客户端实现了,在这里主要是想记录下,设计的大体思路以及实现过程中遇到的坑......

这个项目的github地址:https://github.com/wzpziyi1/GroupPurchase

iOS开发:一个高仿美团的团购ipad客户端的设计和实现(功能:根据拼音进行检索并展示数据,离线缓...

主要实现的功能,用UICollectionViewController展示团购数据,根据拼音进行检索并展示数据,离线缓存团购数据,浏览记录与收藏记录的批量删除,友盟分享的集成,利用UIView+AutoLayout写布局,实现地图定位、自定义大头针等

整个项目,按功能分,有六个大的模块,每个模块里面都有各自的MVC,在各个MVC里面,再按照功能的不同,新建一下文件夹,存放实现统一功能的文件。

除此之外,还有一个Main模块,里面的Lib意为Library,放一些实现此客户端所需要用到的第三方库。Category文件夹里面,放整个项目各个地方都有可能需要用到的分类;Controller里面,放上一些最主要功能的ViewController,比如,自定义的NavigationController和TabBarController,那么整个项目的Navigation或者TabBarController都是可以根据需要继承自自定义的NavigationController,这里,我放的是:

iOS开发:一个高仿美团的团购ipad客户端的设计和实现(功能:根据拼音进行检索并展示数据,离线缓...

后续,Home(首页)界面的viewController和Search(搜索)界面的viewController都是继承自ZYDealViewController的,Collect(收藏的团购记录)和Browse(浏览的团购记录)界面的viewController都是继承自ZYOldDealViewController,整个项目多个地方用到,所以我放在了这里。

Other文件夹里面,我放的是一些杂七杂八的文件,比如说appDelegate等。Tool文件夹里面,我放的是一些工具类文件.

其中,Tool和Category里面的文件不要和他类耦合,因为这些东西,我们写好一次之后,基本上是可以拖到下个项目直接使用的。

Home界面

如图:

iOS开发:一个高仿美团的团购ipad客户端的设计和实现(功能:根据拼音进行检索并展示数据,离线缓...

在联网情况下,默认进来就是广州的所有团购信息,支持上拉、下拉刷新,已经适配好横竖屏。除此之外,还设置了当前地区、分类条件下,所有的团购信息都已经加载完毕了,那么将不可以进行上拉刷新操作(因为所有团购信息加载完毕,已经没有数据可以加载,那么应该要隐藏footer),默认是一次刷新10条团购信息。

除此之外,如果在当前条件下,如果没有团购信息,那么我显示的是一张提示图片:

iOS开发:一个高仿美团的团购ipad客户端的设计和实现(功能:根据拼音进行检索并展示数据,离线缓...

下面这两个弹框是一样的,当初第一次写的时候,是分别写了两个popViewController,后面在进行优化的时候,发现是可以写成一个通用的双表,这样就进行了一系列的优化,使得以后遇到类似的双表界面,直接将我写好的类拖过去,就可以继续使用了,图片如下:

iOS开发:一个高仿美团的团购ipad客户端的设计和实现(功能:根据拼音进行检索并展示数据,离线缓...

iOS开发:一个高仿美团的团购ipad客户端的设计和实现(功能:根据拼音进行检索并展示数据,离线缓...

这是ZYHomeDropdown类,代码如下:

#import <UIKit/UIKit.h> @class ZYHomeDropdown; @protocol ZYHomeDropdownDataSource <NSObject> /**  *  左边表格一共有多少行  */ - (NSUInteger)numberOfRowsInMainTable:(ZYHomeDropdown *)homeDropdown; /**  *  左边表格每一行的标题  *  */ - (NSString *)homeDropdown:(ZYHomeDropdown *)homeDropdown titleForRowInMainTable:(NSUInteger)row; /**  *  左边表格每一行的子数据  *  */ - (NSArray *)homeDropdown:(ZYHomeDropdown *)homeDropdown subDataForRowInMainTable:(NSUInteger)row; @optional /**  *  左边表格每一行的图标  *  */ - (NSString *)homeDropdown:(ZYHomeDropdown *)homeDropdown normalIconForRowInMainTable:(NSUInteger)row; /**  *  左边表格每一行的选中图标  *  */ - (NSString *)homeDropdown:(ZYHomeDropdown *)homeDropdown selectedIconForRowInMainTable:(NSUInteger)row; @end @protocol ZYHomeDropdownDelegate <NSObject> - (void)homeDropdown:(ZYHomeDropdown *)homeDropdown didSelectedRowInMainTable:(int)row; - (void)homeDropdown:(ZYHomeDropdown *)homeDropdown didSelectedRowInSubTable:(int)subRow mainRow:(int)mainRow; @end @interface ZYHomeDropdown : UIView @property (nonatomic, weak) id<ZYHomeDropdownDataSource>dataSource; @property (nonatomic, weak) id<ZYHomeDropdownDelegate>delegate; + (instancetype)homeDropdown; @end #import "ZYHomeDropdown.h" #import "ZYHomeMainCell.h" #import "ZYHomeSubCell.h" @interface ZYHomeDropdown () <UITableViewDelegate, UITableViewDataSource> @property (weak, nonatomic) IBOutlet UITableView *mainTableView; @property (weak, nonatomic) IBOutlet UITableView *subTableview; //主表中被选的cell的row @property (nonatomic, assign) int selectedMainRow; @end @implementation ZYHomeDropdown + (instancetype)homeDropdown {  return [[self alloc] init]; } - (instancetype)init {  if (self = [super init]) {   self = [[[NSBundle mainBundle] loadNibNamed:@"ZYHomeDropdown" owner:nil options:nil] lastObject];   [self commitInit];  }  return self; } - (void)commitInit {  self.mainTableView.delegate = self;  self.mainTableView.dataSource = self;  self.subTableview.delegate = self;  self.subTableview.dataSource = self; } - (void)awakeFromNib {  self.autoresizingMask = UIViewAutoresizingNone; } #pragma mark ----UITableViewDataSource - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {  return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {  if (self.mainTableView == tableView) {   return [self.dataSource numberOfRowsInMainTable:self];  }  else{   return [self.dataSource homeDropdown:self subDataForRowInMainTable:self.selectedMainRow].count;  } } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {  if (tableView == self.mainTableView) {   ZYHomeMainCell *cell = [ZYHomeMainCell mainCellWithTableView:tableView];   cell.textLabel.text = [self.dataSource homeDropdown:self titleForRowInMainTable:indexPath.row];   if ([self.dataSource respondsToSelector:@selector(homeDropdown:normalIconForRowInMainTable:)]) {    cell.imageView.image = [UIImage imageNamed:[self.dataSource homeDropdown:self normalIconForRowInMainTable:indexPath.row]];   }   if ([self.dataSource respondsToSelector:@selector(homeDropdown:selectedIconForRowInMainTable:)]) {    cell.imageView.highlightedImage = [UIImage imageNamed:[self.dataSource homeDropdown:self selectedIconForRowInMainTable:indexPath.row]];   }   NSArray *subData = [self.dataSource homeDropdown:self subDataForRowInMainTable:indexPath.row];   if (subData.count) {    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;   }   else{    cell.accessoryType = UITableViewCellAccessoryNone;   }   return cell;  }  else{   ZYHomeSubCell *cell = [ZYHomeSubCell subCellWithTableView:tableView];   NSArray *subData = [self.dataSource homeDropdown:self subDataForRowInMainTable:self.selectedMainRow];   cell.textLabel.text = subData[indexPath.row];   return cell;  } } #pragma mark ----UITabelViewDelegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {  if (tableView == self.mainTableView) {   self.selectedMainRow = (int)indexPath.row;   [self.subTableview reloadData];   if ([self.delegate respondsToSelector:@selector(homeDropdown:didSelectedRowInMainTable:)]) {    [self.delegate homeDropdown:self didSelectedRowInMainTable:(int)indexPath.row];   }  }  else{   if ([self.delegate respondsToSelector:@selector(homeDropdown:didSelectedRowInSubTable:mainRow:)]) {    [self.delegate homeDropdown:self didSelectedRowInSubTable:(int)indexPath.row mainRow:self.selectedMainRow];   }  } } @end 

上面代码是大概实现思路,在具体实现里面,还有一个xib文件,以及mainCell和subCell。

下面是搜索的图片,实际上,搜索实现很简单,按照拼音或者关键字来检索下,如果符合条件,就放到数组里面,最后刷新tableView即可:

iOS开发:一个高仿美团的团购ipad客户端的设计和实现(功能:根据拼音进行检索并展示数据,离线缓...

iOS开发:一个高仿美团的团购ipad客户端的设计和实现(功能:根据拼音进行检索并展示数据,离线缓...

另外,跨控制进行数据交换,最好是使用Notification

首页还存在这样一个界面:

iOS开发:一个高仿美团的团购ipad客户端的设计和实现(功能:根据拼音进行检索并展示数据,离线缓...

也就是菜单界面,动态展示了一些模块,五角星代表这需要展示收藏模块,圆代表着展示浏览记录模块,另外两个并未实现具体功能。

实现这样一个动态的菜单,我使用的是AwesomeMenu这个第三方库,具体实现可以见它github上的示例,项目中实现菜单的具体代码:

- (void)setupAwesomeMenu {  //initWithImage放背景图片,normal和highlighted状态下的背景图片  //contentImage放具体要显示的图片  AwesomeMenuItem *midItem = [[AwesomeMenuItem alloc] initWithImage:[UIImage imageNamed:@"icon_pathMenu_background_highlighted"] highlightedImage:nil ContentImage:[UIImage imageNamed:@"icon_pathMenu_mainMine_normal"] highlightedContentImage:nil];  AwesomeMenuItem *firstItem = [[AwesomeMenuItem alloc] initWithImage:[UIImage imageNamed:@"bg_pathMenu_black_normal"] highlightedImage:nil ContentImage:[UIImage imageNamed:@"icon_pathMenu_collect_normal"] highlightedContentImage:[UIImage imageNamed:@"icon_pathMenu_collect_highlighted"]];  AwesomeMenuItem *secoendItem = [[AwesomeMenuItem alloc] initWithImage:[UIImage imageNamed:@"bg_pathMenu_black_normal"] highlightedImage:nil ContentImage:[UIImage imageNamed:@"icon_pathMenu_scan_normal"] highlightedContentImage:[UIImage imageNamed:@"icon_pathMenu_scan_highlighted"]];  AwesomeMenuItem *thirdItem = [[AwesomeMenuItem alloc] initWithImage:[UIImage imageNamed:@"bg_pathMenu_black_normal"] highlightedImage:nil ContentImage:[UIImage imageNamed:@"icon_pathMenu_more_normal"] highlightedContentImage:[UIImage imageNamed:@"icon_pathMenu_more_highlighted"]];  AwesomeMenuItem *fourthItem = [[AwesomeMenuItem alloc] initWithImage:[UIImage imageNamed:@"bg_pathMenu_black_normal"] highlightedImage:nil ContentImage:[UIImage imageNamed:@"icon_pathMenu_collect_normal"] highlightedContentImage:[UIImage imageNamed:@"icon_pathMenu_collect_highlighted"]];  NSArray *items = @[firstItem, secoendItem, thirdItem, fourthItem];  AwesomeMenu *awesome = [[AwesomeMenu alloc] initWithFrame:CGRectZero startItem:midItem optionMenus:items];  //开始点  awesome.startPoint = CGPointMake(50, 150);  //设置显示区域(也就是角度)  awesome.menuWholeAngle = M_PI_2;  awesome.delegate = self;  //让中间按钮不旋转  awesome.rotateAddButton = NO;  awesome.alpha = 0.5;  [self.view addSubview:awesome];  [awesome autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:0];  [awesome autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:0];  [awesome autoSetDimensionsToSize:CGSizeMake(200, 200)]; } 

前面提到,Home模块的vc和Search模块的vc(viewController)是继承自ZYDealViewController,在ZYDealViewController我处理了home模块和search模块的相同功能,它们各自的特殊功能是放到各自的vc里面实现的,ZYDealViewController的代码如下:

#import <UIKit/UIKit.h> @interface ZYDealViewController : UICollectionViewController /**  设置请求参数:交给子类去实现  */ - (void)setParams:(NSMutableDictionary *)params; @end #import "ZYDealViewController.h" #import "ZYConst.h" #import "UIBarButtonItem+ZYExtension.h" #import "UIView+Extension.h" #import "ZYHomeTopItem.h" #import "ZYCategoryViewController.h" #import "ZYDistrictViewController.h" #import "ZYSort.h" #import "ZYCity.h" #import "ZYMetaTool.h" #import "ZYSortViewController.h" #import "ZYRegion.h" #import "ZYCategory.h" #import "DPAPI.h" #import "ZYDeal.h" #import "MJExtension.h" #import "ZYDealCell.h" #import "MJRefresh.h" #import "MBProgressHUD+MJ.h" #import "UIView+AutoLayout.h" #import "ZYDetailViewController.h" @interface ZYDealViewController () <DPRequestDelegate> @property (nonatomic, strong) NSMutableArray *deals; @property (nonatomic, strong) DPRequest *lastRequest; @property (nonatomic, assign) int currentPage; @property (nonatomic, assign) int totalCount; /** 当没有团购数据时,显示一张没有数据的背景 */ @property (nonatomic, strong) UIImageView *backgroundImageView; @end @implementation ZYDealViewController static NSString * const reuseIdentifier = @"ZYDealViewControllerCell"; - (UIImageView *)backgroundImageView {  if (!_backgroundImageView) {   _backgroundImageView = [[UIImageView alloc] init];   _backgroundImageView.image = [UIImage imageNamed:@"icon_deals_empty"];   [self.view addSubview:_backgroundImageView];   [_backgroundImageView autoCenterInSuperview];  }  return _backgroundImageView; } - (NSMutableArray *)deals {  if (!_deals) {   _deals = [NSMutableArray array];  }  return _deals; } - (instancetype)init {  UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];  //设置cell的大小  layout.itemSize = CGSizeMake(305, 305);  return [self initWithCollectionViewLayout:layout]; } /**  当屏幕旋转,控制器view的尺寸发生改变调用  */ - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {  // 根据屏幕宽度决定列数  int cols = (size.width == 1024) ? 3 : 2;  // 根据列数计算内边距  UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout;  CGFloat inset = (size.width - cols * layout.itemSize.width) / (cols + 1);  layout.sectionInset = UIEdgeInsetsMake(inset, inset, inset, inset);  // 设置每一行之间的间距  layout.minimumLineSpacing = inset; } - (void)viewDidLoad {  [super viewDidLoad];  [self setupCollection]; } #pragma mark ----setup系列 - (void)setupCollection {  [self.collectionView registerNib:[UINib nibWithNibName:@"ZYDealCell" bundle:nil] forCellWithReuseIdentifier:reuseIdentifier];  self.collectionView.backgroundColor = ZYGlobalBg;  self.collectionView.alwaysBounceVertical = YES;  self.collectionView.header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(heaerRefresh)];  self.collectionView.footer = [MJRefreshAutoNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(footerRefresh)]; } #pragma mark ----与服务器进行交互 - (void)loadNewDeals {  DPAPI *api = [[DPAPI alloc] init];  NSMutableDictionary *params = [NSMutableDictionary dictionary];  //让子类自行设置响应的参数  [self setParams:params];  params[@"page"] = @(self.currentPage);  // 每页的条数  params[@"limit"] = @10;  self.lastRequest = [api requestWithURL:@"v1/deal/find_deals" params:params delegate:self];  // NSLog(@"请求参数:%@", params); } - (void)loadDeals {  self.currentPage = 1;  [self loadNewDeals]; } - (void)loadMoreDeals {  self.currentPage++;  [self loadNewDeals]; } - (void)request:(DPRequest *)request didFinishLoadingWithResult:(id)result {  if (request != self.lastRequest) {  //如果不是同一个请求,是短时间内发了两次请求,那么只要最近的一次请求   return;  }  self.totalCount = [result[@"total_count"] intValue];  NSArray *newDeals = [ZYDeal objectArrayWithKeyValuesArray:result[@"deals"]];  if (self.currentPage == 1) {   [self.deals removeAllObjects];  }  [self.deals addObjectsFromArray:newDeals];  [self.collectionView reloadData];  [self.collectionView.footer endRefreshing];  [self.collectionView.header endRefreshing]; } - (void)request:(DPRequest *)request didFailWithError:(NSError *)error {  if (self.lastRequest != request) {   return;  }  //在ipad开发中,如果要显示HUD,特别需要注意,显示到self.view上  // [MBProgressHUD showError:@"加载失败,请检查您的网络..."];  [MBProgressHUD showError:@"加载失败,请检查您的网络..." toView:self.view];  [self.collectionView.footer endRefreshing];  //当不是请求第一页的时候,如果请求失败,那么应当减去这次请求的  self.currentPage--;  [self.collectionView.footer endRefreshing];  [self.collectionView.header endRefreshing]; } #pragma mark ----刷新方法 - (void)heaerRefresh {  [self loadDeals]; } - (void)footerRefresh {  [self loadMoreDeals]; } #pragma mark <UICollectionViewDataSource> - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {  return 1; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {  //需要在请求发送就设置以此cell的布局  [self viewWillTransitionToSize:CGSizeMake(self.collectionView.width, self.collectionView.height) withTransitionCoordinator:nil];  self.backgroundImageView.hidden = (self.deals.count != 0);  return self.deals.count; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {  ZYDealCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];  cell.deal = self.deals[indexPath.row];  //第一次进入界面,self.totalCount会被初始化为0  self.collectionView.footer.hidden = (self.totalCount == self.deals.count);  return cell; } #pragma mark <UICollectionViewDelegate> - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {  ZYDetailViewController *detailVc = [[ZYDetailViewController alloc] initWithNibName:@"ZYDetailViewController" bundle:nil];  detailVc.deal = self.deals[indexPath.row];  [self presentViewController:detailVc animated:YES completion:nil]; } @end 

其次就是Collect模块和Browse模块,这两个模块很相似,都是需要实现离线缓存,都是需要支持批量删除的,如此,就完全可以将对象相似的功能提取出来,放到基类里面,而这两个类继承自基类,从而可以大大减少代码量,在代码层次、结构上来看,也会好上很多。

我采用的是Sqlite数据库来进行的离线缓存,是这样设计的,有一个自增长的主键,一个团购(deal)model,团购的id,取出数据时,按照主键的倒序排序。

数据库设计代码如下:

#import <Foundation/Foundation.h> @class ZYDeal; @interface ZYDealTool : NSObject + (void)addCollectionDeal:(ZYDeal *)deal; + (void)removeCollectionDeal:(ZYDeal *)deal; + (NSArray *)collectDeals:(int)page; + (int)collectDealsCount; + (BOOL)isCollected:(ZYDeal *)deal; + (void)addBrowseDeal:(ZYDeal *)deal; + (void)removeBrowseDeal:(ZYDeal *)deal; + (NSArray *)browseDeals:(int)page; + (int)browseDealsCount; + (BOOL)isBrowsed:(ZYDeal *)deal; @end #import "ZYDealTool.h" #import "FMDB.h" #import "ZYDeal.h" @implementation ZYDealTool static FMDatabase *_database; + (void)initialize {  NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];  NSString *path = [doc stringByAppendingPathComponent:@"deal.sqlite"];  _database = [FMDatabase databaseWithPath:path];  if (![_database open]) return;  [_database executeUpdateWithFormat:@"CREATE TABLE IF NOT EXISTS t_collect_deal(id integer PRIMARY KEY, deal blob NOT NULL, deal_id text NOT NULL);"];  [_database executeUpdateWithFormat:@"CREATE TABLE IF NOT EXISTS t_browse_deal(id integer PRIMARY KEY, deal blob NOT NULL, deal_id text NOT NULL);"]; } + (void)addCollectionDeal:(ZYDeal *)deal {  NSData *data = [NSKeyedArchiver archivedDataWithRootObject:deal];  [_database executeUpdateWithFormat:@"INSERT INTO t_collect_deal(deal, deal_id) VALUES (%@, %@);",data, deal.deal_id]; } + (void)removeCollectionDeal:(ZYDeal *)deal {  [_database executeUpdateWithFormat:@"DELETE FROM t_collect_deal WHERE deal_id = %@;", deal.deal_id]; } + (NSArray *)collectDeals:(int)page {  int size = 10;  int pos = (page - 1) * size;  NSMutableArray *deals = [NSMutableArray array];  FMResultSet *resultSet = [_database executeQueryWithFormat:@"SELECT * FROM t_collect_deal ORDER BY id DESC LIMIT %d,%d;",pos,size];  while (resultSet.next) {   ZYDeal *deal = [NSKeyedUnarchiver unarchiveObjectWithData:[resultSet objectForColumnName:@"deal"]];   [deals addObject:deal];  }  return deals; } + (int)collectDealsCount {  FMResultSet *resultSet = [_database executeQueryWithFormat:@"SELECT count(*) AS deal_count FROM t_collect_deal;"];  [resultSet next];  return [resultSet intForColumn:@"deal_count"]; } + (BOOL)isCollected:(ZYDeal *)deal {  FMResultSet *resultSet = [_database executeQueryWithFormat:@"SELECT count(*) AS deal_count FROM t_collect_deal WHERE deal_id = %@;", deal.deal_id];  [resultSet next];  #warning 索引从1开始  return [resultSet intForColumn:@"deal_count"] == 1; } + (void)addBrowseDeal:(ZYDeal *)deal {  NSData *data = [NSKeyedArchiver archivedDataWithRootObject:deal];  [_database executeUpdateWithFormat:@"INSERT INTO t_browse_deal(deal, deal_id) VALUES (%@, %@);",data, deal.deal_id]; } + (void)removeBrowseDeal:(ZYDeal *)deal {  [_database executeUpdateWithFormat:@"DELETE FROM t_browse_deal WHERE deal_id = %@;", deal.deal_id]; } + (NSArray *)browseDeals:(int)page {  int size = 10;  int pos = (page - 1) * size;  NSMutableArray *deals = [NSMutableArray array];  FMResultSet *resultSet = [_database executeQueryWithFormat:@"SELECT * FROM t_browse_deal ORDER BY id DESC LIMIT %d,%d;",pos,size];  while (resultSet.next) {   ZYDeal *deal = [NSKeyedUnarchiver unarchiveObjectWithData:[resultSet objectForColumnName:@"deal"]];   [deals addObject:deal];  }  return deals; } + (int)browseDealsCount {  FMResultSet *resultSet = [_database executeQueryWithFormat:@"SELECT count(*) AS deal_count FROM t_browse_deal;"];  [resultSet next];  return [resultSet intForColumn:@"deal_count"]; } + (BOOL)isBrowsed:(ZYDeal *)deal {  FMResultSet *resultSet = [_database executeQueryWithFormat:@"SELECT count(*) AS deal_count FROM t_browse_deal WHERE deal_id = %@;", deal.deal_id];  [resultSet next]; #warning 索引从1开始  return [resultSet intForColumn:@"deal_count"] == 1; } @end 

删除单个、批量删除功能:

iOS开发:一个高仿美团的团购ipad客户端的设计和实现(功能:根据拼音进行检索并展示数据,离线缓...

iOS开发:一个高仿美团的团购ipad客户端的设计和实现(功能:根据拼音进行检索并展示数据,离线缓...

iOS开发:一个高仿美团的团购ipad客户端的设计和实现(功能:根据拼音进行检索并展示数据,离线缓...

这里需要注意的一点就是cell的循环利用功能,你不能只是修改界面展示,而是需要深入去修改导致界面更改的model,这样才可以做到完美的循环利用,不然会导致bug,请注意,在mvc模式下,界面如何展示都是由model决定的,如果要更改,请更改model。还有就是,当点击进去,会是一条团购详情的界面,在这个界面,当我取消收藏之后,回到收藏界面,也是应当同步删除掉取消了收藏的团购数据的,这里需要注意,用Notification发回来的deal和正在展示的那条deal并不会指向同一个内存地址,所以单纯的判断这两条团购数据相等与否是不行的,应当是重写deal的模型的isEqual方法。

下面是代码:

ZYOldDealViewController

#import <UIKit/UIKit.h> @class ZYDeal; @interface ZYOldDealViewController : UICollectionViewController /**  *  这个方法交给子类去实现,从而得到不同子类的具体背景图片  *  */ - (NSString *)bgImageName; /**  *  这个方法交给子类去实现,从而得到不同子类数据库里的具体数据  *  */ - (NSArray *)arrayWithCurretnPage:(int)currentPage; /**  *  这个方法教给子类去调用,移除deals内所有数据,重新从数据库里加载  */ - (void)removeDealsAllObjects; /**  *  让子类实现这个方法,返回数据库中还有多少条团购数据  *  */ - (int)countForDeals; /**  *  让子类实现这个方法,返回navigationBar的标题  *  */ - (NSString *)titleForNavBar; /**  *  让子类实现这个方法,删除掉自身数据库内拥有的deal  *  */ - (void)deletedSqliteDeal:(ZYDeal *)deal; @end #import "ZYOldDealViewController.h" #import "ZYDeal.h" #import "UIView+AutoLayout.h" #import "ZYConst.h" #import "MJRefresh.h" #import "UIView+Extension.h" #import "ZYDealCell.h" #import "ZYDetailViewController.h" #import "ZYDealTool.h" #import "UIBarButtonItem+ZYExtension.h" @interface ZYOldDealViewController () /** 当没有团购数据时,显示一张没有数据的背景 */ @property (nonatomic, strong) UIImageView *backgroundImageView; @property (nonatomic, strong) NSMutableArray *deals; @property (nonatomic, assign) int currentPage; @property (nonatomic, strong) UIBarButtonItem *backItem; @property (nonatomic, strong) UIBarButtonItem *selectedAllItem; @property (nonatomic, strong) UIBarButtonItem *unselectedAllItem; @property (nonatomic, strong) UIBarButtonItem *delectedItem; @end @implementation ZYOldDealViewController static NSString * const reuseIdentifier = @"ZYDealViewControllerCell"; - (UIImageView *)backgroundImageView {  if (!_backgroundImageView) {   _backgroundImageView = [[UIImageView alloc] init];   NSString *imageName = [self bgImageName];   _backgroundImageView.image = [UIImage imageNamed:imageName];   [self.view addSubview:_backgroundImageView];   [_backgroundImageView autoCenterInSuperview];  }  return _backgroundImageView; } - (NSMutableArray *)deals {  if (!_deals) {   _deals = [NSMutableArray array];  }  return _deals; } #pragma mark ----barButtonItem的懒加载 - (UIBarButtonItem *)backItem {  if (!_backItem) {   _backItem = [UIBarButtonItem barButtonItemWithTarget:self action:@selector(clickbackItem) normalImage:@"icon_back" highImage:@"icon_back_highlighted"];  }  return _backItem; } - (UIBarButtonItem *)selectedAllItem {  if (!_selectedAllItem) {   _selectedAllItem = [[UIBarButtonItem alloc] initWithTitle:@"  全选  " style:UIBarButtonItemStyleDone target:self action:@selector(clickSelectedAllItem)];  }  return _selectedAllItem; } - (UIBarButtonItem *)unselectedAllItem {  if (!_unselectedAllItem) {   _unselectedAllItem = [[UIBarButtonItem alloc] initWithTitle:@"  全不选  " style:UIBarButtonItemStyleDone target:self action:@selector(clickUnselectedAllItem)];  }  return _unselectedAllItem; } - (UIBarButtonItem *)delectedItem {  if (!_delectedItem) {   _delectedItem = [[UIBarButtonItem alloc] initWithTitle:@"  删除  " style:UIBarButtonItemStyleDone target:self action:@selector(clickDelectedItem)];  }  return _delectedItem; } - (instancetype)init {  UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];  //设置cell的大小  layout.itemSize = CGSizeMake(305, 305);  return [self initWithCollectionViewLayout:layout]; } /**  当屏幕旋转,控制器view的尺寸发生改变调用  */ - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {  // 根据屏幕宽度决定列数  int cols = (size.width == 1024) ? 3 : 2;  // 根据列数计算内边距  UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionViewLayout;  CGFloat inset = (size.width - cols * layout.itemSize.width) / (cols + 1);  layout.sectionInset = UIEdgeInsetsMake(inset, inset, inset, inset);  // 设置每一行之间的间距  layout.minimumLineSpacing = inset; } - (void)viewDidLoad {  [super viewDidLoad];  [self setupNav];  [self setupNavItem];  [self setupCollection];  [self loadMoreDeals]; } #pragma mark ----setup系列 - (void)setupNav {  UINavigationBar *appearance = [UINavigationBar appearance];  // 设置文字属性  NSMutableDictionary *textAttrs = [NSMutableDictionary dictionary];  textAttrs[UITextAttributeTextColor] = [UIColor blackColor];  textAttrs[UITextAttributeFont] = [UIFont boldSystemFontOfSize:20];//粗体,  // UIOffsetZero是结构体, 只要包装成NSValue对象, 才能放进字典/数组中  textAttrs[UITextAttributeTextShadowOffset] = [NSValue valueWithUIOffset:UIOffsetZero];  [appearance setTitleTextAttributes:textAttrs];  self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"编辑" style:UIBarButtonItemStyleDone target:self action:@selector(clickEditBarButton:)];  self.navigationItem.leftBarButtonItems = @[self.backItem];  self.navigationItem.title = [self titleForNavBar]; } - (void)setupNavItem {  //通过设置这个属性,可是设置整个导航栏的UIBarButtonItem的属性  UIBarButtonItem *appearance = [UIBarButtonItem appearance];  //设置普通状态下得文字属性  NSMutableDictionary *textAttrs = [NSMutableDictionary dictionary];  //文字颜色  textAttrs[NSForegroundColorAttributeName] = ZYColor(47,188,173);  //文字字体  textAttrs[NSFontAttributeName] = [UIFont systemFontOfSize:18.0];  [appearance setTitleTextAttributes:textAttrs forState:UIControlStateNormal];  // 设置不可用状态(disable)的文字属性  NSMutableDictionary *disableTextAttrs = [NSMutableDictionary dictionary];  disableTextAttrs[NSForegroundColorAttributeName] = [UIColor lightGrayColor];  disableTextAttrs[NSFontAttributeName] = [UIFont systemFontOfSize:15];  [appearance setTitleTextAttributes:disableTextAttrs forState:UIControlStateDisabled]; } - (void)setupCollection {  [self.collectionView registerNib:[UINib nibWithNibName:@"ZYDealCell" bundle:nil] forCellWithReuseIdentifier:reuseIdentifier];  self.collectionView.backgroundColor = ZYGlobalBg;  self.collectionView.alwaysBounceVertical = YES;  self.collectionView.footer = [MJRefreshAutoNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(footerRefresh)]; } #pragma mark ----与数据库进行交互 - (void)loadMoreDeals {  self.currentPage++;  NSArray *tempArray = [self arrayWithCurretnPage:self.currentPage];  [self.deals addObjectsFromArray:tempArray];  [self.collectionView reloadData];  [self.collectionView.footer endRefreshing]; } #pragma mark ----刷新方法 - (void)footerRefresh {  [self loadMoreDeals]; } #pragma mark ----click事件 - (void)clickbackItem {  [self.navigationController popViewControllerAnimated:YES]; } - (void)clickEditBarButton:(UIBarButtonItem *)item {  if ([item.title isEqualToString:@"完成"]) {   self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"编辑" style:UIBarButtonItemStyleDone target:self action:@selector(clickEditBarButton:)];   self.navigationItem.leftBarButtonItems = @[self.backItem];   [self.deals enumerateObjectsUsingBlock:^(ZYDeal *obj, NSUInteger idx, BOOL *stop) {    obj.editing = NO;    obj.checking = NO;   }];  }  else{   self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"完成" style:UIBarButtonItemStyleDone target:self action:@selector(clickEditBarButton:)];   self.navigationItem.leftBarButtonItems = self.navigationItem.leftBarButtonItems = @[self.backItem, self.selectedAllItem, self.unselectedAllItem, self.delectedItem];   [self.deals enumerateObjectsUsingBlock:^(ZYDeal *obj, NSUInteger idx, BOOL *stop) {    obj.editing = YES;   }];  }  [self.collectionView reloadData]; } - (void)clickSelectedAllItem {  [self.deals enumerateObjectsUsingBlock:^(ZYDeal *obj, NSUInteger idx, BOOL *stop) {   obj.checking = YES;  }];  [self.collectionView reloadData]; } - (void)clickUnselectedAllItem {  [self.deals enumerateObjectsUsingBlock:^(ZYDeal *obj, NSUInteger idx, BOOL *stop) {   obj.checking = NO;  }];  [self.collectionView reloadData]; } - (void)clickDelectedItem {  NSMutableArray *deletedArray = [NSMutableArray array];  //需要注意的是,在遍历数组的时候,是不可以删除数组内元素的  [self.deals enumerateObjectsUsingBlock:^(ZYDeal *obj, NSUInteger idx, BOOL *stop) {   if (obj.isChecking) {    [deletedArray addObject:obj];   }  }];  [deletedArray enumerateObjectsUsingBlock:^(ZYDeal *obj, NSUInteger idx, BOOL *stop) {   [self.deals removeObject:obj];   [self deletedSqliteDeal:obj];  }];  [self.collectionView reloadData]; } #pragma mark ----其他 - (void)removeDealsAllObjects {  [self.deals removeAllObjects];  self.currentPage = 0; } #pragma mark <UICollectionViewDataSource> - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {  return 1; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {  //需要在请求发送就设置以此cell的布局  [self viewWillTransitionToSize:CGSizeMake(self.collectionView.width, self.collectionView.height) withTransitionCoordinator:nil];  if (self.deals.count == 0 && [self.navigationItem.rightBarButtonItem.title isEqualToString:@"完成"]) {   [self clickEditBarButton:self.navigationItem.rightBarButtonItem];  }  self.backgroundImageView.hidden = (self.deals.count != 0);  self.navigationItem.rightBarButtonItem.enabled = (self.deals.count != 0);  return self.deals.count; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {  ZYDealCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];  cell.deal = self.deals[indexPath.row];  self.collectionView.footer.hidden = (self.deals.count == [self countForDeals]);  return cell; } #pragma mark <UICollectionViewDelegate> - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {  ZYDetailViewController *detailVc = [[ZYDetailViewController alloc] initWithNibName:@"ZYDetailViewController" bundle:nil];  detailVc.deal = self.deals[indexPath.row];  [self presentViewController:detailVc animated:YES completion:nil]; } @end 

ZYCollectViewController是继承自ZYOldDealViewController的,所以相应代码比较少:

#import <UIKit/UIKit.h> #import "ZYOldDealViewController.h" @interface ZYCollectViewController : ZYOldDealViewController @end #import "ZYCollectViewController.h" #import "ZYDeal.h" #import "ZYConst.h" #import "MJRefresh.h" #import "UIView+Extension.h" #import "ZYDealCell.h" #import "ZYDetailViewController.h" #import "ZYDealTool.h" #import "UIBarButtonItem+ZYExtension.h" @interface ZYCollectViewController () @end @implementation ZYCollectViewController - (NSString *)bgImageName {  return @"icon_collects_empty"; } - (void)viewDidLoad {  [super viewDidLoad];  [self setupNotification];  [self loadDeals]; } #pragma mark ----setup系列 - (void)setupNotification {  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(collectStateDidChange:) name:ZYCollectStateDidChangeNotification object:nil]; } - (void)dealloc {  [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma mark ----与数据库进行交互 - (void)loadDeals {  [self.collectionView.footer beginRefreshing]; } - (void)deletedSqliteDeal:(ZYDeal *)deal {  [ZYDealTool removeCollectionDeal:deal]; } #pragma mark ----notification系列 - (void)collectStateDidChange:(NSNotification *)note {  [self removeDealsAllObjects];  [self loadDeals]; } #pragma mark ----其他 - (NSArray *)arrayWithCurretnPage:(int)currentPage {  return [ZYDealTool collectDeals:currentPage]; } - (int)countForDeals {  return [ZYDealTool collectDealsCount]; } - (NSString *)titleForNavBar {  return @"收藏"; } @end 

同理,ZYBrowseViewController也是继承自ZYOldDealViewController的

#import "ZYBrowseViewController.h" #import "MJRefresh.h" #import "ZYConst.h" #import "ZYDealTool.h" @interface ZYBrowseViewController () @end @implementation ZYBrowseViewController - (void)viewDidLoad {  [super viewDidLoad];  // Do any additional setup after loading the view.  [self loadDeals]; } - (NSString *)bgImageName {  return @"icon_latestBrowse_empty"; } - (NSArray *)arrayWithCurretnPage:(int)currentPage {  return [ZYDealTool browseDeals:currentPage]; } - (int)countForDeals {  return [ZYDealTool browseDealsCount]; } - (NSString *)titleForNavBar {  return @"浏览记录"; } #pragma mark ----与数据库进行交互 - (void)loadDeals {  [self.collectionView.footer beginRefreshing]; } - (void)deletedSqliteDeal:(ZYDeal *)deal {  [ZYDealTool removeBrowseDeal:deal]; } @end 

团购详情界面,如图:

这是未被收藏的团购:

iOS开发:一个高仿美团的团购ipad客户端的设计和实现(功能:根据拼音进行检索并展示数据,离线缓...

这是被收藏之后的图片:

iOS开发:一个高仿美团的团购ipad客户端的设计和实现(功能:根据拼音进行检索并展示数据,离线缓...

当点击分享,会跳出关于分享的界面,只是最简单的分享......

下面,这是地图界面的相关图片:

iOS开发:一个高仿美团的团购ipad客户端的设计和实现(功能:根据拼音进行检索并展示数据,离线缓...

iOS开发:一个高仿美团的团购ipad客户端的设计和实现(功能:根据拼音进行检索并展示数据,离线缓...

MapKit框架中,反地理编码、自定义大头针等技术。

以上,就是这个项目的所有实现内容了,没有做缓存限制,这一点也很简单,直接使用SDWebImage框架提供的,在接受到内存警告时,清理缓存即可。

这个项目的github地址:https://github.com/wzpziyi1/GroupPurchase

正文到此结束
Loading...