iOS開發系列--通訊錄、藍牙、內購、GameCenter、iCloud、Passbook系統服務開發彙總

来源:http://www.cnblogs.com/jgCho/archive/2016/01/26/5160810.html
-Advertisement-
Play Games

iOS開發過程中有時候難免會使用iOS內置的一些應用軟體和服務,例如QQ通訊錄、微信電話本會使用iOS的通訊錄,一些第三方軟體會在應用內發送簡訊等。今天將和大家一起學習如何使用系統應用、使用系統服務:調用系統應用使用系統服務簡訊與郵件通訊錄藍牙社交Game Center應用內購買iCloudPass...


iOS開發過程中有時候難免會使用iOS內置的一些應用軟體和服務,例如QQ通訊錄、微信電話本會使用iOS的通訊錄,一些第三方軟體會在應用內發送簡訊等。今天將和大家一起學習如何使用系統應用、使用系統服務:

  1. 調用系統應用
  2. 使用系統服務
    1. 簡訊與郵件
    2. 通訊錄
    3. 藍牙
    4. 社交
    5. Game Center
    6. 應用內購買
    7. iCloud
    8. Passbook
  3. 目 錄

系統應用

在開發某些應用時可能希望能夠調用iOS系統內置的電話、簡訊、郵件、瀏覽器應用,此時你可以直接使用UIApplication的OpenURL:方法指定特定的協議來打開不同的系統應用。常用的協議如下:

打電話:tel:或者tel://、telprompt:或telprompt://(撥打電話前有提示)

發簡訊:sms:或者sms://

發送郵件:mailto:或者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=@"[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的唯一標識,如下圖:

iOSApplication_URLTypes

然後就可以調用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使用步驟:

  1. 創建MFMessageComposeViewController對象。
  2. 設置收件人recipients、信息正文body,如果運行商支持主題和附件的話可以設置主題subject、附件attachments(可以通過canSendSubject、canSendAttachments方法判斷是否支持)
  3. 設置代理messageComposeDelegate(註意這裡不是delegate屬性,因為delegate屬性已經留給UINavigationController,MFMessageComposeViewController沒有覆蓋此屬性而是重新定義了一個代理),實現代理方法獲得發送狀態。

下麵自定義一個發送簡訊的界面演示MFMessageComposeViewController的使用:

MFMessageComposeViewController_Layout

用戶通過在此界面輸入簡訊信息點擊“發送信息”調用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

運行效果:

MFMailComposeViewController_Layout     MFMailComposeViewController

通訊錄

 

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

通訊錄的訪問步驟一般如下:

  1. 調用ABAddressBookCreateWithOptions()方法創建通訊錄對象ABAddressBookRef。
  2. 調用ABAddressBookRequestAccessWithCompletion()方法獲得用戶授權訪問通訊錄。
  3. 調用ABAddressBookCopyArrayOfAllPeople()、ABAddressBookCopyPeopleWithName()方法查詢聯繫人信息。
  4. 讀取聯繫人後如果要顯示聯繫人信息則可以調用ABRecord相關方法讀取相應的數據;如果要進行修改聯繫人信息,則可以使用對應的方法修改ABRecord信息,然後調用ABAddressBookSave()方法提交修改;如果要刪除聯繫人,則可以調用ABAddressBookRemoveRecord()方法刪除,然後調用ABAddressBookSave()提交修改操作。
  5. 也就是說如果要修改或者刪除都需要首先查詢對應的聯繫人,然後修改或刪除後提交更改。如果用戶要增加一個聯繫人則不用進行查詢,直接調用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

運行效果:

AddressBook

備註:

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

運行效果:

ABPersonViewController     ABNewPersonViewController

ABUnkownPersonViewController     ABPeoplePickerNaviagationController

註意:

為了讓大家可以更加清楚的看到幾個控制器視圖的使用,這裡並沒有結合前面的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真機上作為發現節點,並且實現兩個節點的圖片互傳。

首先看一下作為廣播節點的程式:

界面:

MultipeerConnectivity_Advertiser

點擊“開始廣播”來發佈服務,一旦有節點連接此服務就可以使用“選擇照片”來從照片庫中選取一張圖片併發送到所有已連接節點。

程式:

//
//  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 -

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 單選框和覆選框在網頁表單中應用十分廣泛,但是瀏覽器預設自帶的單選框和覆選框樣式不僅不統一,而且大多都比較簡單醜陋。本文給大家介紹了一些基於CSS3的個性化單選框和覆選框,一些選中動畫是基於jQuery的,你可以挑選喜歡的單選框和覆選框應用到自己的網頁中去,非常方便。1、jQuery超級個性化的單線框...
  • //本文支持js線上工具測試、轉載請註明出處。 UntitledDocument 點我點我 username:u p w d: 點我點我 出處:http://blog.csdn.net/xuexiaodong009/article/details/66054...
  • Node.js的介紹Node.js的是建立在Chrome的JavaScript的運行時,可方便地構建快速,可擴展的網路應用程式的平臺。Node.js使用事件驅動,非阻塞I/O模型,輕量、高效,可以完美地處理時時數據,運行在不同的設備上。1.1. 誰在用Node.js?從Node.js官方網站的企業登...
  • 詳細代碼: jQuery form插件的使用--處理server返回的JSON, XML,HTML數據 Demo 8 : jQuery form插件的使用--處理server返回的JSON, XML,HTML數據 ...
  • pull解析器:反序列化 讀取xml文件來獲取一個對象的數據 1 import java.io.FileInputStream; 2 import java.io.IOException; 3 import java.util.ArrayList; 4 import java.util.Lis...
  • 上一篇的基礎上,修改了,CardView的佈局和點擊效果總結:CardView的奇葩屬性 :app:cardPreventCornerOverlap="false" 和園角邊框重疊的效果;實現點擊事件是CardView的子佈局!註意:一個細節,沒明白。在CardView控制項的屬性下,android:...
  • 一、協議和代理模式 1.在NSObject.h頭文件中,我們可以看到// NSObject類是預設遵守協議的@interface NSObject { Class isa OBJC_ISA_AVAILABILITY;}// 往上翻看到NSObject協議的聲明@protocol NSOb...
  • 一、定義兩個巨集//鎖屏通知#define NotificationOff CFSTR("com.apple.springboard.lockcomplete")//解鎖通知#define NotificationOn CFSTR("com.apple.springboard.hasBlankedSc...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...