分析{功能分析(打电话、添加联系人、修改联系人),模块分析(联系人展示、详情模块、添加模块)}
拿到一个项目,首先分析项目框架(工程框架)
首先:判断是否是用户第一次安装:(如果是的,那就加载用户引导页面)(如果不是,那就显示用户联系人的主页面)
其次:用户联系人的主页面VC(用一个协助类 addressbookheleper类(做数据处理相关的内容,属于MVC的M层。只处理相关的数据,这里是为VC瘦身)去提供相应的方法 给 controller (ContactListViewController) )这里面有这几个方法:为VC提供tableViewCell某一分区得到行数、分区个数、对应的分区的标题、右侧的索引、是否要删除某分区、是否要删除某一行、移动某分区的某一行、返回对应行的联系人、添加联系人。
#import "AppDelegate.h" #import "ContactListViewController.h" #import "MainNavigationController.h"//程序导航栏是同样的风格,所以这里用公用的类 @interface AppDelegate () @end @implementation AppDelegate -(void)dealloc{ [_window release]; [super dealloc]; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Override point for customization after application launch. self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; //判断是否是第一次安装 //如果是,则指定为引导用户页为第一启动(为 window 的跟视图控制器)否则进入程序的主页面 //设置 根视图控制器 ContactListViewController * mainVC = [[ContactListViewController alloc]initWithStyle:UITableViewStylePlain]; //创建导航控制器 MainNavigationController * navl = [[MainNavigationController alloc]initWithRootViewController:mainVC]; //指定为 window 根视图控制器 self.window.rootViewController = navl; //释放所有权 [mainVC release]; [navl release]; return YES; }
#import <Foundation/Foundation.h> #import "Contact.h" @interface AddressBookHelper : NSObject //提供相应的方法,给对应的 controller (ContactListViewController) + (NSInteger)numberOfSection;//返回分区个数 + (NSInteger)numberOfRowsInSection:(NSInteger)section;//返回对应分区的行数 + (NSString *)titleForHeaderInSection:(NSInteger)section;//返回对应分区的标题 + (NSArray *)sectionIndexTitles;//返回右侧索引的标题 #pragma mark---delete + (BOOL)isNeedToDeleteWholeSection:(NSInteger)section;//是否要删除整个分区 + (void)deleteWholeSection:(NSInteger)section;//删除整个分区 + (void)deleteRowAtIndexPath:(NSIndexPath *)indexPath;//删除一行 #pragma mark---move + (void)moveFromIndexPath:(NSIndexPath *)sourceIndexPath ToIndexPath:(NSIndexPath *)destinationindexPath;//移动某分区某行 #pragma mark---返回对应行所需的联系人 + (Contact *)contactAtIndexPath:(NSIndexPath *)indexPath; #pragma mark---添加联系人 +(void)addContact:(Contact *)newContact; @end
// // AddressBookHelper.m #import "AddressBookHelper.h" #import <UIKit/UIKit.h> /* AddressHelper 类 是通讯录列表的界面的辅助类,帮助(ContactListViewController )做数据处理相关的内容,为 ContactListViewController 瘦身,该类可有可无 属于 MVC 的 M 层的类 M层的类 只处理相关的数据(和数据有关的类都是M层)如:数据解析类 数据请求类 数据本地化 */ @interface AddressBookHelper () //存储外层的大字典 @property(nonatomic,retain)NSMutableDictionary * dataSource; //存储拍好序的 key @property(nonatomic,retain)NSMutableArray * sortedKeys; @end @implementation AddressBookHelper static AddressBookHelper * helper ;//让生命周期与程序的生命周期一致 //单例方法 + (AddressBookHelper *)defaultHelper{ //安全处理保证多线程下是安全的 @synchronized(self){ if (!helper) { helper = [[AddressBookHelper alloc]init]; [helper readDataFromPlist];//获取本地 plist 文件 } } return helper; } //获取本地文件 -(void)readDataFromPlist{ //获取文件。获取外层字典的信息 NSString * filePath = [[NSBundle mainBundle] pathForResource:kPlistName ofType:@"plist"]; NSDictionary * dict = [NSDictionary dictionaryWithContentsOfFile:filePath]; self.dataSource = [NSMutableDictionary dictionaryWithCapacity:dict.count]; //2.1遍历外层字典的key for (NSString * key in dict) { // NSArray * group = [dict objectForKey:key]; //2.2通过key 获取联系人分组数据 NSArray * group = dict[key]; //2.3创建一个可变数组 来存放联系人对象 NSMutableArray * perArr = [NSMutableArray arrayWithCapacity:group.count]; //2.4遍历分组数组 获得联系人小字典 for (NSDictionary * dic in group) { //2.5将对象封装到字典 Contact * contact = [[Contact alloc]initWithDic:dic]; //2.6将联系人字典添加到存放对象的数组 [perArr addObject:contact]; [contact release]; } //2.7将存放对象的数组以及key 存放到 大字典里 [self.dataSource setObject:perArr forKey:key];//(可变字典可以这样格式写,不可变的不能这样写) //获得排好序的 key NSArray * sorted = [[_dataSource allKeys]sortedArrayUsingSelector:@selector(compare:)]; self.sortedKeys = [NSMutableArray arrayWithArray:sorted]; } } //提供相应的方法,给对应的 controller (ContactListViewController) //返回分区个数 + (NSInteger)numberOfSection{ return [[self defaultHelper].dataSource count]; // [self defaultHelper].sortedKeys.count; } //返回对应分区的行数 + (NSInteger)numberOfRowsInSection:(NSInteger)section{ return [helper.dataSource [helper.sortedKeys[section]] count]; } //返回对应分区的标题 + (NSString *)titleForHeaderInSection:(NSInteger)section{ return helper.sortedKeys[section]; } //返回右侧索引的标题 + (NSArray *)sectionIndexTitles{ return helper.sortedKeys; } #pragma mark---delete //是否要删除整个分区 + (BOOL)isNeedToDeleteWholeSection:(NSInteger)section{ //获取对应分组的联系人数组 NSArray * group = helper.dataSource[helper.sortedKeys[section]]; //如果该分组只有一元素,就代表只有一个人 if (group.count == 1) { return YES;//则需要删除整个分区 } return NO;//否则不需要删除 } //删除整个分区 + (void)deleteWholeSection:(NSInteger)section{ //1.获取对应的 key NSString * key = helper.sortedKeys[section]; //2.从字典中删除 key 的分组数组 [helper.dataSource removeObjectForKey:key]; //3.从key 数组中删除 key [helper.sortedKeys removeObject:key]; /* //从字典删除对应的分组的数组 [helper.dataSource removeObjectForKey:helper.sortedKeys[section]]; //从拍好序的 key 数组中移除 key 值 [helper.sortedKeys removeObjectAtIndex:section]; */ } //删除一行 + (void)deleteRowAtIndexPath:(NSIndexPath *)indexPath{ //1.获取对应的分组 NSMutableArray * group = helper.dataSource[helper.sortedKeys[indexPath.section]];//这里要导入 <UIKit/UIKit.h>框架 //2.从对应的分组中移除 [group removeObjectAtIndex:indexPath.row];//根据下标索引去删除 } #pragma mark---move //移动某分区某行 + (void)moveFromIndexPath:(NSIndexPath *)sourceIndexPath ToIndexPath:(NSIndexPath *)destinationindexPath{ /* //获取对应分组的数组 NSMutableArray * group = helper.dataSource[helper.sortedKeys[sourceIndexPath.section]]; //获取对应的联系人 Contact * per =[[group objectAtIndex:sourceIndexPath.row]retain]; //从对应位置移除 [group removeObjectAtIndex:sourceIndexPath.row]; //插入联系人 [group insertObject:per atIndex:destinationindexPath.row]; */ NSString * key = [helper.sortedKeys objectAtIndex:sourceIndexPath.section]; NSMutableArray * group = [helper.dataSource objectForKey:key]; Contact * per = [[group objectAtIndex:sourceIndexPath.row]retain]; [group removeObjectAtIndex:sourceIndexPath.row]; [group insertObject:per atIndex:destinationindexPath.row]; [per release]; } #pragma mark---返回对应行所需的联系人 + (Contact *)contactAtIndexPath:(NSIndexPath *)indexPath{ //测试的假数据 // Contact * per = [[Contact alloc]initWithDic:@{@"name":@"张三",@"gender":@"男",@"phoneNum":@"13523526303",@"photo":@"jia"}]; // return per; Contact * per = helper.dataSource[helper.sortedKeys[indexPath.section]][indexPath.row]; return per; //对的写法 // NSArray * group = helper.dataSource[helper.sortedKeys[indexPath.section]]; // Contact * per = group[indexPath.row]; // return per; //对 // return helper.dataSource[helper.sortedKeys[indexPath.section]][indexPath.row]; } #pragma mark---添加联系人 +(void)addContact:(Contact *)newContact{ } -(void)dealloc{ self.dataSource = nil; self.sortedKeys = nil; [super dealloc]; } @end
并提供相对应的借口,供VC里的数据处理使用
然后:在联系人的主页面要实现对数据与用户页面的交互功能
#import <UIKit/UIKit.h> @interface ContactListViewController : UITableViewController<UITableViewDelegate> @end 、、、、、、、ContactListViewController.m文件 // // ContactListViewController.m #import "ContactListViewController.h" #import "AddressBookHelper.h" #import "Contact.h" #import "AddContactViewController.h" #import "MainNavigationController.h" #import "DetailViewController.h" #import "CustomContactCellTabviewCell.h" @interface ContactListViewController ()<MakeACallDelegate> @end @implementation ContactListViewController - (void)viewDidLoad { [super viewDidLoad]; [self customizedNavBar];//配置该页面的导航条 //注册 cell [self.tableView registerClass:[CustomContactCellTabviewCell class] forCellReuseIdentifier:@"reuse"]; } #pragma mark---配置该页面的导航条 -(void)customizedNavBar{ self.navigationItem.title = @"XXXX通讯录"; self.navigationItem.rightBarButtonItem = self.editButtonItem; UIBarButtonItem * left = [[UIBarButtonItem alloc]initWithImage:[[UIImage imageNamed:@"add_contact"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] style:UIBarButtonItemStylePlain target:self action:@selector(handleAddAction:)]; self.navigationItem.leftBarButtonItem = left; [left release]; } #pragma mark---模态进入添加联系人页面 -(void)handleAddAction:(UIBarButtonItem *)sender{ AddContactViewController * addContactVC = [[AddContactViewController alloc]init]; MainNavigationController * mainVC = [[MainNavigationController alloc]initWithRootViewController:addContactVC]; mainVC.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal; [self presentViewController:mainVC animated:YES completion:^{ }]; [addContactVC release]; [mainVC release]; } #pragma mark--内存警告处理 - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; if ([self isViewLoaded] && !self.view.window) { self.view = nil; } } #pragma mark - UITableViewDataSource 两个必须实现方法 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { // 通过助手类 返回分区的个数 return [AddressBookHelper numberOfSection]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // 通过助手类 返回分区的行数 return [AddressBookHelper numberOfRowsInSection:section]; } #pragma mark---信息展示 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { CustomContactCellTabviewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"reuse" forIndexPath:indexPath]; //cell 控件的赋值操作 //1. Contact * contact = [AddressBookHelper contactAtIndexPath:indexPath]; //2. // cell.contact = contact; //为系统默认的控件赋值 [cell configureCell:contact]; //视图控制器完成拨打电话 cell.delegate = self; cell.indexPath = indexPath; return cell; } -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{ return [AddressBookHelper titleForHeaderInSection:section]; } #pragma mark---右侧的索引的标题 -(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView{ return [AddressBookHelper sectionIndexTitles]; } #pragma mark--- cell 行高设置 -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ return 90; } #pragma mark---tableViewcell 是否可以编辑 - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { return YES; } /* */ #pragma mark---重写方法去编辑 tableviewcell - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { if ([AddressBookHelper isNeedToDeleteWholeSection:indexPath.section]) { //数据源删除 [AddressBookHelper deleteWholeSection:indexPath.section]; //UI 界面 [tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationLeft]; }else{ //数据源删除 [AddressBookHelper deleteRowAtIndexPath:indexPath]; //UI 界面 [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; } } else if (editingStyle == UITableViewCellEditingStyleInsert) { // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view } } #pragma mark---移动操作 - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { //调用助手类 完成对应的移动操作 [AddressBookHelper moveFromIndexPath:fromIndexPath ToIndexPath:toIndexPath]; } #pragma mark---移动设置(cell是否可以移动) - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { return YES; } #pragma makr---限制移动的范围,禁止跨区移动 -(NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath{ if (sourceIndexPath.section == proposedDestinationIndexPath.section) { return proposedDestinationIndexPath; } return sourceIndexPath; } #pragma mark---点击进入详情页面 (UITableViewDelegate) -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ //push 进入联系人详情页面 DetailViewController * detailVC = [[DetailViewController alloc]init]; //属性传值 [self.navigationController pushViewController:detailVC animated:YES]; [detailVC release]; } #pragma mark---MakeACallDelegate 协议方法实现 //拨打电话 -(void)dial:(NSIndexPath *)indexPath{ Contact * contact = [AddressBookHelper contactAtIndexPath:indexPath]; UIWebView * webview = [[UIWebView alloc]init]; [webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"tel:%@",contact.phoneNum]]]]; [webview release]; } @end
其中要用到的数据层类 M
Contact.h文件 #import <Foundation/Foundation.h> @interface Contact : NSObject @property(nonatomic,copy)NSString * name; @property(nonatomic,copy)NSString * gender; @property(nonatomic,copy)NSString * phoneNum; @property(nonatomic,copy)NSString * photo; -(instancetype)initWithDic:(NSDictionary *)dic; @end Contact.m 文件 #import "Contact.h" @implementation Contact -(instancetype)initWithDic:(NSDictionary *)dic{ self = [super init]; if (self) { //KVC 赋值 [self setValuesForKeysWithDictionary:dic]; } return self; } //对应的KVC 赋值的错误安全处理 -(void)setValue:(id)value forUndefinedKey:(NSString *)key{ } -(void)dealloc{ self.name = nil; self.gender = nil; self.phoneNum = nil; self.photo = nil; [super dealloc]; } @end
视图类 V
#import <UIKit/UIKit.h> @class Contact; //为 callBtn 制订协议,实现拨打电话 @protocol MakeACallDelegate <NSObject> -(void)dial:(NSIndexPath *)indexPath; @end @interface CustomContactCellTabviewCell : UITableViewCell @property (nonatomic ,retain) UIImageView *photoView; @property (nonatomic ,retain) UILabel *nameLabel; @property (nonatomic ,retain) UILabel *phoneLabel; @property (nonatomic ,retain) UIButton *callBtn; @property(nonatomic,assign)id<MakeACallDelegate>delegate; @property(nonatomic,retain)NSIndexPath * indexPath;//存储当前点击 call 的索引 @property(nonatomic,retain)Contact * contact;//声明一个联系人对象的属性 //为cell 上的控件赋值 -(void)configureCell:(Contact *)contact; @end // // CustomContactCellTabviewCell.m #import "CustomContactCellTabviewCell.h" #import "Contact.h" #import "AddressBookHelper.h" @implementation CustomContactCellTabviewCell - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; if (self) { [self.contentView addSubview:self.photoView]; [self.contentView addSubview:self.nameLabel]; [self.contentView addSubview:self.phoneLabel]; [self.contentView addSubview:self.callBtn]; } return self; } #pragma lazy loading 实现控件的创建 - (UIImageView *)photoView { if (!_photoView) { self.photoView = [[UIImageView alloc] initWithFrame:CGRectMake(20, 20, 50, 50)]; _photoView.layer.cornerRadius = 5.0; _photoView.layer.masksToBounds = YES; } return [[_photoView retain]autorelease]; } - (UILabel *)nameLabel { if (!_nameLabel) { self.nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 30, self.frame.size.width / 3, 40)]; } return [[_nameLabel retain]autorelease]; } - (UILabel *)phoneLabel { if (!_phoneLabel) { self.phoneLabel = [[UILabel alloc] initWithFrame:CGRectMake(70 + self.frame.size.width / 3, 30, self.frame.size.width / 2, 40)]; } return [[_phoneLabel retain]autorelease]; } - (UIButton *)callBtn { if (!_callBtn) { self.callBtn = [UIButton buttonWithType:UIButtonTypeSystem]; _callBtn.frame = CGRectMake(self.frame.size.width - 20, 30, 40, 40); //添加触发 [_callBtn addTarget:self action:@selector(handleCall:) forControlEvents:UIControlEventTouchUpInside]; [_callBtn setImage:[UIImage imageNamed:@"action_call"] forState:UIControlStateNormal]; } return _callBtn; } #pragma mark---打电话按钮 - (void)handleCall:(UIButton *)sender { if (!_delegate && [_delegate respondsToSelector:@selector(dial:)]) { [_delegate dial:self.indexPath]; } } - (void)dealloc { self.nameLabel = nil; self.photoView = nil; self.phoneLabel = nil; self.callBtn = nil; [super dealloc]; } - (void)awakeFromNib { // Initialization code } - (void)setSelected:(BOOL)selected animated:(BOOL)animated { [super setSelected:selected animated:animated]; // Configure the view for the selected state } //第一种 : 自定义方法实现对 cell 的控件的赋值 -(void)configureCell:(Contact *)contact{ self.nameLabel.text = contact.name; self.phoneLabel.text = contact.phoneNum; self.photoView.image = [UIImage imageNamed:contact.photo]; } //第二种 :重写 setter 方法 为 cell 上的方法赋值 /* -(void)setContact:(Contact *)contact{ if (_contact != contact) { [_contact release]; _contact = [contact retain]; } self.nameLabel.text = contact.name; self.phoneLabel.text = contact.phoneNum; self.photoView.image = [UIImage imageNamed:contact.photo]; } */ @end
其他参考:参考