iOS之短信,邮件,通信录技术的完美实现

前言
今天给大家详细介绍一下iOS之短信,邮件,通信录技术的具体实现

  1. 系统应用之短信,邮件
    在开发某些应用时可能希望能够调用iOS系统内置的电话、短信、邮件、浏览器应用,此时你可以直接使用UIApplication的OpenURL:方法指定特定的协议来打开不同的系统应用。常用的协议如下:
    打电话:tel:或者tel:、telprompt:或telprompt:(拨打电话前有提示)
    发短信:sms:或者sms:
    发送邮件:mailto:或者mailto:
    启动浏览器:http:或者http:
    下面以一个简单的demo演示如何调用上面几种系统应用:
    #import "ViewController.h"
    @interface ViewController ()
    @end
    @implementation ViewController
    - (void)viewDidLoad {
    [super viewDidLoad];
    }
    #pragma mark - UI事件//打电话
    - (IBAction)callClicK:(UIButton *)sender {
    NSString *phoneNumber=@"18500138888";
    NSString *url=[NSString stringWithFormat:@"tel:%@",phoneNumber];

      //这种方式会直接拨打电话 
      NSString *url=[NSString stringWithFormat:@"telprompt:%@",phoneNumber];
      //这种方式会提示用户确认是否拨打电话
     [self openUrl:url];}
    
     //发送短信
     - (IBAction)sendMessageClick:(UIButton *)sender {
         NSString *phoneNumber=@"18500138888";
         NSString *url=[NSString stringWithFormat:@"sms://%@",phoneNumber]; 
         [self openUrl:url];
     }
    
     //发送邮件
     - (IBAction)sendEmailClick:(UIButton *)sender {
         NSString *mailAddress=@"kenshin@hotmail.com";
         NSString *url=[NSString stringWithFormat:@"mailto:%@",mailAddress]; [self openUrl:url];
     }
    
     //浏览网页
     - (IBAction)browserClick:(UIButton *)sender {
         NSString *url=@"http://www.cnblogs.com/kenshincui"; [self openUrl:url];
     }
      #pragma mark - 私有方法
     -(void)openUrl:(NSString *)urlStr{
      //注意url中包含协议名称,iOS根据协议确定调用哪个应用,例如发送邮件是“sms://”其中“//”可以省略写成“sms:”(其他协议也是如此) 
         NSURL *url=[NSURL URLWithString:urlStr]; 
         UIApplication *application=[UIApplication sharedApplication];
         if(![application canOpenURL:url]){ 
             NSLog(@"无法打开\"%@\",请确保此应用已经正确安装.",url); 
             return;
         } 
         [[UIApplication sharedApplication] openURL:url];
     }
     @end
    

    不难发现当openURL:方法只要指定一个URL Schame并且已经安装了对应的应用程序就可以打开此应用。当然,如果是自己开发的应用也可以调用openURL方法来打开。假设你现在开发了一个应用A,如果用户机器上已经安装了此应用,并且在应用B中希望能够直接打开A。那么首先需要确保应用A已经配置了Url Types,具体方法就是在plist文件中添加URL types节点并配置URL Schemas作为具体协议,配置URL identifier作为这个URL的唯一标识,如下图![](http://upload-images.jianshu.io/upload_images/1663804-9d3c0ae6772c3d6b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)然后就可以调用openURL方法像打开系统应用一样打开第三方应用程序了:

    //打开第三方应用
    - (IBAction)thirdPartyApplicationClick:(UIButton *)sender { 
     NSString *url=@"cmj://myparams"; 
     [self openUrl:url];
    }
    //就像调用系统应用一样,协议后面可以传递一些参数(例如上面传递的myparams),这样一来在应用中可以在AppDelegate的
    -(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
    //代理方法中接收参数并解析。
     -(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{
         NSString *str=[NSString stringWithFormat:@"url:%@,source application:%@,params:%@",url,sourceApplication,[url host]]; 
         NSLog(@"%@",str);
         return YES;//是否打开
     }
    
  2. 系统服务之短信与邮件
    调用系统内置的应用来发送短信、邮件相当简单,但是这么操作也存在着一些弊端:当你点击了发送短信(或邮件)操作之后直接启动了系统的短信(或邮件)应用程序,我们的应用其实此时已经处于一种挂起状态,发送完(短信或邮件)之后无法自动回到应用界面。
    如果想要在应用程序内部完成这些操作则可以利用iOS中的MessageUI.framework,它提供了关于短信和邮件的UI接口供开发者在应用程序内部调用。从框架名称不难看出这是一套UI接口,提供有现成的短信和邮件的编辑界面,开发人员只需要通过编程的方式给短信和邮件控制器设置对应的参数即可。

在MessageUI.framework中主要有两个控制器类分别用于
发送短信(MFMessageComposeViewController)
邮件(MFMailComposeViewController),
它们均继承于UINavigationController。由于两个类使用方法十分类似,这里主要介绍一下MFMessageComposeViewController使用步骤:
*1. 创建MFMessageComposeViewController对象。
*2. 设置收件人recipients、信息正文body,如果运行商支持主题和附件的话可以设置主题subject、附件attachments(可以通过canSendSubject、canSendAttachments方法判断是否支持)
*3. 设置代理messageComposeDelegate(注意这里不是delegate属性,因为delegate属性已经留给UINavigationController,MFMessageComposeViewController没有覆盖此属性而是重新定义了一个代理),实现代理方法获得发送状态。

下面自定义一个发送短信的界面演示MFMessageComposeViewController的使用:
![](http://upload-images.jianshu.io/upload_images/1663804-91ac5967b31defaa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
用户通过在此界面输入短信信息点击“发送信息”调用MFMessageComposeViewController界面来展示或进一步编辑信息,点击MFMessageComposeViewController中的“发送”来完成短信发送工作,当然用户也可能点击“取消”按钮回到前一个短信编辑页面。
![](http://upload-images.jianshu.io/upload_images/1663804-3e8935df249cb1e3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
实现代码:
#import "KCSendMessageViewController.h"
@interface KCSendMessageViewController ()
@property (weak, nonatomic) IBOutlet UITextField *receivers;
@property (weak, nonatomic) IBOutlet UITextField *body;
@property (weak, nonatomic) IBOutlet UITextField subject;
@property (weak, nonatomic) IBOutlet UITextField attachments;
@end@implementation KCSendMessageViewController
#pragma mark - 控制器视图方法- (void)viewDidLoad { [super viewDidLoad];}#pragma mark - UI事件
- (IBAction)sendMessageClick:(UIButton )sender {
//如果能发送文本信息
if([MFMessageComposeViewController canSendText]){
MFMessageComposeViewController messageController=[[MFMessageComposeViewController alloc]init];
//收件人
messageController.recipients=[self.receivers.text componentsSeparatedByString:@","];
//信息正文
messageController.body=self.body.text;
//设置代理,注意这里不是delegate而是messageComposeDelegate
messageController.messageComposeDelegate=self;
//如果运行商支持主题
if([MFMessageComposeViewController canSendSubject]){
messageController.subject=self.subject.text;
}
//如果运行商支持附件
if ([MFMessageComposeViewController canSendAttachments]) {
/
第一种方法
/ //messageController.attachments=...;
/
第二种方法
/
NSArray *attachments= [self.attachments.text componentsSeparatedByString:@","];
if (attachments.count>0) {
[attachments enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString path=[[NSBundle mainBundle]pathForResource:obj ofType:nil];
NSURL url=[NSURL fileURLWithPath:path]; [messageController addAttachmentURL:url withAlternateFilename:obj];
}];
}
/
第三种方法
/
NSString *path=[[NSBundle mainBundle]pathForResource:@"photo.jpg" ofType:nil];
NSURL url=[NSURL fileURLWithPath:path];
NSData data=[NSData dataWithContentsOfURL:url];
//
* attatchData:文件数据 * uti:统一类型标识,标识具体文件类型,详情查看:帮助文档中System-Declared Uniform Type Identifiers * fileName:展现给用户看的文件名称 */
[messageController addAttachmentData:data typeIdentifier:@"public.image" filename:@"photo.jpg"];
}
[self presentViewController:messageController animated:YES completion:nil];
}}
#pragma mark - MFMessageComposeViewController代理方法
//发送完成,不管成功与否
-(void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result{
switch (result) {
case MessageComposeResultSent: NSLog(@"发送成功.");
break;
case MessageComposeResultCancelled: NSLog(@"取消发送.");
break; default: NSLog(@"发送失败.");
break; }
[self dismissViewControllerAnimated:YES completion:nil];
}
@end

这里需要强调一下:
.1) MFMessageComposeViewController的代理不是通过delegate属性指定的而是通过messageComposeDelegate指定的。
.2) 可以通过几种方式来指定发送的附件,在这个过程中请务必指定文件的后缀,否则在发送后无法正确识别文件类别(例如如果发送的是一张jpg图片,在发送后无法正确查看图片)。
.3) 无论发送成功与否代理方法
-(void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result都会执行,通过代理参数中的result来获得发送状态。
其实只要熟悉了MFMessageComposeViewController之后,那么用于发送邮件的MFMailComposeViewController用法和步骤完全一致,只是功能不同。下面看一下MFMailComposeViewController的使用:
#import "KCSendEmailViewController.h"
@interface KCSendEmailViewController ()
@property (weak, nonatomic) IBOutlet UITextField *toTecipients;
//收件人
@property (weak, nonatomic) IBOutlet UITextField *ccRecipients;
//抄送人
@property (weak, nonatomic) IBOutlet UITextField *bccRecipients;
//密送人
@property (weak, nonatomic) IBOutlet UITextField *subject;
//主题
@property (weak, nonatomic) IBOutlet UITextField *body;
//正文
@property (weak, nonatomic) IBOutlet UITextField *attachments;
//附件
@end
@implementation KCSendEmailViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
#pragma mark - UI事件
- (IBAction)sendEmailClick:(UIButton *)sender {
//判断当前是否能够发送邮件
if ([MFMailComposeViewController canSendMail]) {
MFMailComposeViewController *mailController=[[MFMailComposeViewController alloc]init];
//设置代理,注意这里不是delegate,而是mailComposeDelegate
mailController.mailComposeDelegate=self;
//设置收件人
[mailController setToRecipients:[self.toTecipients.text componentsSeparatedByString:@","]];
//设置抄送人
if (self.ccRecipients.text.length>0) {
[mailController setCcRecipients:[self.ccRecipients.text componentsSeparatedByString:@","]];
}
//设置密送人
if (self.bccRecipients.text.length>0) {
[mailController setBccRecipients:[self.bccRecipients.text componentsSeparatedByString:@","]];
}
//设置主题
[mailController setSubject:self.subject.text];
//设置内容
[mailController setMessageBody:self.body.text isHTML:YES];
//添加附件
if (self.attachments.text.length>0) {
NSArray *attachments=[self.attachments.text componentsSeparatedByString:@","] ;
[attachments enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString *file=[[NSBundle mainBundle] pathForResource:obj ofType:nil];
NSData *data=[NSData dataWithContentsOfFile:file];
[mailController addAttachmentData:data mimeType:@"image/jpeg" fileName:obj];
//第二个参数是mimeType类型,jpg图片对应image/jpeg
}];
}
[self presentViewController:mailController animated:YES completion:nil];
}}
#pragma mark - MFMailComposeViewController代理方法
-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error{
switch (result) {
case MFMailComposeResultSent:
NSLog(@"发送成功.");
break;
case MFMailComposeResultSaved://如果存储为草稿(点取消会提示是否存储为草稿,存储后可以到系统邮件应用的对应草稿箱找到)
NSLog(@"邮件已保存.");
break;
case MFMailComposeResultCancelled:
NSLog(@"取消发送.");
break;
default:
NSLog(@"发送失败.");
break;
}
if (error) {
NSLog(@"发送邮件过程中发生错误,错误信息:%@",error.localizedDescription);
}
[self dismissViewControllerAnimated:YES completion:nil];
}
@end

运行效果:

图片加载中...

![](http://upload-images.jianshu.io/upload_images/1663804-b9ba36d0596a4f8b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
2.2. 通讯录
iOS中带有一个Contacts应用程序来管理联系人,但是有些时候我们希望自己的应用能够访问或者修改这些信息,这个时候就要用到AddressBook.framework框架。
iOS中的通讯录是存储在数据库中的,由于iOS的权限设计,开发人员是不允许直接访问通讯录数据库的,必须依靠AddressBook提供的标准API来实现通讯录操作。通过AddressBook.framework开发者可以从底层去操作AddressBook.framework的所有信息,但是需要注意的是这个框架是基于C语言编写的,无法使用ARC来管理内存,开发者需要自己管理内存。下面大致介绍一下通讯录操作中常用的类型:

.1) ABAddressBookRef:代表通讯录对象,通过该对象开发人员不用过多的关注通讯录的存储方式,可以直接以透明的方式去访问、保存(在使用AddressBook.framework操作联系人时,所有的增加、删除、修改后都必须执行保存操作,类似于Core Data)等。

.2) ABRecordRef:代表一个通用的记录对象,可以是一条联系人信息,也可以是一个群组,可以通过ABRecordGetRecordType()函数获得具体类型。如果作为联系人(事实上也经常使用它作为联系人),那么这个记录记录了一个完整的联系人信息(姓名、性别、电话、邮件等),每条记录都有一个唯一的ID标示这条记录(可以通过ABRecordGetRecordID()函数获得)。

.3) ABPersonRef:代表联系人信息,很少直接使用,实际开发过程中通常会使用类型为“kABPersonType”的ABRecordRef来表示联系人(由此可见ABPersonRef其实是一种类型为“kABPersonType”的ABRecordRef)

