iOS 开发系列 -- 通讯录、蓝牙、内购、GameCenter、iCloud、Passbook 系统服务开发汇总
参考链接:http://www.cnblogs.com/kenshincui/p/4220402.html
iOS 开发过程中有时候难免会使用 iOS 内置的一些应用软件和服务,例如 QQ 通讯录、微信电话本会使用 iOS 的通讯录,一些第三方软件会在应用内发送短信等。今天将和大家一起学习如何使用系统应用、使用系统服务:
在开发某些应用时可能希望能够调用 iOS 系统内置的电话、短信、邮件、浏览器应用,此时你可以直接使用 UIApplication 的 OpenURL: 方法指定特定的协议来打开不同的系统应用。常用的协议如下:
打电话:tel: 或者 tel://、telprompt: 或 telprompt://(拨打电话前有提示)
发短信:sms: 或者 sms://
发送邮件:mailto: 或者
启动浏览器:http: 或者 http://
下面以一个简单的 demo 演示如何调用上面几种系统应用:
- //
- // ViewController.m
- // iOSSystemApplication
- //
- // Created by Kenshin Cui on 14/04/05.
- // Copyright (c) 2014年 cmjstudio. All rights reserved.
- //
- #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 的唯一标识,如下图:
然后就可以调用 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; //是否打开
- }
调用系统内置的应用来发送短信、邮件相当简单,但是这么操作也存在着一些弊端:当你点击了发送短信(或邮件)操作之后直接启动了系统的短信(或邮件)应用程序,我们的应用其实此时已经处于一种挂起状态,发送完(短信或邮件)之后无法自动回到应用界面。如果想要在应用程序内部完成这些操作则可以利用 iOS 中的 MessageUI.framework, 它提供了关于短信和邮件的 UI 接口供开发者在应用程序内部调用。从框架名称不难看出这是一套 UI 接口,提供有现成的短信和邮件的编辑界面,开发人员只需要通过编程的方式给短信和邮件控制器设置对应的参数即可。
在 MessageUI.framework 中主要有两个控制器类分别用于发送短信(MFMessageComposeViewController)和邮件(MFMailComposeViewController), 它们均继承于 UINavigationController。由于两个类使用方法十分类似,这里主要介绍一下 MFMessageComposeViewController 使用步骤:
下面自定义一个发送短信的界面演示 MFMessageComposeViewController 的使用:
用户通过在此界面输入短信信息点击 "发送信息" 调用 MFMessageComposeViewController 界面来展示或进一步编辑信息,点击 MFMessageComposeViewController 中的 "发送" 来完成短信发送工作,当然用户也可能点击 "取消" 按钮回到前一个短信编辑页面。
实现代码:
- //
- // KCSendMessageViewController.m
- // iOSSystemApplication
- //
- // Created by Kenshin Cui on 14/04/05.
- // Copyright (c) 2014年 cmjstudio. All rights reserved.
- //
- #import "KCSendMessageViewController.h"#import < MessageUI / MessageUI.h > @interface KCSendMessageViewController() < MFMessageComposeViewControllerDelegate > @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
这里需要强调一下:
其实只要熟悉了 MFMessageComposeViewController 之后,那么用于发送邮件的 MFMailComposeViewController 用法和步骤完全一致,只是功能不同。下面看一下 MFMailComposeViewController 的使用:
- //
- // KCSendEmailViewController.m
- // iOSSystemApplication
- //
- // Created by Kenshin Cui on 14/04/05.
- // Copyright (c) 2014年 cmjstudio. All rights reserved.
- //
- #import "KCSendEmailViewController.h"#import < MessageUI / MessageUI.h > @interface KCSendEmailViewController() < MFMailComposeViewControllerDelegate > @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
运行效果:
iOS 中带有一个 Contacts 应用程序来管理联系人,但是有些时候我们希望自己的应用能够访问或者修改这些信息,这个时候就要用到 AddressBook.framework 框架。iOS 中的通讯录是存储在数据库中的,由于 iOS 的权限设计,开发人员是不允许直接访问通讯录数据库的,必须依靠 AddressBook 提供的标准 API 来实现通讯录操作。通过 AddressBook.framework 开发者可以从底层去操作 AddressBook.framework 的所有信息,但是需要注意的是这个框架是基于 C 语言编写的,无法使用 ARC 来管理内存,开发者需要自己管理内存。下面大致介绍一下通讯录操作中常用的类型:
由于通讯录操作的关键是对 ABRecordRef 的操作,首先看一下常用的操作通讯录记录的方法:
ABPersonCreate(): 创建一个类型为"kABPersonType" 的 ABRecordRef。
ABRecordCopyValue(): 取得指定属性的值。
ABRecordCopyCompositeName(): 取得联系人(或群组)的复合信息(对于联系人则包括:姓、名、公司等信息,对于群组则返回组名称)。
ABRecordSetValue(): 设置 ABRecordRef 的属性值。注意在设置 ABRecordRef 的值时又分为单值属性和多值属性:单值属性设置只要通过 ABRecordSetValue() 方法指定属性名和值即可;多值属性则要先通过创建一个 ABMutableMultiValueRef 类型的变量,然后通过 ABMultiValueAddValueAndLabel() 方法依次添加属性值,最后通过 ABRecordSetValue() 方法将 ABMutableMultiValueRef 类型的变量设置为记录值。
ABRecordRemoveValue(): 删除指定的属性值。
注意:
由于联系人访问时(读取、设置、删除时)牵扯到大量联系人属性,可以到 ABPerson.h 中查询或者直接到帮助文档 ""
通讯录的访问步骤一般如下:
下面就通过一个示例演示一下如何通过 ABAddressBook.framework 访问通讯录,这个例子中通过一个 UITableViewController 模拟一下通讯录的查看、删除、添加操作。
主控制器视图,用于显示联系人,修改删除联系人:
KCContactViewController.h
- //
- // KCTableViewController.h
- // AddressBook
- //
- // Created by Kenshin Cui on 14/04/05.
- // Copyright (c) 2014年 cmjstudio. All rights reserved.
- //
- #import < UIKit / UIKit.h >
- /**
- * 定义一个协议作为代理
- */
- @protocol KCContactDelegate
- //新增或修改联系人
- - (void) editPersonWithFirstName: (NSString * ) firstName lastName: (NSString * ) lastName workNumber: (NSString * ) workNumber;
- //取消修改或新增
- - (void) cancelEdit;@end@interface KCContactTableViewController: UITableViewController@end
KCContactViewController.m
- //
- // KCTableViewController.m
- // AddressBook
- //
- // Created by Kenshin Cui on 14/04/05.
- // Copyright (c) 2014年 cmjstudio. All rights reserved.
- //
- #import "KCContactTableViewController.h"#import#import "KCAddPersonViewController.h"@interface KCContactTableViewController() < KCContactDelegate > @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;i<count;++i){
- // NSString *phoneLabel= (__bridge NSString *)(ABMultiValueCopyLabelAtIndex(phoneNumbersRef, i));
- // NSString *phoneNumber=(__bridge NSString *)(ABMultiValueCopyValueAtIndex(phoneNumbersRef, i));
- // NSLog(@"%@:%@",phoneLabel,phoneNumber);
- // }
- cell.textLabel.text = [NSString stringWithFormat: @"%@ %@", firstName, lastName];
- if (count > 0) {
- 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 < count; ++i) {
- ABRecordRef recordRef = CFArrayGetValueAtIndex(recordsRef, i); //取得指定的记录
- ABAddressBookRemoveRecord(self.addressBook, recordRef, NULL); //删除
- }
- ABAddressBookSave(self.addressBook, NULL); //删除之后提交更改
- CFRelease(recordsRef);
- }
- /**
- * 添加一条记录
- *
- * @param firstName 名
- * @param lastName 姓
- * @param iPhoneName iPhone手机号
- */
- - (void) addPersonWithFirstName: (NSString * ) firstName lastName: (NSString * ) lastName workNumber: (NSString * ) workNumber {
- //创建一条记录
- ABRecordRef recordRef = ABPersonCreate();
- ABRecordSetValue(recordRef, kABPersonFirstNameProperty, (__bridge CFTypeRef)(firstName), NULL); //添加名
- ABRecordSetValue(recordRef, kABPersonLastNameProperty, (__bridge CFTypeRef)(lastName), NULL); //添加姓
- ABMutableMultiValueRef multiValueRef = ABMultiValueCreateMutable(kABStringPropertyType); //添加设置多值属性
- ABMultiValueAddValueAndLabel(multiValueRef, (__bridge CFStringRef)(workNumber), kABWorkLabel, NULL); //添加工作电话
- ABRecordSetValue(recordRef, kABPersonPhoneProperty, multiValueRef, NULL);
- //添加记录
- ABAddressBookAddRecord(self.addressBook, recordRef, NULL);
- //保存通讯录,提交更改
- ABAddressBookSave(self.addressBook, NULL);
- //释放资源
- CFRelease(recordRef);
- CFRelease(multiValueRef);
- }
- /**
- * 根据RecordID修改联系人信息
- *
- * @param recordID 记录唯一ID
- * @param firstName 姓
- * @param lastName 名
- * @param homeNumber 工作电话
- */
- - (void) modifyPersonWithRecordID: (ABRecordID) recordID firstName: (NSString * ) firstName lastName: (NSString * ) lastName workNumber: (NSString * ) workNumber {
- ABRecordRef recordRef = ABAddressBookGetPersonWithRecordID(self.addressBook, recordID);
- ABRecordSetValue(recordRef, kABPersonFirstNameProperty, (__bridge CFTypeRef)(firstName), NULL); //添加名
- ABRecordSetValue(recordRef, kABPersonLastNameProperty, (__bridge CFTypeRef)(lastName), NULL); //添加姓
- ABMutableMultiValueRef multiValueRef = ABMultiValueCreateMutable(kABStringPropertyType);
- ABMultiValueAddValueAndLabel(multiValueRef, (__bridge CFStringRef)(workNumber), kABWorkLabel, NULL);
- ABRecordSetValue(recordRef, kABPersonPhoneProperty, multiValueRef, NULL);
- //保存记录,提交更改
- ABAddressBookSave(self.addressBook, NULL);
- //释放资源
- CFRelease(multiValueRef);
- }@end
新增或修改控制器视图,用于显示一个联系人的信息或者新增一个联系人:
KCAddPersonViewController.h
- //
- // KCAddPersonViewController.h
- // AddressBook
- //
- // kABPersonFirstNameProperty
- // Copyright (c) 2014年 cmjstudio. All rights reserved.
- //
- #import < UIKit / UIKit.h > @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) id < KCContactDelegate > delegate;@end
KCAddPersonViewController.m
- //
- // KCAddPersonViewController.m
- // AddressBook
- //
- // kABPersonFirstNameProperty
- // Copyright (c) 2014年 cmjstudio. All rights reserved.
- //
- #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
运行效果:
备注:
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() 方法完成。
使用 AddressBook.framework 来操作通讯录特点就是可以对通讯录有更加精确的控制,但是缺点就是面对大量 C 语言 API 稍嫌麻烦,于是 Apple 官方提供了另一套框架供开发者使用,那就是 AddressBookUI.framework。例如前面查看、新增、修改人员的界面这个框架就提供了现成的控制器视图供开发者使用。下面是这个框架中提供的控制器视图:
以上三个控制器视图均继承于 UIViewController,在使用过程中必须使用一个 UINavigationController 进行包装,否则只能看到视图内容无法进行操作(例如对于 ABNewPersonViewController 如果不使用 UINavigationController 进行包装则没有新增和取消按钮),同时注意包装后的控制器视图不需要处理具体新增、修改逻辑(增加和修改的处理逻辑对应的控制器视图内部已经完成),但是必须处理控制器的关闭操作(调用 dismissViewControllerAnimated:: 方法),并且可以通过代理方法获得新增、修改的联系人。下面看一下三个控制器视图的代理方法:
1.ABPersonViewController 的 displayViewDelegate 代理方法:
-(BOOL)personViewController:(ABPersonViewController *)personViewController shouldPerformDefaultActionForPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier:此方法会在选择了一个联系人属性后触发,四个参数分别代表:使用的控制器视图、所查看的联系人、所选则的联系人属性、该属性是否是多值属性。
2.ABNewPersonViewController 的 newPersonViewDelegate 代理方法:
-(void)newPersonViewController:(ABNewPersonViewController *)newPersonView didCompleteWithNewPerson:(ABRecordRef)person:点击取消或完成后触发,如果参数中的 person 为 NULL 说明点击了取消,否则说明点击了完成。无论是取消还是完成操作,此方法调用时保存操作已经进行完毕,不需要在此方法中自己保存联系人信息。
3.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 代理方法,此时如果点击一个具体联系人会导航到联系人详细信息界面,用户点击具体的属性后触发此方法。
下面就看一下上面四个控制器视图的使用方法,在下面的程序中定义了四个按钮,点击不同的按钮调用不同的控制器视图用于演示:
- //
- // ViewController.m
- // AddressBookUI
- //
- // Created by Kenshin Cui on 14/04/05.
- // Copyright (c) 2014年 cmjstudio. All rights reserved.
- //
- #import "ViewController.h"#import@interface ViewController() < ABNewPersonViewControllerDelegate,
- ABUnknownPersonViewControllerDelegate,
- ABPeoplePickerNavigationControllerDelegate,
- ABPersonViewControllerDelegate > @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
运行效果:
注意:
为了让大家可以更加清楚的看到几个控制器视图的使用,这里并没有结合前面的 UITableViewController 来使用,事实上大家结合前面 UITableViewController 可以做一个完善的通讯录应用。
随着蓝牙低功耗技术 BLE(Bluetooth Low Energy)的发展,蓝牙技术正在一步步成熟,如今的大部分移动设备都配备有蓝牙 4.0,相比之前的蓝牙技术耗电量大大降低。从 iOS 的发展史也不难看出苹果目前对蓝牙技术也是越来越关注,例如苹果于 2013 年 9 月发布的 iOS7 就配备了 iBeacon 技术,这项技术完全基于蓝牙传输。但是众所周知苹果的设备对于权限要求也是比较高的,因此在 iOS 中并不能像 Android 一样随意使用蓝牙进行文件传输(除非你已经越狱)。在 iOS 中进行蓝牙传输应用开发常用的框架有如下几种:
GameKit.framework:iOS7 之前的蓝牙通讯框架,从 iOS7 开始过期,但是目前多数应用还是基于此框架。
MultipeerConnectivity.framework:iOS7 开始引入的新的蓝牙通讯开发框架,用于取代 GameKit。
CoreBluetooth.framework:功能强大的蓝牙开发框架,要求设备必须支持蓝牙 4.0。
前两个框架使用起来比较简单,但是缺点也比较明显:仅仅支持 iOS 设备,传输内容仅限于沙盒或者照片库中用户选择的文件,并且第一个框架只能在同一个应用之间进行传输(一个 iOS 设备安装应用 A,另一个 iOS 设备上安装应用 B 是无法传输的)。当然 CoreBluetooth 就摆脱了这些束缚,它不再局限于 iOS 设备之间进行传输,你可以通过 iOS 设备向 Android、Windows Phone 以及其他安装有蓝牙 4.0 芯片的智能设备传输,因此也是目前智能家居、无线支付等热门智能设备所推崇的技术。
其实从名称来看这个框架并不是专门为了支持蓝牙传输而设计的,它是为游戏设计的。而很多游戏中会用到基于蓝牙的点对点信息传输,因此这个框架中集成了蓝牙传输模块。前面也说了这个框架本身有很多限制,但是在 iOS7 之前的很多蓝牙传输都是基于此框架的,所以有必要对它进行了解。GameKit 中的蓝牙使用设计很简单,并没有给开发者留有太多的复杂接口,而多数连接细节开发者是不需要关注的。GameKit 中提供了两个关键类来操作蓝牙连接:
GKPeerPickerController:蓝牙查找、连接用的视图控制器,通常情况下应用程序 A 打开后会调用此控制器的 show 方法来展示一个蓝牙查找的视图,一旦发现了另一个同样在查找蓝牙连接的客户客户端 B 就会出现在视图列表中,此时如果用户点击连接 B,B 客户端就会询问用户是否允许 A 连接 B,如果允许后 A 和 B 之间建立一个蓝牙连接。
GKSession:连接会话,主要用于发送和接受传输数据。一旦 A 和 B 建立连接 GKPeerPickerController 的代理方法会将 A、B 两者建立的会话(GKSession)对象传递给开发人员,开发人员拿到此对象可以发送和接收数据。
其实理解了上面两个类之后,使用起来就比较简单了,下面就以一个图片发送程序来演示 GameKit 中蓝牙的使用。此程序一个客户端运行在模拟器上作为客户端 A,另一个运行在 iPhone 真机上作为客户端 B(注意 A、B 必须运行同一个程序,GameKit 蓝牙开发是不支持两个不同的应用传输数据的)。两个程序运行之后均调用 GKPeerPickerController 来发现周围蓝牙设备,一旦 A 发现了 B 之后就开始连接 B,然后 iOS 会询问用户是否接受连接,一旦接受之后就会调用 GKPeerPickerController 的 -(void)peerPickerController:(GKPeerPickerController *)picker didConnectPeer:(NSString *)peerID toSession:(GKSession *)session 代理方法,在此方法中可以获得连接的设备 id(peerID)和连接会话(session);此时可以设置会话的数据接收句柄(相当于一个代理)并保存会话以便发送数据时使用;一旦一端(假设是 A)调用会话的 sendDataToAllPeers: withDataMode: error: 方法发送数据,此时另一端(假设是 B)就会调用句柄的 - (void) receiveData:(NSData *)data fromPeer:(NSString *)peer inSession: (GKSession *)session context:(void *)context 方法,在此方法可以获得发送数据并处理。下面是程序代码:
- //
- // ViewController.m
- // GameKit
- //
- // Created by Kenshin Cui on 14/04/05.
- // Copyright (c) 2014年 cmjstudio. All rights reserved.
- //
- #import "ViewController.h"#import < GameKit / GameKit.h > @interface ViewController() < GKPeerPickerControllerDelegate,
- UIImagePickerControllerDelegate,
- UINavigationBarDelegate > @property(weak, nonatomic) IBOutlet UIImageView * imageView; //照片显示视图
- @property(strong, nonatomic) GKSession * session; //蓝牙连接会话
- @end@implementation ViewController#pragma mark - 控制器视图方法 - (void) viewDidLoad { [super viewDidLoad];
- GKPeerPickerController * pearPickerController = [[GKPeerPickerController alloc] init];
- pearPickerController.delegate = self;
- [pearPickerController show];
- }#pragma mark - UI事件 - (IBAction) selectClick: (UIBarButtonItem * ) sender {
- UIImagePickerController * imagePickerController = [[UIImagePickerController alloc] init];
- imagePickerController.delegate = self;
- [self presentViewController: imagePickerController animated: YES completion: nil];
- } - (IBAction) sendClick: (UIBarButtonItem * ) sender {
- NSData * data = UIImagePNGRepresentation(self.imageView.image);
- NSError * error = nil; [self.session sendDataToAllPeers: data withDataMode: GKSendDataReliable error: &error];
- if (error) {
- NSLog(@"发送图片过程中发生错误,错误信息:%@", error.localizedDescription);
- }
- }#pragm
来源: http://lib.csdn.net/article/embeddeddevelopment/38995