UI:用UITableView制作通讯录的关键代码
阅读原文时间:2021年08月12日阅读:2

分析{功能分析(打电话、添加联系人、修改联系人),模块分析(联系人展示、详情模块、添加模块)}

拿到一个项目,首先分析项目框架(工程框架)

首先:判断是否是用户第一次安装:(如果是的,那就加载用户引导页面)(如果不是,那就显示用户联系人的主页面)

其次:用户联系人的主页面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;
    }

Appdelegate.m文件

#import
#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

/*
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 == ) {
    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]];//这里要导入 框架
    //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

@interface ContactListViewController : UITableViewController

@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 ()

@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 ;
}

#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

ContactListViewController.m & .h文件

其中要用到的数据层类 M

Contact.h文件

#import

@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

contact.h & .m 文件

视图类 V

#import
@class Contact;

//为 callBtn 制订协议,实现拨打电话
@protocol MakeACallDelegate

-(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)iddelegate;
@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(, , , )];
    _photoView.layer.cornerRadius = 5.0;
    _photoView.layer.masksToBounds = YES;
    }

    return [[_photoView retain]autorelease];

}

  • (UILabel *)nameLabel {
    if (!_nameLabel) {
    self.nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(, , self.frame.size.width / , )];
    }
    return [[_nameLabel retain]autorelease];

}

  • (UILabel *)phoneLabel {
    if (!_phoneLabel) {
    self.phoneLabel = [[UILabel alloc] initWithFrame:CGRectMake( + self.frame.size.width / , , self.frame.size.width / , )];
    }
    return [[_phoneLabel retain]autorelease];

}

  • (UIButton *)callBtn {
    if (!_callBtn) {
    self.callBtn = [UIButton buttonWithType:UIButtonTypeSystem];
    _callBtn.frame = CGRectMake(self.frame.size.width - , , , );
    //添加触发
    [_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

contactContactCelltableCell.h & .m 文件

其他参考:参考