.4)ABGroupRef:代表群组,与ABPersonRef类似,很少直接使用ABGroupRef,而是使用类型为“kABGroupType”的ABRecordRef来表示群组,一个群组可以包含多个联系人,一个联系人也同样可以多个群组。由于通讯录操作的关键是对ABRecordRef的操作,首先看一下常用的操作通讯录记录的方法:
ABPersonCreate():创建一个类型为“kABPersonType”的ABRecordRef。ABRecordCopyValue():取得指定属性的值。

ABRecordCopyCompositeName():取得联系人(或群组)的复合信息(对于联系人则包括:姓、名、公司等信息,对于群组则返回组名称)。
ABRecordSetValue():设置ABRecordRef的属性值。注意在设置ABRecordRef的值时又分为单值属性和多值属性:单值属性设置只要通过ABRecordSetValue()方法指定属性名和值即可;多值属性则要先通过创建一个ABMutableMultiValueRef类型的变量,然后通过ABMultiValueAddValueAndLabel()方法依次添加属性值,最后通过ABRecordSetValue()方法将ABMutableMultiValueRef类型的变量设置为记录值。
ABRecordRemoveValue():删除指定的属性值。
注意:由于联系人访问时(读取、设置、删除时)牵扯到大量联系人属性,可以到ABPerson.h中查询或者直接到帮助文档“Personal Information Properties”

