iOS開發:一個高仿美團的團購ipad客戶端的設計和實現(功能:根據拼音進行檢索並展現數據,離線緩存團購數據,瀏覽記錄與收藏記錄的批量刪除等)

大體花了一個月時間,利用各類空閒時間,將這個客戶端實現了,在這裏主要是想記錄下,設計的大致思路以及實現過程當中遇到的坑......git

這個項目的github地址:https://github.com/wzpziyi1/GroupPurchasegithub

主要實現的功能,用UICollectionViewController展現團購數據,根據拼音進行檢索並展現數據,離線緩存團購數據,瀏覽記錄與收藏記錄的批量刪除,友盟分享的集成,利用UIView+AutoLayout寫佈局,實現地圖定位、自定義大頭針等sql

整個項目,按功能分,有六個大的模塊,每一個模塊裏面都有各自的MVC,在各個MVC裏面,再按照功能的不一樣,新建一下文件夾,存放實現統一功能的文件。數據庫

除此以外,還有一個Main模塊,裏面的Lib意爲Library,放一些實現此客戶端所須要用到的第三方庫。Category文件夾裏面,放整個項目各個地方都有可能須要用到的分類;Controller裏面,放上一些最主要功能的ViewController,好比,自定義的NavigationController和TabBarController,那麼整個項目的Navigation或者TabBarController都是能夠根據須要繼承自自定義的NavigationController,這裏,我放的是:api

後續,Home(首頁)界面的viewController和Search(搜索)界面的viewController都是繼承自ZYDealViewController的,Collect(收藏的團購記錄)和Browse(瀏覽的團購記錄)界面的viewController都是繼承自ZYOldDealViewController,整個項目多個地方用到,因此我放在了這裏。數組

Other文件夾裏面,我放的是一些雜七雜八的文件,好比說appDelegate等。Tool文件夾裏面,我放的是一些工具類文件.緩存

其中,Tool和Category裏面的文件不要和他類耦合,由於這些東西,咱們寫好一次以後,基本上是能夠拖到下個項目直接使用的。服務器

Home界面網絡

如圖:mvc

在聯網狀況下,默認進來就是廣州的全部團購信息,支持上拉、下拉刷新,已經適配好橫豎屏。除此以外,還設置了當前地區、分類條件下,全部的團購信息都已經加載完畢了,那麼將不能夠進行上拉刷新操做(由於全部團購信息加載完畢,已經沒有數據能夠加載,那麼應該要隱藏footer),默認是一次刷新10條團購信息。

除此以外,若是在當前條件下,若是沒有團購信息,那麼我顯示的是一張提示圖片:

 

下面這兩個彈框是同樣的,當初第一次寫的時候,是分別寫了兩個popViewController,後面在進行優化的時候,發現是能夠寫成一個通用的雙表,這樣就進行了一系列的優化,使得之後遇到相似的雙表界面,直接將我寫好的類拖過去,就能夠繼續使用了,圖片以下:

這是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便可:

 

 

另外,跨控制進行數據交換,最好是使用Notification

首頁還存在這樣一個界面:

 

也就是菜單界面,動態展現了一些模塊,五角星表明這須要展現收藏模塊,圓表明着展現瀏覽記錄模塊,另外兩個並未實現具體功能。

實現這樣一個動態的菜單,我使用的是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

 刪除單個、批量刪除功能:

 

 

這裏須要注意的一點就是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

 團購詳情界面,如圖:

這是未被收藏的團購:

這是被收藏以後的圖片:

當點擊分享,會跳出關於分享的界面,只是最簡單的分享......

下面,這是地圖界面的相關圖片:

 

 

MapKit框架中,反地理編碼、自定義大頭針等技術。

以上,就是這個項目的全部實現內容了,沒有作緩存限制,這一點也很簡單,直接使用SDWebImage框架提供的,在接受到內存警告時,清理緩存便可。

這個項目的github地址:https://github.com/wzpziyi1/GroupPurchase

相關文章
相關標籤/搜索