#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=@"[email protected]"; 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對象。
- 設置收件人recipients、信息正文body,如果運行商支持主題和附件的話可以設置主題subject、附件attachments(可以通過canSendSubject、canSendAttachments方法判斷是否支持)
- 設置代理messageComposeDelegate(注意這裏不是delegate屬性,因爲delegate屬性已經留給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的代理不是通過delegate屬性指定的而是通過messageComposeDelegate指定的。
- 可以通過幾種方式來指定發送的附件,在這個過程中請務必指定文件的後綴,否則在發送後無法正確識別文件類別(例如如果發送的是一張jpg圖片,在發送後無法正確查看圖片)。
- 無論發送成功與否代理方法-(
void
)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result
都會執行,通過代理參數中的result來獲得發送狀態。
其實只要熟悉了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
運行效果:
通訊錄
AddressBook
iOS中帶有一個Contacts應用程序來管理聯繫人,但是有些時候我們希望自己的應用能夠訪問或者修改這些信息,這個時候就要用到AddressBook.framework框架。iOS中的通訊錄是存儲在數據庫中的,由於iOS的權限設計,開發人員是不允許直接訪問通訊錄數據庫的,必須依靠AddressBook提供的標準API來實現通訊錄操作。通過AddressBook.framework開發者可以從底層去操作AddressBook.framework的所有信息,但是需要注意的是這個框架是基於C語言編寫的,無法使用ARC來管理內存,開發者需要自己管理內存。下面大致介紹一下通訊錄操作中常用的類型:
- ABAddressBookRef:代表通訊錄對象,通過該對象開發人員不用過多的關注通訊錄的存儲方式,可以直接以透明的方式去訪問、保存(在使用AddressBook.framework操作聯繫人時,所有的增加、刪除、修改後都必須執行保存操作,類似於Core Data)等。
- ABRecordRef:代表一個通用的記錄對象,可以是一條聯繫人信息,也可以是一個羣組,可以通過ABRecordGetRecordType()函數獲得具體類型。如果作爲聯繫人(事實上也經常使用它作爲聯繫人),那麼這個記錄記錄了一個完整的聯繫人信息(姓名、性別、電話、郵件等),每條記錄都有一個唯一的ID標示這條記錄(可以通過ABRecordGetRecordID()函數獲得)。
- ABPersonRef:代表聯繫人信息,很少直接使用,實際開發過程中通常會使用類型爲“kABPersonType”的ABRecordRef來表示聯繫人(由此可見ABPersonRef其實是一種類型爲“kABPersonType”的ABRecordRef)
- 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()方法查詢聯繫人信息。
- 讀取聯繫人後如果要顯示聯繫人信息則可以調用ABRecord相關方法讀取相應的數據;如果要進行修改聯繫人信息,則可以使用對應的方法修改ABRecord信息,然後調用ABAddressBookSave()方法提交修改;如果要刪除聯繫人,則可以調用ABAddressBookRemoveRecord()方法刪除,然後調用ABAddressBookSave()提交修改操作。
- 也就是說如果要修改或者刪除都需要首先查詢對應的聯繫人,然後修改或刪除後提交更改。如果用戶要增加一個聯繫人則不用進行查詢,直接調用ABPersonCreate()方法創建一個ABRecord然後設置具體的屬性,調用ABAddressBookAddRecord方法添加即可。
下面就通過一個示例演示一下如何通過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
<AddressBook/AddressBook.h>
#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()方法完成。
AddressBookUI
使用AddressBook.framework來操作通訊錄特點就是可以對通訊錄有更加精確的控制,但是缺點就是面對大量C語言API稍嫌麻煩,於是Apple官方提供了另一套框架供開發者使用,那就是AddressBookUI.framework。例如前面查看、新增、修改人員的界面這個框架就提供了現成的控制器視圖供開發者使用。下面是這個框架中提供的控制器視圖:
- ABPersonViewController
:用於查看聯繫人信息(可設置編輯)。需要設置displayedPerson屬性來設置要顯示或編輯的聯繫人。
- ABNewPersonViewController
:用於新增聯繫人信息。
- ABUnknownPersonViewController
:用於顯示一個未知聯繫人(尚未保存的聯繫人)信息。需要設置displayedPerson屬性來設置要顯示的未知聯繫人。
以上三個控制器視圖均繼承於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
<AddressBookUI/AddressBookUI.h>
@
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芯片的智能設備傳輸,因此也是目前智能家居、無線支付等熱門智能設備所推崇的技術。
GameKit
其實從名稱來看這個框架並不是專門爲了支持藍牙傳輸而設計的,它是爲遊戲設計的。而很多遊戲中會用到基於藍牙的點對點信息傳輸,因此這個框架中集成了藍牙傳輸模塊。前面也說了這個框架本身有很多限制,但是在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); } }
#pragma
mark - GKPeerPickerController代理方法
/** * 連接到某個設備 * * @param picker 藍牙點對點連接控制器 * @param peerID 連接設備藍牙傳輸ID * @param session 連接會話 */
-(
void
)peerPickerController:(GKPeerPickerController *)picker didConnectPeer:(NSString *)peerID toSession:(GKSession *)session{ self.session=session; NSLog(@
"已連接客戶端設備:%@."
,peerID);
//設置數據接收處理句柄,相當於代理,一旦數據接收完成調用它的-receiveData:fromPeer:inSession:context:方法處理數據
[self.session setDataReceiveHandler:self withContext:nil]; [picker dismiss];
//一旦連接成功關閉窗口
}
#pragma
mark - 藍牙數據接收方法 - (
void
) receiveData:(NSData *)data fromPeer:(NSString *)peer inSession: (GKSession *)session context:(
void
*)context{ UIImage *image=[UIImage imageWithData:data]; self.imageView.image=image; UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); NSLog(@
"數據發送成功!"
); }
#pragma
mark - UIImagePickerController代理方法 -(
void
)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{ self.imageView.image=[info objectForKey:UIImagePickerControllerOriginalImage]; [self dismissViewControllerAnimated:YES completion:nil]; } -(
void
)imagePickerControllerDidCancel:(UIImagePickerController *)picker{ [self dismissViewControllerAnimated:YES completion:nil]; } @end
運行效果(左側是真機,右側是模擬器,程序演示了兩個客戶端互發圖片的場景:首先是模擬器發送圖片給真機,然後真機發送圖片給模擬器):
MultipeerConnectivity
前面已經說了GameKit相關的藍牙操作類從iOS7已經全部過期,蘋果官方推薦使用MultipeerConnectivity代替。但是應該瞭解,MultipeerConnectivity.framework並不僅僅支持藍牙連接,準確的說它是一種支持Wi-Fi網絡、P2P Wi-Fi已經藍牙個人局域網的通信框架,它屏蔽了具體的連接技術,讓開發人員有統一的接口編程方法。通過MultipeerConnectivity連接的節點之間可以安全的傳遞信息、流或者其他文件資源而不必通過網絡服務。此外使用MultipeerConnectivity進行近場通信也不再侷限於同一個應用之間傳輸,而是可以在不同的應用之間進行數據傳輸(當然如果有必要的話你仍然可以選擇在一個應用程序之間傳輸)。
要了解MultipeerConnectivity的使用必須要清楚一個概念:廣播(Advertisting)和發現(Disconvering),這很類似於一種Client-Server模式。假設有兩臺設備A、B,B作爲廣播去發送自身服務,A作爲發現的客戶端。一旦A發現了B就試圖建立連接,經過B同意二者建立連接就可以相互發送數據。在使用GameKit框架時,A和B既作爲廣播又作爲發現,當然這種情況在MultipeerConnectivity中也很常見。
A.廣播
無論是作爲服務器端去廣播還是作爲客戶端去發現廣播服務,那麼兩個(或更多)不同的設備之間必須要有區分,通常情況下使用MCPeerID對象來區分一臺設備,在這個設備中可以指定顯示給對方查看的名稱(display name)。另外不管是哪一方,還必須建立一個會話MCSession用於發送和接受數據。通常情況下會在會話的-(void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state
代理方法中跟蹤會話狀態(已連接、正在連接、未連接);在會話的-(void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID代理方法中接收數據;同時還會調用會話的-(void)sendData: toPeers:withMode: error:方法去發送數據。
廣播作爲一個服務器去發佈自身服務,供周邊設備發現連接。在MultipeerConnectivity中使用MCAdvertiserAssistant來表示一個廣播,通常創建廣播時指定一個會話MCSession對象將廣播服務和會話關聯起來。一旦調用廣播的start方法周邊的設備就可以發現該廣播並可以連接到此服務。在MCSession的代理方法中可以隨時更新連接狀態,一旦建立了連接之後就可以通過MCSession的connectedPeers獲得已經連接的設備。
B.發現
前面已經說過作爲發現的客戶端同樣需要一個MCPeerID來標誌一個客戶端,同時會擁有一個MCSession來監聽連接狀態併發送、接受數據。除此之外,要發現廣播服務,客戶端就必須要隨時查找服務來連接,在MultipeerConnectivity中提供了一個控制器MCBrowserViewController來展示可連接和已連接的設備(這類似於GameKit中的GKPeerPickerController),當然如果想要自己定製一個界面來展示設備連接的情況你可以選擇自己開發一套UI界面。一旦通過MCBroserViewController選擇一個節點去連接,那麼作爲廣播的節點就會收到通知,詢問用戶是否允許連接。由於初始化MCBrowserViewController的過程已經指定了會話MCSession,所以連接過程中會隨時更新會話狀態,一旦建立了連接,就可以通過會話的connected屬性獲得已連接設備並且可以使用會話發送、接受數據。
下面用兩個不同的應用程序來演示使用MultipeerConnectivity的使用過程,其中一個應用運行在模擬器中作爲廣播節點,另一個運行在iPhone真機上作爲發現節點,並且實現兩個節點的圖片互傳。
首先看一下作爲廣播節點的程序:
界面:
點擊“開始廣播”來發布服務,一旦有節點連接此服務就可以使用“選擇照片”來從照片庫中選取一張圖片併發送到所有已連接節點。
程序:
//
// ViewController.m
// MultipeerConnectivity_Advertiser
//
// Created by Kenshin Cui on 14/04/05.
// Copyright (c) 2015年 cmjstudio. All rights reserved.
//
#import
"ViewController.h"
#import
<MultipeerConnectivity/MultipeerConnectivity.h>
@
interface
ViewController ()<MCSessionDelegate,MCAdvertiserAssistantDelegate, UIImagePickerControllerDelegate,UINavigationControllerDelegate> @
property
(strong,nonatomic) MCSession *session; @
property
(strong,nonatomic) MCAdvertiserAssistant *advertiserAssistant; @
property
(strong,nonatomic) UIImagePickerController *imagePickerController; @
property
(weak, nonatomic) IBOutlet UIImageView *photo; @end @implementation ViewController
#pragma
mark - 控制器視圖事件 - (
void
)viewDidLoad { [super viewDidLoad];
//創建節點,displayName是用於提供給周邊設備查看和區分此服務的
MCPeerID *peerID=[[MCPeerID alloc]initWithDisplayName:@
"KenshinCui_Advertiser"
]; _session=[[MCSession alloc]initWithPeer:peerID]; _session.
delegate
=self;
//創建廣播
_advertiserAssistant=[[MCAdvertiserAssistant alloc]initWithServiceType:@
"cmj-stream"
discoveryInfo:nil session:_session]; _advertiserAssistant.
delegate
=self; }
#pragma
mark - UI事件 - (IBAction)advertiserClick:(UIBarButtonItem *)sender {
//開始廣播
[self.advertiserAssistant start]; } - (IBAction)selectClick:(UIBarButtonItem *)sender { _imagePickerController=[[UIImagePickerController alloc]init]; _imagePickerController.
delegate
=self; [self presentViewController:_imagePickerController animated:YES completion:nil]; }
#pragma
mark - MCSession代理方法 -(
void
)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state{ NSLog(@
"didChangeState"
);
switch
(state) {
case
MCSessionStateConnected: NSLog(@
"連接成功."
);
break
;
case
MCSessionStateConnecting: NSLog(@
"正在連接..."
);
break
;
default
: NSLog(@
"連接失敗."
);
break
; } }
//接收數據
-(
void
)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{ NSLog(@
"開始接收數據..."
); UIImage *image=[UIImage imageWithData:data]; [self.photo setImage:image];
//保存到相冊
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); }
#pragma
mark - MCAdvertiserAssistant代理方法
#pragma
mark - UIImagePickerController代理方法 -(
void
)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{ UIImage *image=[info objectForKey:UIImagePickerControllerOriginalImage]; [self.photo setImage:image];
//發送數據給所有已連接設備
NSError *error=nil; [self.session sendData:UIImagePNGRepresentation(image) toPeers:[self.session connectedPeers] withMode:MCSessionSendDataUnreliable error:&error]; NSLog(@
"開始發送數據..."
);
if
(error) { NSLog(@
"發送數據過程中發生錯誤,錯誤信息:%@"
,error.localizedDescription); } [self.imagePickerController dismissViewControllerAnimated:YES completion:nil]; } -(
void
)imagePickerControllerDidCancel:(UIImagePickerController *)picker{ [self.imagePickerController dismissViewControllerAnimated:YES completion:nil]; } @end
再看一下作爲發現節點的程序:
界面:
點擊“查找設備”瀏覽可用服務,點擊服務建立連接;一旦建立了連接之後就可以點擊“選擇照片”會從照片庫中選擇一張圖片併發送給已連接的節點。
程序:
//
// ViewController.m
// MultipeerConnectivity
//
// Created by Kenshin Cui on 14/04/05.
// Copyright (c) 2015年 cmjstudio. All rights reserved.
//
#import
"ViewController.h"
#import
<MultipeerConnectivity/MultipeerConnectivity.h>
@
interface
ViewController ()<MCSessionDelegate,MCBrowserViewControllerDelegate,UIImagePickerControllerDelegate,UINavigationControllerDelegate> @
property
(strong,nonatomic) MCSession *session; @
property
(strong,nonatomic) MCBrowserViewController *browserController; @
property
(strong,nonatomic) UIImagePickerController *imagePickerController; @
property
(weak, nonatomic) IBOutlet UIImageView *photo; @end @implementation ViewController
#pragma
mark - 控制器視圖事件 - (
void
)viewDidLoad { [super viewDidLoad];
//創建節點
MCPeerID *peerID=[[MCPeerID alloc]initWithDisplayName:@
"KenshinCui"
];
//創建會話
_session=[[MCSession alloc]initWithPeer:peerID]; _session.
delegate
=self; }
#pragma
mark- UI事件 - (IBAction)browserClick:(UIBarButtonItem *)sender { _browserController=[[MCBrowserViewController alloc]initWithServiceType:@
"cmj-stream"
session:self.session]; _browserController.
delegate
=self; [self presentViewController:_browserController animated:YES completion:nil]; } - (IBAction)selectClick:(UIBarButtonItem *)sender { _imagePickerController=[[UIImagePickerController alloc]init]; _imagePickerController.
delegate
=self; [self presentViewController:_imagePickerController animated:YES completion:nil]; }
#pragma
mark - MCBrowserViewController代理方法 -(
void
)browserViewControllerDidFinish:(MCBrowserViewController *)browserViewController{ NSLog(@
"已選擇"
); [self.browserController dismissViewControllerAnimated:YES completion:nil]; } -(
void
)browserViewControllerWasCancelled:(MCBrowserViewController *)browserViewController{ NSLog(@
"取消瀏覽."
); [self.browserController dismissViewControllerAnimated:YES completion:nil]; }
#pragma
mark - MCSession代理方法 -(
void
)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state{ NSLog(@
"didChangeState"
);
switch
(state) {
case
MCSessionStateConnected: NSLog(@
"連接成功."
); [self.browserController dismissViewControllerAnimated:YES completion:nil];
break
;
case
MCSessionStateConnecting: NSLog(@
"正在連接..."
);
break
;
default
: NSLog(@
"連接失敗."
);
break
; } }
//接收數據
-(
void
)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{ NSLog(@
"開始接收數據..."
); UIImage *image=[UIImage imageWithData:data]; [self.photo setImage:image];
//保存到相冊
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); }
#pragma
mark - UIImagePickerController代理方法 -(
void
)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{ UIImage *image=[info objectForKey:UIImagePickerControllerOriginalImage]; [self.photo setImage:image];
//發送數據給所有已連接設備
NSError *error=nil; [self.session sendData:UIImagePNGRepresentation(image) toPeers:[self.session connectedPeers] withMode:MCSessionSendDataUnreliable error:&error]; NSLog(@
"開始發送數據..."
);
if
(error) { NSLog(@
"發送數據過程中發生錯誤,錯誤信息:%@"
,error.localizedDescription); } [self.imagePickerController dismissViewControllerAnimated:YES completion:nil]; } -(
void
)imagePickerControllerDidCancel:(UIImagePickerController *)picker{ [self.imagePickerController dismissViewControllerAnimated:YES completion:nil]; } @end
在兩個程序中無論是MCBrowserViewController還是MCAdvertiserAssistant在初始化的時候都指定了一個服務類型“cmj-photo”,這是唯一標識一個服務類型的標記,可以按照官方的要求命名,應該儘可能表達服務的作用。需要特別指出的是,如果廣播命名爲“cmj-photo”那麼發現節點只有在MCBrowserViewController中指定爲“cmj-photo”才能發現此服務。
運行效果:
CoreBluetooth
無論是GameKit還是MultipeerConnectivity,都只能在iOS設備之間進行數據傳輸,這就大大降低了藍牙的使用範圍,於是從iOS6開始蘋果推出了CoreBluetooth.framework,這個框架最大的特點就是完全基於BLE4.0標準並且支持非iOS設備。當前BLE應用相當廣泛,不再僅僅是兩個設備之間的數據傳輸,它還有很多其他應用市場,例如室內定位、無線支付、智能家居等等,這也使得CoreBluetooth成爲當前最熱門的藍牙技術。
CoreBluetooth設計同樣也是類似於客戶端-服務器端的設計,作爲服務器端的設備稱爲外圍設備(Peripheral),作爲客戶端的設備叫做中央設備(Central),CoreBlueTooth整個框架就是基於這兩個概念來設計的。
外圍設備和中央設備在CoreBluetooth中使用CBPeripheralManager和CBCentralManager表示。
CBPeripheralManager:外圍設備通常用於發佈服務、生成數據、保存數據。外圍設備發佈並廣播服務,告訴周圍的中央設備它的可用服務和特徵。
CBCentralManager:中央設備使用外圍設備的數據。中央設備掃描到外圍設備後會就會試圖建立連接,一旦連接成功就可以使用這些服務和特徵。
外圍設備和中央設備之間交互的橋樑是服務(CBService)和特徵(CBCharacteristic),二者都有一個唯一的標識UUID(CBUUID類型)來唯一確定一個服務或者特徵,每個服務可以擁有多個特徵,下面是他們之間的關係:
一臺iOS設備(注意iPhone4以下設備不支持BLE,另外iOS7.0、8.0模擬器也無法模擬BLE)既可以作爲外圍設備又可以作爲中央設備,但是不能同時即是外圍設備又是中央設備,同時注意建立連接的過程不需要用戶手動選擇允許,這一點和前面兩個框架是不同的,這主要是因爲BLE應用場景不再侷限於兩臺設備之間資源共享了。
A.外圍設備
創建一個外圍設備通常分爲以下幾個步驟:
- 創建外圍設備CBPeripheralManager對象並指定代理。
- 創建特徵CBCharacteristic、服務CBSerivce並添加到外圍設備
- 外圍設備開始廣播服務(startAdvertisting:)。
- 和中央設備CBCentral進行交互。
下面是簡單的程序示例,程序有兩個按鈕“啓動”和“更新”,點擊啓動按鈕則創建外圍設備、添加服務和特徵並開始廣播,一旦發現有中央設備連接並訂閱了此服務的特徵則通過更新按鈕更新特徵數據,此時已訂閱的中央設備就會收到更新數據。
界面設計:
程序設計:
//
// ViewController.m
// PeripheralApp
//
// Created by Kenshin Cui on 14/04/05.
// Copyright (c) 2014年 cmjstudio. All rights reserved.
// 外圍設備(周邊設備)
#import
"ViewController.h"
#import
<CoreBluetooth/CoreBluetooth.h>
#define
kPeripheralName @
"Kenshin Cui's Device"
//外圍設備名稱
#define
kServiceUUID @
"C4FB2349-72FE-4CA2-94D6-1F3CB16331EE"
//服務的UUID
#define
kCharacteristicUUID @
"6A3E4B28-522D-4B3B-82A9-D5E2004534FC"
//特徵的UUID
@
interface
ViewController ()<CBPeripheralManagerDelegate> @
property
(strong,nonatomic) CBPeripheralManager *peripheralManager;
//外圍設備管理器
@
property
(strong,nonatomic) NSMutableArray *centralM;
//訂閱此外圍設備特徵的中心設備
@
property
(strong,nonatomic) CBMutableCharacteristic *characteristicM;
//特徵
@
property
(weak, nonatomic) IBOutlet UITextView *log;
//日誌記錄
@end @implementation ViewController
#pragma
mark - 視圖控制器方法 - (
void
)viewDidLoad { [super viewDidLoad]; }
#pragma
mark - UI事件
//創建外圍設備
- (IBAction)startClick:(UIBarButtonItem *)sender { _peripheralManager=[[CBPeripheralManager alloc]initWithDelegate:self queue:nil]; }
//更新數據
- (IBAction)transferClick:(UIBarButtonItem *)sender { [self updateCharacteristicValue]; }
#pragma
mark - CBPeripheralManager代理方法
//外圍設備狀態發生變化後調用
-(
void
)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
switch
(peripheral.state) {
case
CBPeripheralManagerStatePoweredOn: NSLog(@
"BLE已打開."
); [self writeToLog:@
"BLE已打開."
];
//添加服務
[self setupService];
break
;
default
: NSLog(@
"此設備不支持BLE或未打開藍牙功能,無法作爲外圍設備."
); [self writeToLog:@
"此設備不支持BLE或未打開藍牙功能,無法作爲外圍設備."
];
break
; } }
//外圍設備添加服務後調用
-(
void
)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{
if
(error) { NSLog(@
"向外圍設備添加服務失敗,錯誤詳情:%@"
,error.localizedDescription); [self writeToLog:[NSString stringWithFormat:@
"向外圍設備添加服務失敗,錯誤詳情:%@"
,error.localizedDescription]];
return
; }
//添加服務後開始廣播
NSDictionary *dic=@{CBAdvertisementDataLocalNameKey:kPeripheralName};
//廣播設置
[self.peripheralManager startAdvertising:dic];
//開始廣播
NSLog(@
"向外圍設備添加了服務並開始廣播..."
); [self writeToLog:@
"向外圍設備添加了服務並開始廣播..."
]; } -(
void
)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error{
if
(error) { NSLog(@
"啓動廣播過程中發生錯誤,錯誤信息:%@"
,error.localizedDescription); [self writeToLog:[NSString stringWithFormat:@
"啓動廣播過程中發生錯誤,錯誤信息:%@"
,error.localizedDescription]];
return
; } NSLog(@
"啓動廣播..."
); [self writeToLog:@
"啓動廣播..."
]; }
//訂閱特徵
-(
void
)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{ NSLog(@
"中心設備:%@ 已訂閱特徵:%@."
,central,characteristic); [self writeToLog:[NSString stringWithFormat:@
"中心設備:%@ 已訂閱特徵:%@."
,central.identifier.UUIDString,characteristic.UUID]];
//發現中心設備並存儲
if
(![self.centralM containsObject:central]) { [self.centralM addObject:central]; }
/*中心設備訂閱成功後外圍設備可以更新特徵值發送到中心設備,一旦更新特徵值將會觸發中心設備的代理方法: -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error */ // [self updateCharacteristicValue];
}
//取消訂閱特徵
-(
void
)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic{ NSLog(@
"didUnsubscribeFromCharacteristic"
); } -(
void
)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(CBATTRequest *)request{ NSLog(@
"didReceiveWriteRequests"
); } -(
void
)peripheralManager:(CBPeripheralManager *)peripheral willRestoreState:(NSDictionary *)dict{ NSLog(@
"willRestoreState"
); }
#pragma
mark -屬性 -(NSMutableArray *)centralM{
if
(!_centralM) { _centralM=[NSMutableArray
array
]; }
return
_centralM; }
#pragma
mark - 私有方法
//創建特徵、服務並添加服務到外圍設備
-(
void
)setupService{
/*1.創建特徵*/ //創建特徵的UUID對象
CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
//特徵值 // NSString *valueStr=kPeripheralName; // NSData *value=[valueStr dataUsingEncoding:NSUTF8StringEncoding]; //創建特徵 /** 參數 * uuid:特徵標識 * properties:特徵的屬性,例如:可通知、可寫、可讀等 * value:特徵值 * permissions:特徵的權限 */
CBMutableCharacteristic *characteristicM=[[CBMutableCharacteristic alloc]initWithType:characteristicUUID properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable]; self.characteristicM=characteristicM;
// CBMutableCharacteristic *characteristicM=[[CBMutableCharacteristic alloc]initWithType:characteristicUUID properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable]; // characteristicM.value=value; /*創建服務並且設置特徵*/ //創建服務UUID對象
CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID];
//創建服務
CBMutableService *serviceM=[[CBMutableService alloc]initWithType:serviceUUID primary:YES];
//設置服務的特徵
[serviceM setCharacteristics:@[characteristicM]];
/*將服務添加到外圍設備*/
[self.peripheralManager addService:serviceM]; }
//更新特徵值
-(
void
)updateCharacteristicValue{
//特徵值
NSString *valueStr=[NSString stringWithFormat:@
"%@ --%@"
,kPeripheralName,[NSDate date]]; NSData *value=[valueStr dataUsingEncoding:NSUTF8StringEncoding];
//更新特徵值
[self.peripheralManager updateValue:value forCharacteristic:self.characteristicM onSubscribedCentrals:nil]; [self writeToLog:[NSString stringWithFormat:@
"更新特徵值:%@"
,valueStr]]; }
/** * 記錄日誌 * * @param info 日誌信息 */
-(
void
)writeToLog:(NSString *)info{ self.log.text=[NSString stringWithFormat:@
"%@\r\n%@"
,self.log.text,info]; } @end
上面程序運行的流程如下(圖中藍色代表外圍設備操作,綠色部分表示中央設備操作):
B.中央設備
中央設備的創建一般可以分爲如下幾個步驟:
- 創建中央設備管理對象CBCentralManager並指定代理。
- 掃描外圍設備,一般發現可用外圍設備則連接並保存外圍設備。
- 查找外圍設備服務和特徵,查找到可用特徵則讀取特徵數據。
下面是一個簡單的中央服務器端實現,點擊“啓動”按鈕則開始掃描周圍的外圍設備,一旦發現了可用的外圍設備則建立連接並設置外圍設備的代理,之後開始查找其服務和特徵。一旦外圍設備的特徵值做了更新,則可以在代理方法中讀取更新後的特徵值。
界面設計:
程序設計:
//
// ViewController.m
// CentralApp
//
// Created by Kenshin Cui on 14/04/05.
// Copyright (c) 2014年 cmjstudio. All rights reserved.
// 中心設備
#import
"ViewController.h"
#import
<CoreBluetooth/CoreBluetooth.h>
#define
kServiceUUID @
"C4FB2349-72FE-4CA2-94D6-1F3CB16331EE"
//服務的UUID
#define
kCharacteristicUUID @
"6A3E4B28-522D-4B3B-82A9-D5E2004534FC"
//特徵的UUID
@
interface
ViewController ()<CBCentralManagerDelegate,CBPeripheralDelegate> @
property
(strong,nonatomic) CBCentralManager *centralManager;
//中心設備管理器
@
property
(strong,nonatomic) NSMutableArray *peripherals;
//連接的外圍設備
@
property
(weak, nonatomic) IBOutlet UITextView *log;
//日誌記錄
@end @implementation ViewController
#pragma
mark - 控制器視圖事件 - (
void
)viewDidLoad { [super viewDidLoad]; }
#pragma
mark - UI事件 - (IBAction)startClick:(UIBarButtonItem *)sender {
//創建中心設備管理器並設置當前控制器視圖爲代理
_centralManager=[[CBCentralManager alloc]initWithDelegate:self queue:nil]; }
#pragma
mark - CBCentralManager代理方法
//中心服務器狀態更新後
-(
void
)centralManagerDidUpdateState:(CBCentralManager *)central{
switch
(central.state) {
case
CBPeripheralManagerStatePoweredOn: NSLog(@
"BLE已打開."
); [self writeToLog:@
"BLE已打開."
];
//掃描外圍設備 // [central scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES}];
[central scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES}];
break
;
default
: NSLog(@
"此設備不支持BLE或未打開藍牙功能,無法作爲外圍設備."
); [self writeToLog:@
"此設備不支持BLE或未打開藍牙功能,無法作爲外圍設備."
];
break
; } }
/** * 發現外圍設備 * * @param central 中心設備 * @param peripheral 外圍設備 * @param advertisementData 特徵數據 * @param RSSI 信號質量(信號強度) */
-(
void
)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{ NSLog(@
"發現外圍設備..."
); [self writeToLog:@
"發現外圍設備..."
];
//停止掃描
[self.centralManager stopScan];
//連接外圍設備
if
(peripheral) {
//添加保存外圍設備,注意如果這裏不保存外圍設備(或者說peripheral沒有一個強引用,無法到達連接成功(或失敗)的代理方法,因爲在此方法調用完就會被銷燬
if
(![self.peripherals containsObject:peripheral]){ [self.peripherals addObject:peripheral]; } NSLog(@
"開始連接外圍設備..."
); [self writeToLog:@
"開始連接外圍設備..."
]; [self.centralManager connectPeripheral:peripheral options:nil]; } }
//連接到外圍設備
-(
void
)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{ NSLog(@
"連接外圍設備成功!"
); [self writeToLog:@
"連接外圍設備成功!"
];
//設置外圍設備的代理爲當前視圖控制器
peripheral.
delegate
=self;
//外圍設備開始尋找服務
[peripheral discoverServices:@[[CBUUID UUIDWithString:kServiceUUID]]]; }
//連接外圍設備失敗
-(
void
)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{ NSLog(@
"連接外圍設備失敗!"
); [self writeToLog:@
"連接外圍設備失敗!"
]; }
#pragma
mark - CBPeripheral 代理方法
//外圍設備尋找到服務後
-(
void
)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{ NSLog(@
"已發現可用服務..."
); [self writeToLog:@
"已發現可用服務..."
];
if
(error){ NSLog(@
"外圍設備尋找服務過程中發生錯誤,錯誤信息:%@"
,error.localizedDescription); [self writeToLog:[NSString stringWithFormat:@
"外圍設備尋找服務過程中發生錯誤,錯誤信息:%@"
,error.localizedDescription]]; }
//遍歷查找到的服務
CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID]; CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
for
(CBService *service in peripheral.services) {
if
([service.UUID isEqual:serviceUUID]){
//外圍設備查找指定服務中的特徵
[peripheral discoverCharacteristics:@[characteristicUUID] forService:service]; } } }
//外圍設備尋找到特徵後
-(
void
)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{ NSLog(@
"已發現可用特徵..."
); [self writeToLog:@
"已發現可用特徵..."
];
if
(error) { NSLog(@
"外圍設備尋找特徵過程中發生錯誤,錯誤信息:%@"
,error.localizedDescription); [self writeToLog:[NSString stringWithFormat:@
"外圍設備尋找特徵過程中發生錯誤,錯誤信息:%@"
,error.localizedDescription]]; }
//遍歷服務中的特徵
CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID]; CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
if
([service.UUID isEqual:serviceUUID]) {
for
(CBCharacteristic *characteristic in service.characteristics) {
if
([characteristic.UUID isEqual:characteristicUUID]) {
//情景一:通知 /*找到特徵後設置外圍設備爲已通知狀態(訂閱特徵): *1.調用此方法會觸發代理方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error *2.調用此方法會觸發外圍設備的訂閱代理方法 */
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
//情景二:讀取 // [peripheral readValueForCharacteristic:characteristic]; // if(characteristic.value){ // NSString *value=[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding]; // NSLog(@"讀取到特徵值:%@",value); // }
} } } }
//特徵值被更新後
-(
void
)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{ NSLog(@
"收到特徵更新通知..."
); [self writeToLog:@
"收到特徵更新通知..."
];
if
(error) { NSLog(@
"更新通知狀態時發生錯誤,錯誤信息:%@"
,error.localizedDescription); }
//給特徵值設置新的值
CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
if
([characteristic.UUID isEqual:characteristicUUID]) {
if
(characteristic.isNotifying) {
if
(characteristic.properties==CBCharacteristicPropertyNotify) { NSLog(@
"已訂閱特徵通知."
); [self writeToLog:@
"已訂閱特徵通知."
];
return
; }
else if
(characteristic.properties ==CBCharacteristicPropertyRead) {
//從外圍設備讀取新值,調用此方法會觸發代理方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
[peripheral readValueForCharacteristic:characteristic]; } }
else
{ NSLog(@
"停止已停止."
); [self writeToLog:@
"停止已停止."
];
//取消連接
[self.centralManager cancelPeripheralConnection:peripheral]; } } }
//更新特徵值後(調用readValueForCharacteristic:方法或者外圍設備在訂閱後更新特徵值都會調用此代理方法)
-(
void
)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if
(error) { NSLog(@
"更新特徵值時發生錯誤,錯誤信息:%@"
,error.localizedDescription); [self writeToLog:[NSString stringWithFormat:@
"更新特徵值時發生錯誤,錯誤信息:%@"
,error.localizedDescription]];
return
; }
if
(characteristic.value) { NSString *value=[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding]; NSLog(@
"讀取到特徵值:%@"
,value); [self writeToLog:[NSString stringWithFormat:@
"讀取到特徵值:%@"
,value]]; }
else
{ NSLog(@
"未發現特徵值."
); [self writeToLog:@
"未發現特徵值."
]; } }
#pragma
mark - 屬性 -(NSMutableArray *)peripherals{
if
(!_peripherals){ _peripherals=[NSMutableArray
array
]; }
return
_peripherals; }
#pragma
mark - 私有方法
/** * 記錄日誌 * * @param info 日誌信息 */
-(
void
)writeToLog:(NSString *)info{ self.log.text=[NSString stringWithFormat:@
"%@\r\n%@"
,self.log.text,info]; } @end
上面程序運行的流程圖如下:
有了上面兩個程序就可以分別運行在兩臺支持BLE的iOS設備上,當兩個應用建立連接後,一旦外圍設備更新特徵之後,中央設備就可以立即獲取到更新後的值。需要強調的是使用CoreBluetooth開發的應用不僅僅可以和其他iOS設備進行藍牙通信,還可以同其他第三方遵循BLE規範的設備進行藍牙通訊,這裏就不再贅述。
注意:本節部分圖片來自於互聯網,版權歸原作者所有。
社交
Social
現在很多應用都內置“社交分享”功能,可以將看到的新聞、博客、廣告等內容分享到微博、微信、QQ、空間等,其實從iOS6.0開始蘋果官方就內置了Social.framework專門來實現社交分享功能,利用這個框架開發者只需要幾句代碼就可以實現內容分享。下面就以一個分享到新浪微博的功能爲例來演示Social框架的應用,整個過程分爲:創建內容編輯控制器,設置分享內容(文本內容、圖片、超鏈接等),設置發送(或取消)後的回調事件,展示控制器。
程序代碼:
//
// ViewController.m
// Social
//
// Created by Kenshin Cui on 14/04/05.
// Copyright (c) 2015年 cmjstudio. All rights reserved.
//
#import
"ViewController.h"
#import
<Social/Social.h>
@
interface
ViewController () @end @implementation ViewController
#pragma
mark - 控制器視圖事件 - (
void
)viewDidLoad { [super viewDidLoad]; }
#pragma
mark - UI事件 - (IBAction)shareClick:(UIBarButtonItem *)sender { [self shareToSina]; }
#pragma
mark - 私有方法 -(
void
)shareToSina{
//檢查新浪微博服務是否可用
if
(![SLComposeViewController isAvailableForServiceType:SLServiceTypeSinaWeibo]){ NSLog(@
"新浪微博服務不可用."
);
return
; }
//初始化內容編寫控制器,注意這裏指定分享類型爲新浪微博
SLComposeViewController *composeController=[SLComposeViewController composeViewControllerForServiceType:SLServiceTypeSinaWeibo];
//設置默認信息
[composeController setInitialText:@
"Kenshin Cui's Blog..."
];
//添加圖片
[composeController addImage:[UIImage imageNamed:@
"stevenChow"
]];
//添加連接
[composeController addURL:[NSURL URLWithString:@
"http://www.cnblogs.com/kenshincui"
]];
//設置發送完成後的回調事件
__block SLComposeViewController *composeControllerForBlock=composeController; composeController.completionHandler=^(SLComposeViewControllerResult result){
if
(result==SLComposeViewControllerResultDone) { NSLog(@
"開始發送..."
); } [composeControllerForBlock dismissViewControllerAnimated:YES completion:nil]; };
//顯示編輯視圖
[self presentViewController:composeController animated:YES completion:nil]; } @end
運行效果:
發送成功之後:
在這個過程中開發人員不需要知道新浪微博的更多分享細節,Social框架中已經統一了分享的接口,你可以通過ServiceType設置是分享到Facebook、Twitter、新浪微博、騰訊微博,而不關心具體的細節實現。那麼當運行上面的示例時它是怎麼知道用哪個賬戶來發送微博呢?其實在iOS的設置中有專門設置Facebook、Twitter、微博的地方:
必須首先在這裏設置微博賬戶才能完成上面的發送,不然Social框架也不可能知道具體使用哪個賬戶來發送。
第三方框架
當然,通過上面的設置界面應該可以看到,蘋果官方默認支持的分享並不太多,特別是對於國內的應用只支持新浪微博和騰訊微博(事實上從iOS7蘋果才考慮支持騰訊微博),那麼如果要分享到微信、人人、開心等等國內較爲知名的社交網絡怎麼辦呢?目前最好的選擇就是使用第三方框架,因爲如果要自己實現各個應用的接口還是比較複雜的。當前使用較多的就是友盟社會化組件
、ShareSDK,而且現在百度也出了社會化分享組件。今天無法對所有組件都進行一一介紹,這裏就以友盟社交化組件爲例簡單做一下介紹:
- 註冊友盟賬號並新建應用獲得AppKey。
- 下載友盟SDK並將下載的文件放到項目中(注意下載的過程中可以選擇所需要的分享服務)。
- 在應用程序中設置友盟的AppKey。
- 分享時調用presentSnsIconSheetView: appKey: shareText: shareImage: shareToSnsNames: delegate:方法或者presentSnsController: appKey: shareText: shareImage: shareToSnsNames: delegate:方法顯示分享列表(注意這個過程中要使用某些服務需要到對應的平臺去申請並對應擴展框架進行設置,否則分享列表中不會顯示對應的分享按鈕)。
下面是一個簡單的示例:
//
// ViewController.m
// Social_UM
//
// Created by Kenshin Cui on 14/04/05.
// Copyright (c) 2015年 cmjstudio. All rights reserved.
//
#import
"ViewController.h"
#import
"UMSocial.h"
#import
"UMSocialWechatHandler.h"
@
interface
ViewController ()<UMSocialUIDelegate> @end @implementation ViewController
#pragma
mark - 控制器視圖事件 - (
void
)viewDidLoad { [super viewDidLoad]; }
#pragma
mark - UI事件 - (IBAction)shareClick:(UIBarButtonItem *)sender {
//設置微信AppId、appSecret,分享url // [UMSocialWechatHandler setWXAppId:@"wx30dbea5d5a258ed3" appSecret:@"cd36a9829e4b49a0dcac7b4162da5a5" url:@"http://www.cmj.com/social-UM"]; //微信好友、微信朋友圈、微信收藏、QQ空間、QQ好友、來往好友等都必須經過各自的平臺集成否則不會出現在分享列表,例如上面是設置微信的AppId和appSecret
[UMSocialSnsService presentSnsIconSheetView:self appKey:@
"54aa0a0afd98c5209f000efa"
shareText:@
"Kenshin Cui's Blog..."
shareImage:[UIImage imageNamed:@
"stevenChow"
] shareToSnsNames:@[UMShareToSina,UMShareToTencent,UMShareToRenren,UMShareToDouban]
delegate
:self]; }
#pragma
mark - UMSocialSnsService代理
//分享完成
-(
void
)didFinishGetUMSocialDataInViewController:(UMSocialResponseEntity *)response{
//分享成功
if
(response.responseCode==UMSResponseCodeSuccess){ NSLog(@
"分享成功"
); } } @end
運行效果:
注意:在第一次使用某個分享服務是需要輸入相應的賬號獲得授權才能分享。
GameCenter
Game Center是由蘋果發佈的在線多人遊戲社交網絡,通過它遊戲玩家可以邀請好友進行多人遊戲,它也會記錄玩家的成績並在排行榜中展示,同時玩家每經過一定的階段會獲得不同的成就。這裏就簡單介紹一下如何在自己的應用中集成Game Center服務來讓用戶獲得積分、成就以及查看遊戲排行和已獲得成就。
由於Game Center是蘋果推出的一項重要服務,蘋果官方對於它的控制相當嚴格,因此使用Game Center之前必須要做許多準備工作。通常需要經過以下幾個步驟(下面的準備工作主要是針對真機的,模擬器省略Provisioning Profile配置過程):
學習ios 重要還是要理清楚思路 在做或者看老師代碼的時候 自己多想想爲什麼 不要自己看着就抄 另外還是要推薦一下 藍懿IOS這個培訓機構 和劉國斌老師劉國斌老師還是很有名氣的,聽朋友說劉老師成立了藍懿iOS,,老師講課方式很獨特,能夠儘量讓每個人都能弄明白,有的比較難懂的地方,如果有的地方還是不懂得話,老師會換個其它方法再講解,這對於我們這些學習iOS的同學是非常好的,多種方式的講解會理解得更全面,這個必須得給個贊,嘻嘻,還有就是這裏的學習環境很好,很安靜,可以很安心的學習,安靜的環境是學習的基礎,小班講課,每個班20幾個學生,學習氛圍非常好,每天都學到9點多才離開教室,練習的時間很充裕,而且如果在練習的過程中有什麼困難,隨時可以向老師求助,不像其它機構,通過視頻教學,有的甚至學完之後都看不到講師本人,問點問題都不方便,這就是藍懿與其它機構的區別,相信在劉國斌老師的細心指導下,每個藍懿學員都能找到滿意的工作,加油!
寫博客第八十九天;
QQ:565803433