通讯录的访问步骤一般如下:
调用ABAddressBookCreateWithOptions()方法创建通讯录对象ABAddressBookRef。
调用ABAddressBookRequestAccessWithCompletion()方法获得用户授权访问通讯录。
调用ABAddressBookCopyArrayOfAllPeople()、ABAddressBookCopyPeopleWithName()方法查询联系人信息。
调用ABAddressBookCopyArrayOfAllPeople()、ABAddressBookCopyPeopleWithName()方法查询联系人信息。
也就是说如果要修改或者删除都需要首先查询对应的联系人,然后修改或删除后提交更改。如果用户要增加一个联系人则不用进行查询,直接调用ABPersonCreate()方法创建一个ABRecord然后设置具体的属性,调用ABAddressBookAddRecord方法添加即可下面就通过一个示例演示一下如何通过ABAddressBook.framework访问通讯录,这个例子中通过一个UITableViewController模拟一下通讯录的查看、删除、添加操作。
主控制器视图,用于显示联系人,修改删除联系人:

KCContactViewController.h
#import/** * 定义一个协议作为代理 */
@protocol KCContactDelegate
//新增或修改联系人
-(void)editPersonWithFirstName:(NSString *)firstName lastName:(NSString *)lastName workNumber:(NSString *)workNumber;
//取消修改或新增
-(void)cancelEdit;@end@interface KCContactTableViewController : UITableViewController
@end

KCContactViewController.m
#import "KCContactTableViewController.h"
#import "KCAddPersonViewController.h"
@interface KCContactTableViewController ()
@property (assign,nonatomic) ABAddressBookRef addressBook;
//通讯录
@property (strong,nonatomic) NSMutableArray *allPerson;
//通讯录所有人员
@property (assign,nonatomic) int isModify;
//标识是修改还是新增,通过选择cell进行导航则认为是修改,否则视为新增
@property (assign,nonatomic) UITableViewCell *selectedCell;
//当前选中的单元格
@end
@implementation KCContactTableViewController
#pragma mark - 控制器视图
- (void)viewDidLoad {
     [super viewDidLoad]; 
     //请求访问通讯录并初始化数据
     [self requestAddressBook];
}
//由于在整个视图控制器周期内addressBook都驻留在内存中,所有当控制器视图销毁时销毁该对象
-(void)dealloc{ 
   if (self.addressBook!=NULL) { 
       CFRelease(self.addressBook); 
    }
}
#pragma mark - UI事件
//点击删除按钮
- (IBAction)trashClick:(UIBarButtonItem *)sender {    
self.tableView.editing=!self.tableView.editing;
}
#pragma mark - UITableView数据源方法
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 
    return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 
    return self.allPerson.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 
    static NSString *identtityKey=@"myTableViewCellIdentityKey1"; 
    UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:identtityKey]; 
    if(cell==nil){ 
        cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identtityKey]; 
    }
    //取得一条人员记录 
    ABRecordRef recordRef=(__bridge ABRecordRef)self.allPerson[indexPath.row]; 
   //取得记录中得信息
    NSString *firstName=(__bridge NSString *) ABRecordCopyValue(recordRef, kABPersonFirstNameProperty);
   //注意这里进行了强转,不用自己释放资源 
    NSString *lastName=(__bridge NSString *)ABRecordCopyValue(recordRef, kABPersonLastNameProperty); 
    ABMultiValueRef phoneNumbersRef= ABRecordCopyValue(recordRef, kABPersonPhoneProperty);
   //获取手机号,注意手机号是ABMultiValueRef类,有可能有多条//
    NSArray *phoneNumbers=(__bridge NSArray *)ABMultiValueCopyArrayOfAllValues(phoneNumbersRef);
   //取得CFArraryRef类型的手机记录并转化为
   NSArrary long count= ABMultiValueGetCount(phoneNumbersRef);
 // 
     for(int i=0;i0) { 
         cell.detailTextLabel.text=(__bridge NSString *)(ABMultiValueCopyValueAtIndex(phoneNumbersRef, 0));
      }
    if(ABPersonHasImageData(recordRef)){
        //如果有照片数据
        NSData *imageData= (__bridge NSData *)(ABPersonCopyImageData(recordRef)); 
        cell.imageView.image=[UIImage imageWithData:imageData];
     }else{ 
         cell.imageView.image=[UIImage imageNamed:@"avatar"];
         //没有图片使用默认头像 
    }
    //使用cell的tag存储记录id 
    cell.tag=ABRecordGetRecordID(recordRef); 
   return cell;
 }
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { 
    if (editingStyle == UITableViewCellEditingStyleDelete) {     
        ABRecordRef recordRef=(__bridge ABRecordRef )self.allPerson[indexPath.row];
        [self removePersonWithRecord:recordRef];
        //从通讯录删除
        [self.allPerson removeObjectAtIndex:indexPath.row];
        //从数组移除
        [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 - UITableView代理方法
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ 
    self.isModify=1; 
    self.selectedCell=[tableView cellForRowAtIndexPath:indexPath];
    [self performSegueWithIdentifier:@"AddPerson" sender:self];
}
 #pragma mark - Navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
   if([segue.identifier isEqualToString:@"AddPerson"]){ 
       UINavigationController *navigationController=(UINavigationController *)segue.destinationViewController; 
   //根据导航控制器取得添加/修改人员的控制器视图 
   KCAddPersonViewController *addPersonController=(KCAddPersonViewController *)navigationController.topViewController; addPersonController.delegate=self; 
   //如果是通过选择cell进行的导航操作说明是修改,否则为添加 
   if (self.isModify) { 
       UITableViewCell *cell=self.selectedCell; addPersonController.recordID=(ABRecordID)cell.tag;
       //设置 NSArray *array=[cell.textLabel.text componentsSeparatedByString:@" "];
       if (array.count>0) {
           addPersonController.firstNameText=[array firstObject]; 
       }
       if (array.count>1) {
           addPersonController.lastNameText=[array lastObject]; 
       } 
       addPersonController.workPhoneText=cell.detailTextLabel.text; 
    } 
}}
#pragma mark - KCContact代理方法
-(void)editPersonWithFirstName:(NSString *)firstName lastName:(NSString *)lastName workNumber:(NSString *)workNumber{ 
    if (self.isModify) { 
        UITableViewCell *cell=self.selectedCell; NSIndexPath *indexPath= [self.tableView indexPathForCell:cell]; 
        [self modifyPersonWithRecordID:(ABRecordID)cell.tag firstName:firstName lastName:lastName workNumber:workNumber];
        [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight]; 
    }else{
        [self addPersonWithFirstName:firstName lastName:lastName workNumber:workNumber];
        //通讯簿中添加信息 
        [self initAllPerson];
        //重新初始化数据 
        [self.tableView reloadData];
    } 
    self.isModify=0;
}
-(void)cancelEdit{ 
    self.isModify=0;
}
#pragma mark - 私有方法/** * 请求访问通讯录 */
-(void)requestAddressBook{
    //创建通讯录对象 
    self.addressBook=ABAddressBookCreateWithOptions(NULL, NULL); 
    //请求访问用户通讯录,注意无论成功与否block都会调用 
    ABAddressBookRequestAccessWithCompletion(self.addressBook, ^(bool granted, CFErrorRef error) {
       if (!granted) { NSLog(@"未获得通讯录访问权限!"); 
    }
    [self initAllPerson]; 
   });
}
/** * 取得所有通讯录记录 */
-(void)initAllPerson{ 
    //取得通讯录访问授权 
    ABAuthorizationStatus authorization= ABAddressBookGetAuthorizationStatus(); 
    //如果未获得授权 
    if (authorization!=kABAuthorizationStatusAuthorized) { 
        NSLog(@"尚未获得通讯录访问授权!"); 
        return ;
     } 
    //取得通讯录中所有人员记录
    CFArrayRef allPeople= ABAddressBookCopyArrayOfAllPeople(self.addressBook); 
    self.allPerson=(__bridge NSMutableArray *)allPeople;
    //释放资源 CFRelease(allPeople);
}
/** * 删除指定的记录 * * @param recordRef 要删除的记录 */
-(void)removePersonWithRecord:(ABRecordRef)recordRef{ 
    ABAddressBookRemoveRecord(self.addressBook, recordRef, NULL);
    //删除 
    ABAddressBookSave(self.addressBook, NULL);
    //删除之后提交更改
}
/** * 根据姓名删除记录 */
-(void)removePersonWithName:(NSString *)personName{ 
    CFStringRef personNameRef=(__bridge CFStringRef)(personName); 
    CFArrayRef recordsRef= ABAddressBookCopyPeopleWithName(self.addressBook, personNameRef);
    //根据人员姓名查找 
    CFIndex count= CFArrayGetCount(recordsRef);
    //取得记录数 for (CFIndex i=0; i

新增或修改控制器视图,用于显示一个联系人的信息或者新增一个联系人:
KCAddPersonViewController.h

#import
@protocol KCContactDelegate;
@interface KCAddPersonViewController : UIViewController
@property (assign,nonatomic) int recordID;
//通讯录记录id,如果ID不为0则代表修改否则认为是新增
@property (strong,nonatomic) NSString *firstNameText;
@property (strong,nonatomic) NSString *lastNameText;
@property (strong,nonatomic) NSString *workPhoneText;
@property (strong,nonatomic) iddelegate;@end

KCAddPersonViewController.m

#import "KCAddPersonViewController.h"
#import "KCContactTableViewController.h"
@interface KCAddPersonViewController ()
@property (weak, nonatomic) IBOutlet UITextField *firstName;
@property (weak, nonatomic) IBOutlet UITextField *lastName;
@property (weak, nonatomic) IBOutlet UITextField *workPhone;
@end
@implementation KCAddPersonViewController
- (void)viewDidLoad { 
    [super viewDidLoad]; 
    [self setupUI];
}
#pragma mark - UI事件
- (IBAction)cancelClick:(UIBarButtonItem *)sender { 
    [self.delegate cancelEdit]; 
    [self dismissViewControllerAnimated:YES completion:nil];
}
- (IBAction)doneClick:(UIBarButtonItem *)sender { 
    //调用代理方法 
    [self.delegate editPersonWithFirstName:self.firstName.text lastName:self.lastName.text workNumber:self.workPhone.text]; 
    [self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - 私有方法
-(void)setupUI{
     if (self.recordID) {
        //如果ID不为0则认为是修改,此时需要初始化界面
        self.firstName.text=self.firstNameText; 
        self.lastName.text=self.lastNameText; 
        self.workPhone.text=self.workPhoneText; 
     }
}
@end

运行效果:![](http://upload-images.jianshu.io/upload_images/1663804-aadc436a1d0ba28f.gif?imageMogr2/auto-orient/strip)

备注:
1.上文中所指的以Ref结尾的对象事实上是该对象的指针(或引用),在C语言的框架中多数类型会以Ref结尾,这个类型本身就是一个指针,定义时不需要加“*”。

2.通常方法中包含copy、create、new、retain等关键字的方法创建的变量使用之后需要调用对应的release方法释放。例如:使用ABPersonCreate();创建完ABRecordRef变量后使用CFRelease()方法释放。

3.在与很多C语言框架交互时可以都存在Obj-C和C语言类型之间的转化(特别是Obj-C和Core Foundation框架中的一些转化),此时可能会用到桥接,只要在强转之后前面加上”__bridge”即可,经过桥接转化后的类型不需要再去手动维护内存,也就不需要使用对应的release方法释放内存。

4.AddressBook框架中很多类型的创建、属性设置等都是以这个类型名开发头的方法来创建的,事实上如果大家熟悉了其他框架会发现也都是类似的,这是Apple开发中约定俗成的命名规则(特别是C语言框架)。例如:要给ABRecordRef类型的变量设置属性则可以通过ABRecordSetValue()方法完成。

AddressBookUI

使用AddressBook.framework来操作通讯录特点就是可以对通讯录有更加精确的控制,但是缺点就是面对大量C语言API稍嫌麻烦,于是Apple官方提供了另一套框架供开发者使用,那就是AddressBookUI.framework。例如前面查看、新增、修改人员的界面这个框架就提供了现成的控制器视图供开发者使用。

下面是这个框架中提供的控制器视图:

  1. ABPersonViewController:用于查看联系人信息(可设置编辑)。需要设置displayedPerson属性来设置要显示或编辑的联系人。
  2. ABNewPersonViewController:用于新增联系人信息。
  3. ABUnknownPersonViewController:用于显示一个未知联系人(尚未保存的联系人)信息。需要设置displayedPerson属性来设置要显示的未知联系人。

以上三个控制器视图均继承于UIViewController,在使用过程中必须使用一个UINavigationController进行包装,否则只能看到视图内容无法进行操作(例如对于ABNewPersonViewController如果不使用UINavigationController进行包装则没有新增和取消按钮),同时注意包装后的控制器视图不需要处理具体新增、修改逻辑(增加和修改的处理逻辑对应的控制器视图内部已经完成),但是必须处理控制器的关闭操作(调用dismissViewControllerAnimated::方法),并且可以通过代理方法获得新增、修改的联系人。下面看一下三个控制器视图的代理方法:

ABPersonViewController的displayViewDelegate代理方法:

-(BOOL)personViewController:(ABPersonViewController *)personViewController shouldPerformDefaultActionForPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:``(ABMultiValueIdentifier)identifier:
此方法会在选择了一个联系人属性后触发,四个参数分别代表:使用的控制器视图、所查看的联系人、所选则的联系人属性、该属性是否是多值属性。

ABNewPersonViewController的newPersonViewDelegate代理方法:

-(void)newPersonViewController:(ABNewPersonViewController *)newPersonView didCompleteWithNewPerson:(ABRecordRef)person:
 点击取消或完成后触发,如果参数中的person为NULL说明点击了取消,否则说明点击了完成。无论是取消还是完成操作,此方法调用时保存操作已经进行完毕,不需要在此方法中自己保存联系人信息。

ABUnkownPersonViewcontroller的unkownPersonViewDelegate代理方法:

-(void)unknownPersonViewController:(ABUnknownPersonViewController *)unknownCardViewController didResolveToPerson:(ABRecordRef)person:
保存此联系人时调用,调用后将此联系人返回。
-(BOOL)unknownPersonViewController:(ABUnknownPersonViewController *)personViewController shouldPerformDefaultActionForPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier:
选择一个位置联系人属性之后执行,返回值代表是否执行默认的选择操作(例如如果是手机号,默认操作会拨打此电话)

除了上面三类控制器视图在AddressBookUI中还提供了另外一个控制器视图ABPeoplePickerNavigationController,它与之前介绍的UIImagePickerController、MPMediaPickerController类似,只是他是用来选择一个联系人的。这个控制器视图本身继承于UINavigationController,视图自身的“组”、“取消”按钮操作不需要开发者来完成(例如开发者不用在点击取消是关闭当前控制器视图,它自身已经实现了关闭方法),当然这里主要说一下这个

控制器视图的peoplePickerDelegate代理方法:

-(void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person:
选择一个联系人后执行。此代理方法实现后代理方法“
-(void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier”
不会再执行。并且一旦实现了这个代理方法用户只能选择到联系人视图,无法查看具体联系人的信息。
-(void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker:
用户点击取消后执行。
-(void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier:
选择联系人具体的属性后执行,注意如果要执行此方法则不能实现
-(void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person
代理方法,此时如果点击一个具体联系人会导航到联系人详细信息界面,用户点击具体的属性后触发此方法。

下面就看一下上面四个控制器视图的使用方法,在下面的程序中定义了四个按钮,点击不同的按钮调用不同的控制器视图用于演示:

#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
     [super viewDidLoad];
}
#pragma mark - UI事件//添加联系人
- (IBAction)addPersonClick:(UIButton *)sender { 
    ABNewPersonViewController *newPersonController=[[ABNewPersonViewController alloc]init]; 
    //设置代理 
    newPersonController.newPersonViewDelegate=self; 
    //注意ABNewPersonViewController必须包装一层UINavigationController才能使用,否则不会出现取消和完成按钮,无法进行保存等操作 
    UINavigationController *navigationController=[[UINavigationController alloc]initWithRootViewController:newPersonController]; 
    [self presentViewController:navigationController animated:YES completion:nil];
}
- (IBAction)unknownPersonClick:(UIButton *)sender { 
    ABUnknownPersonViewController *unknownPersonController=[[ABUnknownPersonViewController alloc]init]; 
    //设置未知人员 
    ABRecordRef recordRef=ABPersonCreate(); 
    ABRecordSetValue(recordRef, kABPersonFirstNameProperty, @"Kenshin", NULL); 
    ABRecordSetValue(recordRef, kABPersonLastNameProperty, @"Cui", NULL);
    ABMultiValueRef multiValueRef=ABMultiValueCreateMutable(kABStringPropertyType); 
    ABMultiValueAddValueAndLabel(multiValueRef, @"18500138888", kABHomeLabel, NULL); 
    ABRecordSetValue(recordRef, kABPersonPhoneProperty, multiValueRef, NULL); unknownPersonController.displayedPerson=recordRef; 
    //设置代理 
    unknownPersonController.unknownPersonViewDelegate=self;
    //设置其他属性 
    unknownPersonController.allowsActions=YES;
   //显示标准操作按钮 
    unknownPersonController.allowsAddingToAddressBook=YES;
    //是否允许将联系人添加到地址簿 
    CFRelease(multiValueRef); 
    CFRelease(recordRef); 
    //使用导航控制器包装 
    UINavigationController *navigationController=[[UINavigationController alloc]initWithRootViewController:unknownPersonController]; 
    [self presentViewController:navigationController animated:YES completion:nil];
}
- (IBAction)showPersonClick:(UIButton *)sender { 
    ABPersonViewController *personController=[[ABPersonViewController alloc]init]; 
    //设置联系人 ABAddressBookRef addressBook=ABAddressBookCreateWithOptions(NULL, NULL); 
    ABRecordRef recordRef= ABAddressBookGetPersonWithRecordID(addressBook, 1);
    //取得id为1的联系人记录 
    personController.displayedPerson=recordRef; 
    //设置代理 
    personController.personViewDelegate=self; 
    //设置其他属性 
    personController.allowsActions=YES;
    //是否显示发送信息、共享联系人等按钮 
    personController.allowsEditing=YES;
    //允许编辑// 
    personController.displayedProperties=@[@(kABPersonFirstNameProperty),@(kABPersonLastNameProperty)];
    //显示的联系人属性信息,默认显示所有信息 //使用导航控制器包装
    UINavigationController *navigationController=[[UINavigationController alloc]initWithRootViewController:personController]; 
    [self presentViewController:navigationController animated:YES completion:nil];
}
- (IBAction)selectPersonClick:(UIButton *)sender { 
    ABPeoplePickerNavigationController *peoplePickerController=[[ABPeoplePickerNavigationController alloc]init]; 
    //设置代理 
    peoplePickerController.peoplePickerDelegate=self;
    [self presentViewController:peoplePickerController animated:YES completion:nil];
}
#pragma mark - ABNewPersonViewController代理方法//完成新增(点击取消和完成按钮时调用),注意这里不用做实际的通讯录增加工作,此代理方法调用时已经完成新增,当保存成功的时候参数中得person会返回保存的记录,如果点击取消person为NULL
-(void)newPersonViewController:(ABNewPersonViewController *)newPersonView didCompleteWithNewPerson:(ABRecordRef)person{ 
    //如果有联系人信息 
    if (person) { 
        NSLog(@"%@ 信息保存成功.",(__bridge NSString *)(ABRecordCopyCompositeName(person))); 
    }else{ 
        NSLog(@"点击了取消."); 
    }
    //关闭模态视图窗口 
    [self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - ABUnknownPersonViewController代理方法//保存未知联系人时触发
-(void)unknownPersonViewController:(ABUnknownPersonViewController *)unknownCardViewController didResolveToPerson:(ABRecordRef)person{
     if (person) { 
        NSLog(@"%@ 信息保存成功!",(__bridge NSString *)(ABRecordCopyCompositeName(person)));
     } 
    [self dismissViewControllerAnimated:YES completion:nil];
}
//选择一个人员属性后触发,返回值YES表示触发默认行为操作,否则执行代理中自定义的操作
-(BOOL)unknownPersonViewController:(ABUnknownPersonViewController *)personViewController shouldPerformDefaultActionForPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier{
     if (person) {
         NSLog(@"选择了属性:%i,值:%@.",property,(__bridge NSString *)ABRecordCopyValue(person, property)); 
     }
 return NO;
}
#pragma mark - ABPersonViewController代理方法
//选择一个人员属性后触发,返回值YES表示触发默认行为操作,否则执行代理中自定义的操作
-(BOOL)personViewController:(ABPersonViewController *)personViewController shouldPerformDefaultActionForPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier{
    if (person) { 
        NSLog(@"选择了属性:%i,值:%@.",property,(__bridge NSString *)ABRecordCopyValue(person, property)); 
     } 
return NO;
}
#pragma mark - ABPeoplePickerNavigationController代理方法//选择一个联系人后,注意这个代理方法实现后属性选择的方法将不会再调用
-(void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person{ 
    if (person) {
        NSLog(@"选择了%@.",(__bridge NSString *)(ABRecordCopyCompositeName(person)));
    }
}
//选择属性之后,注意如果上面的代理方法实现后此方法不会被调用//
-(void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier{
    // if (person && property) {
       // NSLog(@"选择了属性:%i,值:%@.",property,(__bridge NSString *)ABRecordCopyValue(person, property));
    // }
}
//点击取消按钮    
-(void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker{
     NSLog(@"取消选择.");
}
@end

![](http://upload-images.jianshu.io/upload_images/1663804-52aeaf5b8ee5d9ac.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

![](http://upload-images.jianshu.io/upload_images/1663804-d9e2e1b7c8f89c24.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

![](http://upload-images.jianshu.io/upload_images/1663804-ea2b1abc409270b1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

![](http://upload-images.jianshu.io/upload_images/1663804-7bb9e96ad7f56df6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

注意:为了让大家可以更加清楚的看到几个控制器视图的使用,这里并没有结合前面的UITableViewController来使用,事实上大家结合前面UITableViewController可以做一个完善的通讯录应用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容

  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,124评论 29 470
  • 一、系统应用 在开发某些应用时,我们可能希望能够调用iOS系统内置的电话、短信、邮件、浏览器应用,或者直接调用安装...
    执着丶执念阅读 1,641评论 8 25
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 中国股市政策的制定,是以巩固和加强党的领导为出发点的,这是股市政策风险的根源。保持政权的稳定,是党和政府的“大局”...
    会逃跑的小偷阅读 65评论 0 0
  • 1:准备工作 在网页http://dev.mysql.com/downloads/mysql/下载Windows ...
    clshinem阅读 1,037评论 1 1