究竟是香港(Hong Kong)人

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=@"kenshin@hotmail.com";
    NSString *url=[NSString stringWithFormat:@"mailto://%@",mailAddress];
    [self openUrl:url];
}

//浏览网页
- (IBAction)browserClick:(UIButton *)sender {
    NSString *url=@"http://www.cnblogs.com/kenshincui";
    [self openUrl:url];
}

#pragma mark - 私有方法
-(void)openUrl:(NSString *)urlStr{
    //注意url中包含协议名称,iOS根据协议确定调用哪个应用,例如发送邮件是“sms://”其中“//”可以省略写成“sms:”(其他协议也是如此)
    NSURL *url=[NSURL URLWithString:urlStr];
    UIApplication *application=[UIApplication sharedApplication];
    if(![application canOpenURL:url]){
        NSLog(@"无法打开\"%@\",请确保此应用已经正确安装.",url);
        return;
    }
    [[UIApplication sharedApplication] openURL:url];
}

@end

毫不费劲察觉当openURL:方法只要指定一个URL
Schame并且已经安装了对应的应用程序就足以打开此选择。当然,要是是投机开发的行使也得以调用openURL方法来开辟。假诺你现在付出了一个应用A,倘诺用户机器上一度设置了此采用,并且在应用B中希望可以一向打开A。那么首先须求保障应用A已经配备了Url
Types,具体方法就是在plist文件中添加URL types节点并配备URL
Schemas作为具体磋商,配置URL identifier作为那些URL的唯一标识,如下图:

图片 1

下一场就足以调用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的利用:

图片 2

用户通过在此界面输入短信新闻点击“发送信息”调用MFMessageComposeViewController界面来展示或更为编辑音讯,点击MFMessageComposeViewController中的“发送”来落成短信发送工作,当然用户也恐怕点击“废除”按钮回到前一个短信编辑页面。

图片 3

贯彻代码:

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

运作效果:

图片 4     图片 5

近几年来,好像每个赛季,都有北京滩的恩恩怨怨情仇点缀。最雅观的,自然是二〇一五年足协杯季前赛,新加坡申花对香江上港的德比战。

通讯录

 

90分钟健康时间里,双方打成平手,孔卡武磊莫雷诺等人齐声进献了四个可以的进球,最后点球决战。

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

运作效果:

图片 6

备注:

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

运行效果:

图片 7     图片 8

图片 9     图片 10

注意:

为了让我们可以更进一步清楚的收看多少个控制器视图的选取,那里并不曾结合前边的UITableViewController来使用,事实上大家结合前边UITableViewController能够做一个完善的通讯录应用。

后天中午,又是“圣鲁迅公园”,又是申花对上港,又是足协杯,又是一场恶战。

蓝牙

乘机蓝牙低功耗技术BLE(Bluetooth Low
Energy)的提升,蓝牙技巧正在一步步老谋深算,近年来的大多数移动设备都布署有蓝牙4.0,相比较以前的蓝牙技术耗电量大大下落。从iOS的发展史也简单看出苹果目前对蓝牙技术也是尤为关心,例如苹果于二〇一三年2月公布的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事先的很多蓝牙传输都是根据此框架的,所以有必不可少对它进行问询。Game基特中的蓝牙使用安顿很简单,并没有给开发者留有太多的扑朔迷离接口,而半数以上老是细节开发者是不需求关爱的。Game基特(Kit)中提供了多少个至关首要类来操作蓝牙连接:

GKPeerPickerController:蓝牙查找、连接用的视图控制器,平时情形下行使程序A打开后会调用此控制器的show方法来浮现一个蓝牙查找的视图,一旦发觉了另一个一致在检索蓝牙连接的客户客户端B就会油可是生在视图列表中,此时如果用户点击连接B,B客户端就会精通用户是否允许A连接B,即便允许后A和B之间建立一个蓝牙连接。

GKSession:连接会话,首要用于发送和经受传输数据。一旦A和B建立连接GKPeerPickerController的代办方法会将A、B两者建立的对话(GKSession)对象传递给开发人士,开发人士获得此目的可以发送和接收数据。

骨子里通晓了上面八个类之后,使用起来就比较简单了,上边就以一个图片发送程序来演示GameKit中蓝牙的应用。此程序一个客户端运行在模拟器上作为客户端A,另一个周转在OPPO真机上作为客户端B(注意A、B必须运行同一个程序,Game基特蓝牙开发是不帮助三个分化的行使传输数据的)。多少个程序运行之后均调用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

运行效果(左侧是真机,左边是模拟器,程序演示了八个客户端互发图片的风貌:首先是模拟器发送图片给真机,然后真机发送图片给模拟器):

图片 11

说来也怪,在大家最广泛的偏见里,都说东京(Tokyo)男人过度理性精明,缺少雄性基因。

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的选用进程,其中一个用到运行在模拟器中作为广播节点,另一个运作在华为真机上作为发现节点,并且完毕八个节点的图纸互传。

先是看一下看成广播节点的顺序:

界面:

图片 12

点击“起初播放”来揭橥服务,一旦有节点连接此服务就可以利用“选取照片”来从照片库中选取一张图纸并发送到所有已一连节点。

程序:

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

再看一下看成发现节点的先后:

界面:

图片 13

点击“查找设备”浏览可用服务,点击服务建立连接;一旦创设了连年之后就足以点击“选取照片”会从照片库中拔取一张图片并发送给已连接的节点。

程序:

//
//  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”才能窥见此服务。

运作效果:

图片 14

那般的以讹传讹不知情传了多短时间,可以肯定的是,说那话的人或者都不看球。

CoreBluetooth

不论是GameKit如故MultipeerConnectivity,都不得不在iOS设备之间展开数据传输,那就大大下降了蓝牙的采纳范围,于是从iOS6方始苹果推出了CoreBluetooth.framework,那个框架最大的表征就是完全基于BLE4.0标准还要帮助非iOS设备。当前BLE应用格外普遍,不再仅仅是多少个装备之间的数量传输,它还有众多其余应用市场,例如室内定位、有线支付、智能家居等等,那也使得CoreBluetooth成为当下最看好的蓝牙技术。

CoreBluetooth设计同样也是相仿于客户端-服务器端的设计,作为服务器端的设施称为外围设备(Peripheral),作为客户端的设备叫做中心设备(Central),CoreBlueTooth整个框架就是依照那多少个概念来统筹的。

图片 15

外围设备和中心设备在CoreBluetooth中应用CBPeripheralManager和CBCentralManager表示。

CBPeripheralManager:外围设备常常用于揭橥服务、生成数据、保存数据。外围设备公布并播放服务,告诉周围的中心设备它的可用服务和特征。

CBCentralManager:要旨设备使用外围设备的数目。中心设备扫描到外围设备后会就会估量确立连接,一旦接二连三成功就能够动用那些劳动和特色。

外围设备和中心设备之间相互的桥梁是劳务(CB瑟维斯(Service)(Service))和特点(CBCharacteristic),二者都有一个唯一的标识UUID(CBUUID类型)来唯一确定一个劳务依然特征,每个服务能够具备多个特色,上边是他们中间的关联:

图片 16

一台iOS设备(注意OPPO4以下设备不协理BLE,别的iOS7.0、8.0模拟器也无能为力模拟BLE)既可以当作外围设备又足以作为宗旨设备,但是不可能而且即是外围设备又是中心设备,同时注意建立连接的进程不要求用户手动选拔允许,那或多或少和后边多少个框架是例外的,那至关重倘诺因为BLE应用场景不再局限于两台设备之间资源共享了。

A.外围设备

创制一个外围设备日常分为以下多少个步骤:

  1. 创制外围设备CBPeripheralManager对象并指定代理。
  2. 创设特征CBCharacteristic、服务CBSerivce并添加到外围设备
  3. 外围设备开头播放服务(startAdvertisting:)。
  4. 和中心设备CBCentral进行交互。

下边是粗略的程序示例,程序有八个按钮“启动”和“更新”,点击启动按钮则开创外围设备、添加服务和特征并初始播报,一旦发现有主旨设备连接并订阅了此服务的性状则经过立异按钮更新特征数据,此时已订阅的中心设备就会吸收更新数据。

界面设计:

图片 17

先后设计:

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

地点程序运行的流程如下(图中紫色表示外围设备操作,紫色部分代表中心设备操作):

图片 18

B.要旨设备

中心设备的开创一般可以分为如下几个步骤:

  1. 成立中央设备管理对象CBCentralManager并点名代理。
  2. 扫描外围设备,一般发现可用外围设备则一而再并保存外围设备。
  3. 摸索外围设备服务和特征,查找到可用特征则读取特征数据。

下边是一个简练的中心服务器端完成,点击“启动”按钮则始于扫描周围的外围设备,一旦发觉了可用的外围设备则树立连接并设置外围设备的代理,之后开首查找其劳动和特性。一旦外围设备的特色值做了履新,则足以在代理方法中读取更新后的特征值。

界面设计:

图片 19

程序设计:

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

上边程序运行的流程图如下:

图片 20

有了上边多个程序就可以分级运行在两台支持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

运作效果:

图片 21

出殡成功之后:

图片 22

在那么些进程中开发人士不须求精通腾讯网博客园的越来越多分享细节,Social框架中一度统一了享受的接口,你能够因而瑟维斯(Service)(Service)Type设置是享受到非死不可、推特、和讯今日头条、腾讯微博,而不尊崇具体的细节达成。那么当运行方面的演示时它是怎么精通用哪些账户来发送新浪啊?其实在iOS的安装中有越发设置非死不可、推特(TWTR.US)、新浪的地方:

图片 23

不可以不首先在那里安装天涯论坛账户才能一气浑成地点的出殡,不然Social框架也不容许清楚具体运用哪个账户来发送。

昨夜,就是在那种近似完美的空气下,申花在2017足协杯决赛第三次合里,1:0将同城敌手香岛上港斩于马下。

其三方框架

理所当然,通过上边的装置界面应该可以看看,苹果官方默认援救的享受并不太多,更加是对于国内的施用只匡助今日头条新浪和腾讯微博(事实上从iOS7苹果才考虑匡助腾讯今日头条),那么只要要分享到微信、人人、心花怒放等等国内较为盛名的交际网络肿么办呢?方今最好的抉择就是应用第三方框架,因为即使要协调已毕种种应用的接口如故比较复杂的。当前应用较多的就是友盟社会化组件ShareSDK,而且现在百度也出了社会化分享组件。今日无法对负有组件都进行逐一介绍,那里就以友盟社交化组件为例简单做一下介绍:

  1. 挂号友盟账号并新建应用得到AppKey。
  2. 下载友盟SDK并将下载的文件放到项目中(注意下载的经过中得以挑选所必要的享用服务)。
  3. 在应用程序中设置友盟的AppKey。
  4. 分享时调用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

运行效果:

图片 24

小心:在率先次使用某个分享服务是急需输入相应的账号得到授权才能享用。

比赛过程并未怎么好说的,一边是一支团结一致万众一心的球队,另一头则是多少个有名的人带球猛冲压根不懂传球的一盘散沙,胜负可想而知。

GameCenter

Game
Center是由苹果发布的在线多人游戏社交网络,通过它玩耍玩家可以邀请好友举办两个人游玩,它也会记录玩家的成就并在排名榜中浮现,同时玩家每经过一定的级差会收获不一致的到位。那里就大致介绍一下什么在和谐的应用中集成Game
Center服务来让用户得到积分、成就以及查看游戏排名和已赢得成功。

是因为Game
Center是苹果推出的一项主要服务,苹果官方对于它的操纵非常严厉,由此使用Game
Center此前务必要做过多预备工作。平时须要经过以下多少个步骤(上面的准备干活重大是指向真机的,模拟器省略Provisioning
Profile配置进度):

  1. 在苹果开发者要旨创造扶助Game Center服务的App ID并点名具体的Bundle
    ID,假使是“com.cmjstudio.kctest”(注意这些Bundle
    ID就是未来要开发的游艺的Bundle
    ID)。 图片 25
  2. 根据“com.cmjstudio.kctest”创设开发者配置文件(或描述文件)并导入对应的装备(创制进度中采纳接济Game
    Center服务的App ID,那样iOS设备在运行指定Boundle
    ID应用程序就精晓此拔取支撑Game
    Center服务)。 图片 26
  3. 在iTunes
    Connect中开创一个选拔(如果叫“KCTest”,这是一款足球比赛游艺)并点名“套装ID”为事先创制的“com.cmjstudio.kctest”,让动用和那么些App关联(注意那个动用不须要付出)。
  4. 在iTunes
    Connect的“用户和功用”中创制沙盒测试用户(由于在测试阶段应用还没有正经提交到App
    Store,所以唯有沙盒用户可以登录Game Center)。
  5. 在iTunes Connect中配置此选择Game
    Center(那里配置了游戏在游戏中央的突显名称为“CMJ”),在里面添加排名榜和姣好(借使添加一个名次榜ID“Goals”表示进球个数;七个成功ID分别为“Adidas高尔德Ball”、“Adidas高尔德Boot”代表金球奖和金靴奖成就,点数分别为80、100)。图片 27
  6. 在iOS“设置”中找到Game
    Center允许沙盒,否则真机无法调试(即便是模拟器不须要此项设置)。 图片 28

有了以上准备就足以在应用程序中追加积分、添加成就了,当然在实际上支付进度积分和成就都是依照玩家所经过的关卡来落成的,为了简化那些进度那里就径直通过多少个按钮手动触发这么些事件。Game
Center开发要求使用GameKit框架,首先熟练一下常用的多少个类:

GKLocalPlayer:表示当地玩家,在Game基特(Kit)中还有一个GKPlayer表示联机玩家,为了有限支撑非联网用户也得以正常使用游戏效果,一般采纳GKLocalPlayer。

GKScore:管理游戏积分,例如设置积分、排行等。

GKLeaderboard:表示游戏名次榜,主用用于管理玩家名次,例如加载排名榜、设置默许排名榜、加载名次榜图片等。

GKAchievement:表示成就,主用用于管理玩家形成,例如加载成功、提交成功,重置成就等。

GKAchievementDescription:成就描述信息,包罗成就的题目、得到前描述、获得后描述、是否可另行得到成就等新闻。

GKGameCenterViewController:排名榜、成就查看视图控制器。若是利用本身不必要自己支付排名榜、成就查看试图可以一向调用此控制器。

上边就以一个简易的示范来成功名次榜、成就设置和查看,在这几个演示程序中经过二种艺术来查阅名次和已毕:一种是一贯利用框架自带的GKGameCenterViewContrller调用系统视图查看,另一种是通过API自己读取排名榜、成就新闻并展现。其它在利用中有两个增加按钮分别用于安装得分和姣好。应用大约布局如下(图片较大可点击查阅大图):

图片 29

1.先是看一下主视图控制器KCMainTableViewController:

主视图控制器调用GKLeaderboard的loadLeaderboardsWithCompletionHandler:主意加载了富有名次榜,这些进度必要小心每个名次榜(GKLeaderboard)中的scores属性是绝非值的,即使要让种种名次榜的scores属性有值必须调用三回排名榜的loadScoresWithCompletionHandler:方法。

调用GKAchievement的loadAchievementsWithCompletionHandler:艺术加载加载成就,注意那几个主意只好获取完毕度不为0的达成,要是成功度为0是取得不到的;然后调用GKAchievementDesciption的loadAchievementDescriptionsWithCompletionHandler:措施加载了装有成就描述,那里加载的是具有成就描述(不管落成度是否为0);紧接着调用了各样成就描述的loadImageWithCompletionHandler:办法加载成功图片。

将收获的排名榜、成就、成就描述、成就图片音讯保存,并在导航到详情视图时传递给名次榜视图控制器和成就视图控制器以便在子控制器视图中显示。

在主视图控制器左上方添加查看游戏要旨决定按钮,点击按钮调用GKGameCenterViewController来呈现排名榜、成就、玩家消息,那是系统自带的一个游玩为主视图方便和前边我们和好拿走的新闻对比。

先后如下

//
//  KCMainTableViewController.m
//  kctest
//
//  Created by Kenshin Cui on 14/4/5.
//  Copyright (c) 2015年 cmjstudio. All rights reserved.
//  静态表格

#import "KCMainTableViewController.h"
#import <GameKit/GameKit.h>
#import "KCLeaderboardTableViewController.h"
#import "KCAchievementTableViewController.h"

@interface KCMainTableViewController ()<GKGameCenterControllerDelegate>

@property (strong,nonatomic) NSArray *leaderboards;//排行榜对象数组
@property (strong,nonatomic) NSArray *achievements;//成就
@property (strong,nonatomic) NSArray *achievementDescriptions;//成就描述
@property (strong,nonatomic) NSMutableDictionary *achievementImages;//成就图片

@property (weak, nonatomic) IBOutlet UILabel *leaderboardLabel; //排行个数
@property (weak, nonatomic) IBOutlet UILabel *achievementLable; //成就个数

@end

@implementation KCMainTableViewController

#pragma mark - 控制器视图事件
- (void)viewDidLoad {
    [super viewDidLoad];

    [self authorize];
}

#pragma mark - UI事件
- (IBAction)viewGameCenterClick:(UIBarButtonItem *)sender {
    [self viewGameCenter];
}

#pragma mark - GKGameCenterViewController代理方法
//点击完成
-(void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController{
    NSLog(@"完成.");
    [gameCenterViewController dismissViewControllerAnimated:YES completion:nil];
}

#pragma mark -导航
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    //如果是导航到排行榜,则将当前排行榜传递到排行榜视图
    if ([segue.identifier isEqualToString:@"leaderboard"]) {
        UINavigationController *navigationController=segue.destinationViewController;
        KCLeaderboardTableViewController *leaderboardController=[navigationController.childViewControllers firstObject];
        leaderboardController.leaderboards=self.leaderboards;
    }else if ([segue.identifier isEqualToString:@"achievement"]) {
        UINavigationController *navigationController=segue.destinationViewController;
        KCAchievementTableViewController *achievementController=[navigationController.childViewControllers firstObject];
        achievementController.achievements=self.achievements;
        achievementController.achievementDescriptions=self.achievementDescriptions;
        achievementController.achievementImages=self.achievementImages;
    }
}

#pragma mark - 私有方法
//检查是否经过认证,如果没经过认证则弹出Game Center登录界面
-(void)authorize{
    //创建一个本地用户
    GKLocalPlayer *localPlayer= [GKLocalPlayer localPlayer];
    //检查用于授权,如果没有登录则让用户登录到GameCenter(注意此事件设置之后或点击登录界面的取消按钮都会被调用)
    [localPlayer setAuthenticateHandler:^(UIViewController * controller, NSError *error) {
        if ([[GKLocalPlayer localPlayer] isAuthenticated]) {
            NSLog(@"已授权.");
            [self setupUI];
        }else{
            //注意:在设置中找到Game Center,设置其允许沙盒,否则controller为nil
            [self  presentViewController:controller animated:YES completion:nil];
        }
    }];
}
//UI布局
-(void)setupUI{
    //更新排行榜个数
    [GKLeaderboard loadLeaderboardsWithCompletionHandler:^(NSArray *leaderboards, NSError *error) {
        if (error) {
            NSLog(@"加载排行榜过程中发生错误,错误信息:%@",error.localizedDescription);
        }
        self.leaderboards=leaderboards;
        self.leaderboardLabel.text=[NSString stringWithFormat:@"%i",leaderboards.count];
        //获取得分,注意只有调用了loadScoresWithCompletionHandler:方法之后leaderboards中的排行榜中的scores属性才有值,否则为nil
        [leaderboards enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            GKLeaderboard *leaderboard=obj;
            [leaderboard loadScoresWithCompletionHandler:^(NSArray *scores, NSError *error) {
            }];
        }];
    }];
    //更新获得成就个数,注意这个个数不一定等于iTunes Connect中的总成就个数,此方法只能获取到成就完成进度不为0的成就
    [GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error) {
        if (error) {
            NSLog(@"加载成就过程中发生错误,错误信息:%@",error.localizedDescription);
        }
        self.achievements=achievements;
        self.achievementLable.text=[NSString stringWithFormat:@"%i",achievements.count];
        //加载成就描述(注意,即使没有获得此成就也能获取到)
        [GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:^(NSArray *descriptions, NSError *error) {
            if (error) {
                NSLog(@"加载成就描述信息过程中发生错误,错误信息:%@",error.localizedDescription);
                return ;
            }
            self.achievementDescriptions=descriptions;
            //加载成就图片
            _achievementImages=[NSMutableDictionary dictionary];
            [descriptions enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
                GKAchievementDescription *description=(GKAchievementDescription *)obj;
               [description loadImageWithCompletionHandler:^(UIImage *image, NSError *error) {
                   [_achievementImages setObject:image forKey:description.identifier];
               }];
            }];
        }];
    }];
}

//查看Game Center
-(void)viewGameCenter{
    if (![GKLocalPlayer localPlayer].isAuthenticated) {
        NSLog(@"未获得用户授权.");
        return;
    }
    //Game Center视图控制器
    GKGameCenterViewController *gameCenterController=[[GKGameCenterViewController alloc]init];
    //设置代理
    gameCenterController.gameCenterDelegate=self;
    //显示
    [self presentViewController:gameCenterController animated:YES completion:nil];
}
@end

2.然后看一下排名榜控制器视图KCLeaderboardTableViewController:

在名次榜控制器视图中定义一个leaderboards属性用于吸纳主视图控制器传递的名次榜信息同时通过一个UITableView浮现排名榜名称、得分等。

在名次榜控制器视图中通过GKScore的reportScores:
withCompletionHandler:
安装排名榜得分,注意每个GKScore对象必须安装value属性来表示得分(GKScore是通过identifier来和排名榜涉及起来的)。

次第如下

//
//  KCLeaderboardTableViewController.m
//  kctest
//
//  Created by Kenshin Cui on 14/4/5.
//  Copyright (c) 2015年 cmjstudio. All rights reserved.
//

#import "KCLeaderboardTableViewController.h"
#import <GameKit/GameKit.h>
//排行榜标识,就是iTunes Connect中配置的排行榜ID
#define kLeaderboardIdentifier1 @"Goals"

@interface KCLeaderboardTableViewController ()
@end

@implementation KCLeaderboardTableViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}
#pragma mark - UI事件
//添加得分(这里指的是进球数)
- (IBAction)addScoreClick:(UIBarButtonItem *)sender {
    [self addScoreWithIdentifier:kLeaderboardIdentifier1 value:100];
}
#pragma mark - UITableView数据源方法
-(NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index{
    return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return self.leaderboards.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    static NSString *identtityKey=@"myTableViewCellIdentityKey1";
    UITableViewCell *cell=[self.tableView dequeueReusableCellWithIdentifier:identtityKey];
    if(cell==nil){
        cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identtityKey];
    }
    GKLeaderboard *leaderboard=self.leaderboards[indexPath.row];
    GKScore *score=[leaderboard.scores firstObject];
    NSLog(@"scores:%@",leaderboard.scores);
    cell.textLabel.text=leaderboard.title;//排行榜标题
    cell.detailTextLabel.text=[NSString stringWithFormat:@"%lld",score.value]; //排行榜得分
    return cell;
}

#pragma mark - 属性

#pragma mark - 私有方法
/**
 *  设置得分
 *
 *  @param identifier 排行榜标识
 *  @param value      得分
 */
-(void)addScoreWithIdentifier:(NSString *)identifier value:(int64_t)value{
    if (![GKLocalPlayer localPlayer].isAuthenticated) {
        NSLog(@"未获得用户授权.");
        return;
    }
    //创建积分对象
    GKScore *score=[[GKScore alloc]initWithLeaderboardIdentifier:identifier];
    //设置得分
    score.value=value;
    //提交积分到Game Center服务器端,注意保存是异步的,并且支持离线提交
    [GKScore reportScores:@[score] withCompletionHandler:^(NSError *error) {
        if(error){
            NSLog(@"保存积分过程中发生错误,错误信息:%@",error.localizedDescription);
            return ;
        }
        NSLog(@"添加积分成功.");
    }];
}
@end

3.终极就是到位视图控制器KCAchievementTableViewController:

在成就视图控制器定义achievements、achievementDescriptions、achievementImages多个属性分别代表成就、成就描述、成就图片,那两个属性均从主视图控制器中传送进入,然后选择UITableView体现成就、成就图片、成就进程。

创办GKAchievemnt对象(通过identifier属性来代表具体的已毕)并指定达成度,通过调用GKAchievement的reportAchievements:
withCompletionHandler:
艺术提交达成度到Game Center服务器。

先后如下

//
//  KCAchievementTableViewController.m
//  kctest
//
//  Created by Kenshin Cui on 14/4/5.
//  Copyright (c) 2015年 cmjstudio. All rights reserved.
//

#import "KCAchievementTableViewController.h"
#import <GameKit/GameKit.h>
//成就标识,就是iTunes Connect中配置的成就ID
#define kAchievementIdentifier1 @"AdidasGoldenBall"
#define kAchievementIdentifier2 @"AdidasGoldBoot"

@interface KCAchievementTableViewController ()

@end

@implementation KCAchievementTableViewController
#pragma mark - 控制器视图方法
- (void)viewDidLoad {
    [super viewDidLoad];

}

#pragma mark - UI事件
//添加成就
- (IBAction)addAchievementClick:(UIBarButtonItem *)sender {
    [self addAchievementWithIdentifier:kAchievementIdentifier1];
}

#pragma mark - UITableView数据源方法
-(NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index{
    return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return self.achievementDescriptions.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    static NSString *identtityKey=@"myTableViewCellIdentityKey1";
    UITableViewCell *cell=[self.tableView dequeueReusableCellWithIdentifier:identtityKey];
    if(cell==nil){
        cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identtityKey];
    }
    GKAchievementDescription *desciption=[self.achievementDescriptions objectAtIndex:indexPath.row];
    cell.textLabel.text=desciption.title ;//成就标题
    //如果已经获得成就则加载进度,否则为0
    double percent=0.0;
    GKAchievement *achievement=[self getAchievementWithIdentifier:desciption.identifier];
    if (achievement) {
        percent=achievement.percentComplete;
    }
    cell.detailTextLabel.text=[NSString stringWithFormat:@"%3.2f%%",percent]; //成就完成度
    //设置成就图片
    cell.imageView.image=[self.achievementImages valueForKey:desciption.identifier];
    return cell;
}



#pragma mark - 私有方法
//添加指定类别的成就
-(void)addAchievementWithIdentifier:(NSString *)identifier{
    if (![GKLocalPlayer localPlayer].isAuthenticated) {
        NSLog(@"未获得用户授权.");
        return;
    }
    //创建成就
    GKAchievement *achievement=[[GKAchievement alloc]initWithIdentifier:identifier];
    achievement.percentComplete=100;//设置此成就完成度,100代表获得此成就
    NSLog(@"%@",achievement);
    //保存成就到Game Center服务器,注意保存是异步的,并且支持离线提交
    [GKAchievement reportAchievements:@[achievement] withCompletionHandler:^(NSError *error) {
        if(error){
            NSLog(@"保存成就过程中发生错误,错误信息:%@",error.localizedDescription);
            return ;
        }
        NSLog(@"添加成就成功.");
    }];
}

//根据标识获得已取得的成就
-(GKAchievement *)getAchievementWithIdentifier:(NSString *)identifier{
    for (GKAchievement *achievement in self.achievements) {
        if ([achievement.identifier isEqualToString:identifier]) {
            return achievement;
        }
    }
    return nil;
}
@end

运行效果:

图片 30

小心第一次接纳游戏时出于尚未对Game Center授权,会唤醒用户登录Game
Center。

足篮球场上的事体很风趣,傲慢的人何以也得不到,哪怕是实力高出对手一截。

内购

世家都晓得做iOS开发自己的收入有二种来自:出售应用、内购和广告。国内用户一般很少直接购买使用,因而对此开发者而言(尤其是个体开发者),内购和广告获益就成了重大的低收入来源。内购营销格局,平常软件本身是不收费的,可是要博得某些特权就亟须购买部分道具,而内购的进程是由苹果官方统一来保管的,所以和Game
Center一样,在支付内购程序此前要做一些预备干活(下边的备选工作重大是针对性真机的,模拟器省略Provisioning
Profile配置进程):

  1. 前四步和Game Center基本完全一致,只是在甄选服务时不是选项Game
    Center而是要选拔内购服务(In-App Purchase)。
  2. 到iTuens Connect中装置“App
    内购买项目”,那里依然以地方的“KCTest”项目为例,即使这些足球比赛游艺中有二种道具,分别为“强力手套”(增强防卫)、“金球”(扩大金球率)和“能量瓶”(提供丰裕体力),前双方是非消耗品只用四回性购买,后者是消耗品用完两遍必须重新购入。图片 31
  3. 到iTunes Connect中找到“协议、税务和银行业务”扩张“iOS Paid
    Applications”协议,并形成具有配置后等待审批通过(注意这一步尽管不安装在应用程序中不可以得到可购买产品)。
  4. 在iOS“设置”中找到”iTunes Store与App
    Store“,在此处可以选择选择沙盒用户登录仍然处于注销状态,不过毫无疑问留神不可能应用真实用户登录,否则上面的买进测试不会马到功成,因为到如今停止大家的接纳并从未真的通过苹果官方审核只好用沙盒测试用户(如若是模拟器不须求此项设置)。
  5. 有了地点的设置之后保险应用程序Bundle ID和iTunes Connect中的Bundle
    ID(或者说App ID中布局的Bundle ID)一致即可准备付出。

付出内购应用时要求动用StoreKit.framework,下边是以此框架中常用的多少个类:

SKProduct:可购得的产品(例如地点安装的能量瓶、强力手套等),其productIdentifier属性对应iTunes
Connect中配置的“产品ID“,不过此类不指出直接早先化使用,而是要由此SKProductRequest来加载可用产品(幸免出现购买到不行的出品)。

SKProductRequest:产品请求类,首要用来加载产品列表(包涵可用产品和不可用产品),经常加载完事后会透过其-(void)productsRequest:(SKProductsRequest
*)request didReceiveResponse:(SKProductsResponse
*)response
代办方法取得响应,得到响应中的可用产品。

SKPayment:产品采购支付类,保存了产品ID、购买数量等信息(注意与其相应的有一个SKMutablePayment对象,此目的足以修改产品数量等音信)。

SKPaymentQueue:产品购买支付队列,一旦将一个SKPayment添加到此行列就会向苹果服务器发送请求完开销次交易。注意交易的事态汇报不是由此代理完结的,而是经过一个贸易监听者(类似于代理,可以经过队列的addTransactionObserver来设置)。

SKPaymentTransaction:几次产品采购交易,经常交易成功后支付队列会调用交易监听者的-(void)paymentQueue:(SKPaymentQueue
*)queue updatedTransactions:(NSArray
*)transaction
艺术反馈交易境况,并在此办法少将交易对象回来。

SKStoreProductViewController:应用程序商店产品体现视图控制器,用于在应用程序内部突显此接纳在行使商店的动静。(例如可以选择它让用户在使用内形成评价,注意由于此次演示的言传身教程序没有正规提交到应用集团,所以在此暂不演示此控制器视图的施用)。

问询了上述几个常用的支付API之后,下边看一下应用内选购的流程:

  1. 透过SKProductRequest得到可购买产品SKProduct数组(SKProductRequest会按照程序的Bundle
    ID去相应的内购配置中取得指定ID的产品对象),这些进度中要求知道产品标识(必须和iTuens
    Connect中的对应起来),可以储存到沙盒中也足以储存到数据库中(上面的Demo中定义成了宏定义)。
  2. 伸手完毕后可以在SKProductRequest的-(void)productsRequest:(SKProductsRequest
    *)request didReceiveResponse:(SKProductsResponse
    *)response
    代理方法中赢得SKProductResponse对象,这些目的中保留了products属性表示可用产品对象数组。
  3. 给SKPaymentQueue设置一个监听者来取得贸易的情况(它好像于一个代理),监听者通过-(void)paymentQueue:(SKPaymentQueue
    *)queue updatedTransactions:(NSArray
    *)transaction
    主意反馈交易的变通情形(平常在此格局中得以根据交易成功、復苏成功等状态来做一些处理)。
  4. 一旦用户决定选购某个产品(SKProduct),就可以依照SKProduct来成立一个遥相呼应的开支对象SKPayment,只要将这几个目的参预到SKPaymentQueue中就会触发购买行为(将订单提交到苹果服务器),一旦一个贸易爆发变化就会触发SKPaymentQueue监听者来举报交易意况。
  5. 交易提交给苹果服务器之后若是不出意外的话平时就会弹出一个确认购买的对话框,带领用户落成交易,最后成就交易后(平常是成功交易,用户点击”好“)会调用交易监听者-(void)paymentQueue:(SKPaymentQueue
    *)queue updatedTransactions:(NSArray
    *)transaction
    措施将本次交易的享有交易对象SKPaymentTransaction数组重回,可以透过贸易意况判断贸易意况。
  6. 平凡一遍交易形成后须求对本次交易举办认证,幸免越狱机器模拟苹果官方的报告造成交易成功假象。苹果官方提供了一个证实的URL,只要将交易成功后的证据(这一个证据从iOS7过后在交易得逞会会存储到沙盒中)传递给那么些地点就会提交交易景况和此次交易的详细音信,通过那些信息(平常可以按照交易景况、Bundler
    ID、ProductID等确认)可以标识出交易是否确实形成。
  7. 对于非消耗品,用户在形成采购后要是用户使用别的机器登录照旧用户卸载重新安装应用后平日希望那几个非消耗品可以东山再起(事实上若是不恢复生机用户再度买入也不会成功)。调用SKPaymentQueue的restoreCompletedTransactions就能够已毕苏醒,復苏后会调用交易监听者的paymentQueue:(SKPaymentQueue
    *)queue updatedTransactions:(NSArray
    *)transaction
    艺术反馈回复的贸易(也就是已购置的非消耗品交易,注意这一个进程中借使没有非消耗品可还原,是不会调用此格局的)。

下边通过一个示范程序演示内购和回复的总体经过,程序界面差不离如下:

主界面中浮现了颇具可选购产品和售卖价格,以及采购境况。

挑选一个产品点”购买“可以购买此商品,购买成功后刷新购买景况(若是是非消耗品则显示已购置,假诺是消耗品则呈现购买个数)。

次第卸载后重新安装可以点击”苏醒购买“来复苏已购置的非消耗品。

图片 32

程序代码:

//
//  KCMainTableViewController.m
//  kctest
//
//  Created by Kenshin Cui on 14/4/5.
//  Copyright (c) 2015年 cmjstudio. All rights reserved.
//

#import "KCMainTableViewController.h"
#import <StoreKit/StoreKit.h>
#define kAppStoreVerifyURL @"https://buy.itunes.apple.com/verifyReceipt" //实际购买验证URL
#define kSandboxVerifyURL @"https://sandbox.itunes.apple.com/verifyReceipt" //开发阶段沙盒验证URL


//定义可以购买的产品ID,必须和iTunes Connect中设置的一致
#define kProductID1 @"ProtectiveGloves" //强力手套,非消耗品
#define kProductID2 @"GoldenGlobe" //金球,非消耗品
#define kProductID3 @"EnergyBottle" //能量瓶,消耗品


@interface KCMainTableViewController ()<SKProductsRequestDelegate,SKPaymentTransactionObserver>

@property (strong,nonatomic) NSMutableDictionary *products;//有效的产品
@property (assign,nonatomic) int selectedRow;//选中行
@end

@implementation KCMainTableViewController
#pragma mark - 控制器视图方法
- (void)viewDidLoad {
    [super viewDidLoad];

    [self loadProducts];
    [self addTransactionObjserver];
}

#pragma mark - UI事件
//购买产品
- (IBAction)purchaseClick:(UIBarButtonItem *)sender {
    NSString *productIdentifier=self.products.allKeys[self.selectedRow];
    SKProduct *product=self.products[productIdentifier];
    if (product) {
        [self purchaseProduct:product];
    }else{
        NSLog(@"没有可用商品.");
    }

}
//恢复购买
- (IBAction)restorePurchaseClick:(UIBarButtonItem *)sender {
    [self restoreProduct];
}

#pragma mark - UITableView数据源方法

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.products.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *identtityKey=@"myTableViewCellIdentityKey1";
    UITableViewCell *cell=[self.tableView dequeueReusableCellWithIdentifier:identtityKey];
    if(cell==nil){
        cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identtityKey];
    }
    cell.accessoryType=UITableViewCellAccessoryNone;
    NSString *key=self.products.allKeys[indexPath.row];
    SKProduct *product=self.products[key];
    NSString *purchaseString;
    NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
    if ([product.productIdentifier isEqualToString:kProductID3]) {
        purchaseString=[NSString stringWithFormat:@"已购买%i个",[defaults integerForKey:product.productIdentifier]];
    }else{
        if([defaults boolForKey:product.productIdentifier]){
            purchaseString=@"已购买";
        }else{
            purchaseString=@"尚未购买";
        }
    }
    cell.textLabel.text=[NSString stringWithFormat:@"%@(%@)",product.localizedTitle,purchaseString] ;
    cell.detailTextLabel.text=[NSString stringWithFormat:@"%@",product.price];
    return cell;
}
#pragma mark - UITableView代理方法
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

    UITableViewCell *currentSelected=[tableView cellForRowAtIndexPath:indexPath];
    currentSelected.accessoryType=UITableViewCellAccessoryCheckmark;
    self.selectedRow=indexPath.row;
}
-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell *currentSelected=[tableView cellForRowAtIndexPath:indexPath];
    currentSelected.accessoryType=UITableViewCellAccessoryNone;
}

#pragma mark - SKProductsRequestd代理方法
/**
 *  产品请求完成后的响应方法
 *
 *  @param request  请求对象
 *  @param response 响应对象,其中包含产品信息
 */
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    //保存有效的产品
    _products=[NSMutableDictionary dictionary];
    [response.products enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        SKProduct *product=obj;
        [_products setObject:product forKey:product.productIdentifier];
    }];
    //由于这个过程是异步的,加载成功后重新刷新表格
    [self.tableView reloadData];
}
-(void)requestDidFinish:(SKRequest *)request{
    NSLog(@"请求完成.");
}
-(void)request:(SKRequest *)request didFailWithError:(NSError *)error{
    if (error) {
        NSLog(@"请求过程中发生错误,错误信息:%@",error.localizedDescription);
    }
}

#pragma mark - SKPaymentQueue监听方法
/**
 *  交易状态更新后执行
 *
 *  @param queue        支付队列
 *  @param transactions 交易数组,里面存储了本次请求的所有交易对象
 */
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
    [transactions enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        SKPaymentTransaction *paymentTransaction=obj;
        if (paymentTransaction.transactionState==SKPaymentTransactionStatePurchased){//已购买成功
            NSLog(@"交易\"%@\"成功.",paymentTransaction.payment.productIdentifier);
            //购买成功后进行验证
            [self verifyPurchaseWithPaymentTransaction];
            //结束支付交易
            [queue finishTransaction:paymentTransaction];
        }else if(paymentTransaction.transactionState==SKPaymentTransactionStateRestored){//恢复成功,对于非消耗品才能恢复,如果恢复成功则transaction中记录的恢复的产品交易
            NSLog(@"恢复交易\"%@\"成功.",paymentTransaction.payment.productIdentifier);
            [queue finishTransaction:paymentTransaction];//结束支付交易

            //恢复后重新写入偏好配置,重新加载UITableView
            [[NSUserDefaults standardUserDefaults]setBool:YES forKey:paymentTransaction.payment.productIdentifier];
            [self.tableView reloadData];
        }else if(paymentTransaction.transactionState==SKPaymentTransactionStateFailed){
            if (paymentTransaction.error.code==SKErrorPaymentCancelled) {//如果用户点击取消
                NSLog(@"取消购买.");
            }
            NSLog(@"ErrorCode:%i",paymentTransaction.error.code);
        }

    }];
}
//恢复购买完成
-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue{
    NSLog(@"恢复完成.");
}

#pragma mark - 私有方法
/**
 *  添加支付观察者监控,一旦支付后则会回调观察者的状态更新方法:
 -(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
 */
-(void)addTransactionObjserver{
    //设置支付观察者(类似于代理),通过观察者来监控购买情况
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
/**
 *  加载所有产品,注意产品一定是从服务器端请求获得,因为有些产品可能开发人员知道其存在性,但是不经过审核是无效的;
 */
-(void)loadProducts{
    //定义要获取的产品标识集合
    NSSet *sets=[NSSet setWithObjects:kProductID1,kProductID2,kProductID3, nil];
    //定义请求用于获取产品
    SKProductsRequest *productRequest=[[SKProductsRequest alloc]initWithProductIdentifiers:sets];
    //设置代理,用于获取产品加载状态
    productRequest.delegate=self;
    //开始请求
    [productRequest start];
}
/**
 *  购买产品
 *
 *  @param product 产品对象
 */
-(void)purchaseProduct:(SKProduct *)product{
    //如果是非消耗品,购买过则提示用户
    NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
    if ([product.productIdentifier isEqualToString:kProductID3]) {
        NSLog(@"当前已经购买\"%@\" %i 个.",kProductID3,[defaults integerForKey:product.productIdentifier]);
    }else if([defaults boolForKey:product.productIdentifier]){
        NSLog(@"\"%@\"已经购买过,无需购买!",product.productIdentifier);
        return;
    }

    //创建产品支付对象
    SKPayment *payment=[SKPayment paymentWithProduct:product];
    //支付队列,将支付对象加入支付队列就形成一次购买请求
    if (![SKPaymentQueue canMakePayments]) {
        NSLog(@"设备不支持购买.");
        return;
    }
    SKPaymentQueue *paymentQueue=[SKPaymentQueue defaultQueue];
    //添加都支付队列,开始请求支付
//    [self addTransactionObjserver];
    [paymentQueue addPayment:payment];
}

/**
 *  恢复购买,对于非消耗品如果应用重新安装或者机器重置后可以恢复购买
 *  注意恢复时只能一次性恢复所有非消耗品
 */
-(void)restoreProduct{
    SKPaymentQueue *paymentQueue=[SKPaymentQueue defaultQueue];
    //设置支付观察者(类似于代理),通过观察者来监控购买情况
//    [paymentQueue addTransactionObserver:self];
    //恢复所有非消耗品
    [paymentQueue restoreCompletedTransactions];
}

/**
 *  验证购买,避免越狱软件模拟苹果请求达到非法购买问题
 *
 */
-(void)verifyPurchaseWithPaymentTransaction{
    //从沙盒中获取交易凭证并且拼接成请求体数据
    NSURL *receiptUrl=[[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receiptData=[NSData dataWithContentsOfURL:receiptUrl];

    NSString *receiptString=[receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];//转化为base64字符串

    NSString *bodyString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", receiptString];//拼接请求数据
    NSData *bodyData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];

    //创建请求到苹果官方进行购买验证
    NSURL *url=[NSURL URLWithString:kSandboxVerifyURL];
    NSMutableURLRequest *requestM=[NSMutableURLRequest requestWithURL:url];
    requestM.HTTPBody=bodyData;
    requestM.HTTPMethod=@"POST";
    //创建连接并发送同步请求
    NSError *error=nil;
    NSData *responseData=[NSURLConnection sendSynchronousRequest:requestM returningResponse:nil error:&error];
    if (error) {
        NSLog(@"验证购买过程中发生错误,错误信息:%@",error.localizedDescription);
        return;
    }
    NSDictionary *dic=[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil];
    NSLog(@"%@",dic);
    if([dic[@"status"] intValue]==0){
        NSLog(@"购买成功!");
        NSDictionary *dicReceipt= dic[@"receipt"];
        NSDictionary *dicInApp=[dicReceipt[@"in_app"] firstObject];
        NSString *productIdentifier= dicInApp[@"product_id"];//读取产品标识
        //如果是消耗品则记录购买数量,非消耗品则记录是否购买过
        NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
        if ([productIdentifier isEqualToString:kProductID3]) {
            int purchasedCount=[defaults integerForKey:productIdentifier];//已购买数量
            [[NSUserDefaults standardUserDefaults] setInteger:(purchasedCount+1) forKey:productIdentifier];
        }else{
            [defaults setBool:YES forKey:productIdentifier];
        }
        [self.tableView reloadData];
        //在此处对购买记录进行存储,可以存储到开发商的服务器端
    }else{
        NSLog(@"购买失败,未通过验证!");
    }
}
@end

运作效果(那是先后在卸载后重新安装的运作效果,卸载前一度购置”强力手套“,因而程序运行后点击了”復苏购买“):

图片 33

兴许申花教练吴金贵就是看清了那点,才坚决把特维斯那种“球星”,排除在大名单之外,而是派上了十一个肯为球队加油的运动员。固然,他们的薪资远远达不到“球星”的零头。

扩展–广告

地方也波及做iOS开发另一低收入来源就是广告,在iOS上有很多广告服务可以合二为一,使用相比较多的就是苹果的iAd、谷歌(Google)的Admob,下边不难演示一下咋样利用iAd来集成广告。使用iAd集成广告的经过相比不难,首先引入iAd.framework框架,然后创设ADBannerView来显示广告,日常会设置ADBannerView的代办方法来监听广告点击并在广告加载战败时隐藏广告显示控件。下边的代码简单的示范了这些历程:

//
//  ViewController.m
//  kctest
//
//  Created by Kenshin Cui on 14/4/5.
//  Copyright (c) 2015年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <iAd/iAd.h>

@interface ViewController ()<ADBannerViewDelegate>
@property (weak, nonatomic) IBOutlet ADBannerView *advertiseBanner;//广告展示视图

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //设置代理
    self.advertiseBanner.delegate=self;
}

#pragma mark - ADBannerView代理方法
//广告加载完成
-(void)bannerViewDidLoadAd:(ADBannerView *)banner{
    NSLog(@"广告加载完成.");
}
//点击Banner后离开之前,返回NO则不会展开全屏广告
-(BOOL)bannerViewActionShouldBegin:(ADBannerView *)banner willLeaveApplication:(BOOL)willLeave{
    NSLog(@"点击Banner后离开之前.");
    return YES;
}
//点击banner后全屏显示,关闭后调用
-(void)bannerViewActionDidFinish:(ADBannerView *)banner{
    NSLog(@"广告已关闭.");
}
//获取广告失败
-(void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error{
    NSLog(@"加载广告失败.");
    self.advertiseBanner.hidden=YES;
}

@end

运作效果:

图片 34

事实注脚他的抉择是不利的。申花全体表现出的积极进取、强力压迫、攻守平衡,最终战胜了上港的蒙头控球、空档不传、禁区跳水和对话评判。

iCloud

iCloud是苹果提供的云端服务,用户可以将通讯录、备忘录、邮件、照片、音乐、录像等备份到云服务器并在各类苹果设备间平素开展共享而无需关心数据同步问题,甚至即使你的设施丢失后在一台新的设施上也足以通过Apple
ID登录同步。当然这个情节都是iOS内置的效率,那么对于开放者咋样运用iCloud呢?苹果已经将云端存储效率开放给开发者,利用iCloud开发者能够储存两类数据:用户文档和使用数据、应用配置项。前者首要用来一些用户文档、文件的贮存,后者更如同于一般开放中的偏好设置,只是那一个布置音信会同步到云端。

要拓展iCloud开发同样需求部分准备干活(上边的预备工作首假如针对真机的,模拟器省略Provisioning
Profile配置进程):

1、2手续如故是创办App ID启用iCloud服务、生成对应的配置(Provisioning
Profile),这一个进度中Bundle ID可以拔取通配符(Data
Protection、iCloud、Inter-App 奥迪o、Passbook服务在开立App
ID时其中的Bundle ID是可以利用通配ID的)。

3.在Xcode中创制项目(若是项目名称为“kctest”)并在类型的Capabilities中找到iCloud并打开。那里须要留意的就是由于在此选择中要以身作则文档存储和首选项存储,因而在Service(Service)中勾选“Key-value
storae”和“iCloud Documents”:

图片 35

在档次中会自动生成一个”kctest.entitlements”配置文件,那个文档配置了文档存储容器标识、键值对存储容器标识等新闻。

图片 36

4.无论是真机依然模拟器都不可以不在iOS“设置”中找到iCloud设置签到账户,注意这几个账户不必是沙盒测试用户。

A.首先看一下怎么举行文档存储。文档存储紧要是利用UIDocument类来形成,那么些类提供了新建、修改(其实在API中是覆盖操作)、查询文档、打开文档、删除文档的机能。

UIDocument对文档的激增、修改、删除、读取全体基于一个云端URL来形成(事实上在支付进度中新增、修改只是一步简单的保存操作),对于开发者而言没有地面和云端之分,那样大大简化了支付进程。那么些URL可以透过NSFileManager的URLForUbiquityContainerIdentifier:办法得到,identifier是云端存储容器的唯一标识,即使传入nil则意味着第二个容器(事实上这一个容器可以通过前边生成的“kctest.entiements”中的Ubiquity
Container
Identifiers来收获。如上图可以见见那是一个数组,可以配备八个容器,例如大家的率先个容器标识是“iCloud.$(CFBundleIdentifier)”,其中$(CFBundleIdentifier)是Bundle
ID,那么依据使用的Bundle
ID就足以摸清第二个容器的标识是“iCloud.com.cmjstudio.kctest”。)。下边是常用的文档操作方法:

-(void)saveToURL:forSaveOperation:completionHandler::将点名URL的文档保存到iCloud(可以是新增或者覆盖,通过saveOperation参数设定)。

-(void)openWithCompletionHandler::打开当前文档。

小心:删除一个iCloud文档是使用NSFileManager的removeItemAtURL:error:措施来成功的。

由于实在支出进度中多少的存储和读取情况是扑朔迷离的,因而UIDocument在规划时并没有提供联合的储存形式来保存数据,而是期待开发者自己继承UIDocument类不分轩轾写-(id)contentsForType:(NSString
*)typeName error:(NSError *__autoreleasing
*)outError
-(BOOL)loadFromContents:(id)contents ofType:(NSString
*)typeName error:(NSError *__autoreleasing
*)outError
艺术来按照不一致的文档类型自己来操作数据(contents)。那多个章程分别在保存文档(-(void)saveToURL:forSaveOperation:completionHandler:)和打开文档(-(void)openWithCompletionHandler:)时调用。日常在子类中会定义一个属性A来储存文档数据,当保存文档时,会因此-(id)contentsForType:(NSString
*)typeName error:(NSError *__autoreleasing
*)outError方法将A转化为NSData或者NSFileWrapper(UIDocument保存数据的精神就是保留转化得到的NSData或者NSFileWrapper);当打开文档时,会透过-(BOOL)loadFromContents:(id)contents
ofType:(NSString *)typeName error:(NSError *__autoreleasing
*)outError方法将云端下载的NSData或者NSFileWrapper数据转载为A对应类型的多寡。为了方便演示下边简单定义一个持续自UIDocument的KCDocument类,在内部定义一个data属性存储数据:

//
//  KCDocument.m
//  kctest
//
//  Created by Kenshin Cui on 14/4/5.
//  Copyright (c) 2015年 cmjstudio. All rights reserved.
//

#import "KCDocument.h"

@interface KCDocument()

@end

@implementation KCDocument

#pragma mark - 重写父类方法
/**
 *  保存时调用
 *
 *  @param typeName <#typeName description#>
 *  @param outError <#outError description#>
 *
 *  @return <#return value description#>
 */
-(id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError{
    if (self.data) {
        return [self.data copy];
    }
    return [NSData data];
}

/**
 *  读取数据时调用
 *
 *  @param contents <#contents description#>
 *  @param typeName <#typeName description#>
 *  @param outError <#outError description#>
 *
 *  @return <#return value description#>
 */
-(BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError{
    self.data=[contents copy];
    return true;
}
@end

假设要加载iCloud中的文档列表就须要利用另一个类NSMetadataQuery,平常考虑到网络的来头并不会三遍性加载所有数据,而采纳NSMetadataQuery并指定searchScopes为NSMetadataQueryUbiquitousDocumentScope来限制查找iCloud文档数据。使用NSMetadataQuery还足以经过谓词限制搜索关键字等新闻,并在摸索完结未来通过公告的款式布告客户端搜索的情况。

我们都知晓微软的OneNote云台式机软件,通过它可以兑现多种分歧设置间的笔记同步,那里就概括完成一个依照iCloud服务的笔记软件。在底下的主次中落到实处笔记的激增、修改、保存、读取等操作。程序界面大致如下,点击界面右上方伸张按钮扩张一个笔记,点击某个笔记可以查看并编写。

图片 37

在主视图控制器首先查询所有iCloud保存的文档并在询问通告中遍历查询结果保存文档名称和创立日期到UITableView突显;其次当用户点击了增添按钮会调用KCDocument达成文档添加并导航到文档详情界面编辑文档内容。

//
//  KCMainTableViewController.m
//  kctest
//
//  Created by Kenshin Cui on 14/4/5.
//  Copyright (c) 2015年 cmjstudio. All rights reserved.
//

#import "KCMainTableViewController.h"
#import "KCDocument.h"
#import "KCDetailViewController.h"
#define kContainerIdentifier @"iCloud.com.cmjstudio.kctest" //容器id,可以从生产的entitiements文件中查看Ubiquity Container Identifiers(注意其中的$(CFBundleIdentifier)替换为BundleID)


@interface KCMainTableViewController ()
@property (strong,nonatomic) KCDocument *document;//当前选中的管理对象
@property (strong,nonatomic) NSMutableDictionary *files; //现有文件名、创建日期集合
@property (strong,nonatomic) NSMetadataQuery *dataQuery;//数据查询对象,用于查询iCloud文档

@end

@implementation KCMainTableViewController
#pragma mark - 控制器视图方法
- (void)viewDidLoad {
    [super viewDidLoad];

    [self loadDocuments];
}

#pragma mark - UI事件
//新建文档
- (IBAction)addDocumentClick:(UIBarButtonItem *)sender {
    UIAlertController *promptController=[UIAlertController alertControllerWithTitle:@"KCTest" message:@"请输入笔记名称" preferredStyle:UIAlertControllerStyleAlert];
    [promptController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
        textField.placeholder=@"笔记名称";
    }];
    UIAlertAction *okAction=[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
        UITextField *textField= promptController.textFields[0];
        [self addDocument:textField.text];
    }];
    [promptController addAction:okAction];
    UIAlertAction *cancelAction=[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {

    }];
    [promptController addAction:cancelAction];
    [self presentViewController:promptController animated:YES completion:nil];

}
#pragma mark - 导航
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"noteDetail"]) {
        KCDetailViewController *detailController= segue.destinationViewController;
        detailController.document=self.document;
    }
}

#pragma mark - 属性
-(NSMetadataQuery *)dataQuery{
    if (!_dataQuery) {
        //创建一个iCloud查询对象
        _dataQuery=[[NSMetadataQuery alloc]init];
        _dataQuery.searchScopes=@[NSMetadataQueryUbiquitousDocumentsScope];
        //注意查询状态是通过通知的形式告诉监听对象的
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(metadataQueryFinish:) name:NSMetadataQueryDidFinishGatheringNotification object:_dataQuery];//数据获取完成通知
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(metadataQueryFinish:) name:NSMetadataQueryDidUpdateNotification object:_dataQuery];//查询更新通知
    }
    return _dataQuery;
}
#pragma mark - 私有方法
/**
 *  取得云端存储文件的地址
 *
 *  @param fileName 文件名,如果文件名为nil则重新创建一个url
 *
 *  @return 文件地址
 */
-(NSURL *)getUbiquityFileURL:(NSString *)fileName{
    //取得云端URL基地址(参数中传入nil则会默认获取第一个容器)
    NSURL *url= [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:kContainerIdentifier];
    //取得Documents目录
    url=[url URLByAppendingPathComponent:@"Documents"];
    //取得最终地址
    url=[url URLByAppendingPathComponent:fileName];
    return url;
}

/**
 *  添加文档到iCloud
 *
 *  @param fileName 文件名称(不包括后缀)
 */
-(void)addDocument:(NSString *)fileName{
    //取得保存URL
    fileName=[NSString stringWithFormat:@"%@.txt",fileName];
    NSURL *url=[self getUbiquityFileURL:fileName];

    /**
     创建云端文档操作对象
     */
    KCDocument *document= [[KCDocument alloc]initWithFileURL:url];
    [document saveToURL:url forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
        if (success) {
            NSLog(@"保存成功.");
            [self loadDocuments];
            [self.tableView reloadData];
            self.document=document;
            [self performSegueWithIdentifier:@"noteDetail" sender:self];
        }else{
            NSLog(@"保存失败.");
        }

    }];
}

/**
 *  加载文档列表
 */
-(void)loadDocuments{
    [self.dataQuery startQuery];
}
/**
 *  获取数据完成后的通知执行方法
 *
 *  @param notification 通知对象
 */
-(void)metadataQueryFinish:(NSNotification *)notification{
    NSLog(@"数据获取成功!");
    NSArray *items=self.dataQuery.results;//查询结果集
    self.files=[NSMutableDictionary dictionary];
    //变量结果集,存储文件名称、创建日期
    [items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSMetadataItem *item=obj;
        NSString *fileName=[item valueForAttribute:NSMetadataItemFSNameKey];
        NSDate *date=[item valueForAttribute:NSMetadataItemFSContentChangeDateKey];
        NSDateFormatter *dateformate=[[NSDateFormatter alloc]init];
        dateformate.dateFormat=@"YY-MM-dd HH:mm";
        NSString *dateString= [dateformate stringFromDate:date];
        [self.files setObject:dateString forKey:fileName];
    }];
    [self.tableView reloadData];
}

-(void)removeDocument:(NSString *)fileName{
    NSURL *url=[self getUbiquityFileURL:fileName];
    NSError *error=nil;
    //删除文件
    [[NSFileManager defaultManager] removeItemAtURL:url error:&error];
    if (error) {
        NSLog(@"删除文档过程中发生错误,错误信息:%@",error.localizedDescription);
    }
    [self.files removeObjectForKey:fileName];//从集合中删除
}


#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.files.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *identtityKey=@"myTableViewCellIdentityKey1";
    UITableViewCell *cell=[self.tableView dequeueReusableCellWithIdentifier:identtityKey];
    if(cell==nil){
        cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identtityKey];
        cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator;
    }
    NSArray *fileNames=self.files.allKeys;
    NSString *fileName=fileNames[indexPath.row];
    cell.textLabel.text=fileName;
    cell.detailTextLabel.text=[self.files valueForKey:fileName];
    return cell;
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        UITableViewCell *cell=[self.tableView cellForRowAtIndexPath:indexPath];
        [self removeDocument:cell.textLabel.text];
        [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{
    UITableViewCell *cell=[self.tableView cellForRowAtIndexPath:indexPath];
    NSURL *url=[self getUbiquityFileURL:cell.textLabel.text];
    self.document=[[KCDocument alloc]initWithFileURL:url];
    [self performSegueWithIdentifier:@"noteDetail" sender:self];
}

@end

当新增一个笔记或选择一个已存在的笔记后可以查阅、保存笔记内容。

//
//  ViewController.m
//  kctest
//
//  Created by Kenshin Cui on 14/4/5.
//  Copyright (c) 2015年 cmjstudio. All rights reserved.
//

#import "KCDetailViewController.h"
#import "KCDocument.h"
#define kSettingAutoSave @"com.cmjstudio.kctest.settings.autosave"

@interface KCDetailViewController ()
@property (weak, nonatomic) IBOutlet UITextView *textView;

@end

@implementation KCDetailViewController
#pragma mark - 控制器视图方法
- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupUI];
}

-(void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    //根据首选项来确定离开当前控制器视图是否自动保存
    BOOL autoSave=[[NSUbiquitousKeyValueStore defaultStore] boolForKey:kSettingAutoSave];
    if (autoSave) {
        [self saveDocument];
    }
}

#pragma mark - 私有方法
-(void)setupUI{
    UIBarButtonItem *rightButtonItem=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(saveDocument)];
    self.navigationItem.rightBarButtonItem=rightButtonItem;

    if (self.document) {
        //打开文档,读取文档
        [self.document openWithCompletionHandler:^(BOOL success) {
            if(success){
                NSLog(@"读取数据成功.");
                NSString *dataText=[[NSString alloc]initWithData:self.document.data encoding:NSUTF8StringEncoding];
                self.textView.text=dataText;
            }else{
                NSLog(@"读取数据失败.");
            }
        }];
    }
}
/**
 *  保存文档
 */
-(void)saveDocument{
    if (self.document) {
        NSString *dataText=self.textView.text;
        NSData *data=[dataText dataUsingEncoding:NSUTF8StringEncoding];
        self.document.data=data;
        [self.document saveToURL:self.document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
            NSLog(@"保存成功!");
        }];
    }
}

@end

到近来为止都是关于咋样选拔iCloud来保存文档的情节,上边也提到过还是可以动用iCloud来保存首选项,这在许多情形下一般很有用,尤其是对于开发了三星版又开发了华为平板版的应用,假诺用户在一台设备上举行了首选项配置之后到另一台装备上也能运用是何等漂亮的体会啊。比较文档存储,首选项存储要简明的多,在下边“kctest.entitlements”中可以观望首选项配置并非像文档一样可以涵盖四个容器,那里唯有一个Key-Value
Store,平日使用NSUbiquitousKeyValueStore的defaultStore来赢得,它的应用办法和NSUserDefaults大致统统等同,当键值对存储爆发变化后方可因此NSUbiquitousKeyValueStoreDidChangeExternallyNotification等收获对应的打招呼。在下面的笔记应用中有一个”设置“按钮用于安装退出笔记详情视图后是否自动保存,那一个选项就是经过iCloud的首选项来囤积的。

//
//  KCSettingTableViewController.m
//  kctest
//
//  Created by Kenshin Cui on 14/4/5.
//  Copyright (c) 2015年 cmjstudio. All rights reserved.
//

#import "KCSettingTableViewController.h"
#define kSettingAutoSave @"com.cmjstudio.kctest.settings.autosave"

@interface KCSettingTableViewController ()
@property (weak, nonatomic) IBOutlet UISwitch *autoSaveSetting;

@end

@implementation KCSettingTableViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupUI];
}

#pragma mark - UI事件
- (IBAction)autoSaveClick:(UISwitch *)sender {
    [self setSetting:sender.on];
}

#pragma mark - 私有方法
-(void)setupUI{
    //设置iCloud中的首选项值
    NSUbiquitousKeyValueStore *defaults=[NSUbiquitousKeyValueStore defaultStore];
    self.autoSaveSetting.on= [defaults boolForKey:kSettingAutoSave];
    //添加存储变化通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(
    keyValueStoreChange:) name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:defaults];
}
/**
 *  key-value store发生变化或存储空间不足
 *
 *  @param notification 通知对象
 */
-(void)keyValueStoreChange:(NSNotification *)notification{
    NSLog(@"Key-value store change...");
}

/**
 *  设置首选项
 *
 *  @param value 是否自动保存
 */
-(void)setSetting:(BOOL)value{
    //iCloud首选项设置
    NSUbiquitousKeyValueStore *defaults=[NSUbiquitousKeyValueStore defaultStore];
    [defaults setBool:value forKey:kSettingAutoSave];
    [defaults synchronize];//同步
}
@end

运作效果:

图片 38

专注:所有的囤积到iCloud的文档、首选项都是首先存储到地点,然后通过daemon进度同步到iCloud的,保存、读取的文书都是地方同步副本并不一定是真正的iCloud存储文件。

顺便说一句,评判是个东瀛人,得亏有了她,这一场交锋的意味深长和观赏性才能得以有限支撑。

Passbook

Passbook是苹果推出的一个管理登机牌、会员卡、电影票、打折券等音信的工具。Passbook就好像一个卡包,用于存放你的购物卡、积分卡、电影票、礼品卡等,而这么些单据就是一个“Pass”。和情理票据不相同的是您可以动态更新Pass的音信,提醒用户降价券即将过期;甚至若是您的Pass中涵盖地理地方信息的话当你到达某个集团还足以动态提示用户方今供销社有啥种降价活动;当用户将一张团购券添加到Passbook之后,用户到了公司之后Passbook可以自行弹出团购券,店员扫描之后进行消费、积分等等都是Passbook的行使场景。Passbook可以管理多类票据,苹果将其分割为五类:

  1. 登机牌(Boarding pass)
  2. 优惠券(Coupon)
  3. 活动票据、入场券(伊夫nt ticket)
  4. 购物卡、积分卡(Store Cards)
  5. 一般说来票据(自定义票据)(Generic pass)

苹果的分割一方面由于差别票据功效及体现新闻不一样,另一方面也是为着统一票据的设计,上面是苹果官方关于五种票据的布局规划布局:

图片 39

既是一个票据就是一个Pass,那么怎么着是Pass呢?在iOS中一个Pass其实就是一个.pkpass文件,事实上它是一个Zip压缩包,只是这一个压缩包要鲁人持竿一定的目录结构来规划,上面是一个Pass包的目录结构(注意分裂的票证类型会方便删减):

Pass Package

├── icon.png

├── icon@2x.png

├── logo.png

├── logo@2x.png

├── thumbnail.png

├── thumbnail@2x.png

├── background.png

├── background@2x.png

├── strip.png

├── strip@2x.png

├── manifest.json

├── fr.lproj

│ └── pass.strings

├── de.lproj

│ └── pass.strings

├── pass.json

└── signature

也就是说在Passbook应用中显得的情节其实就是一个比照地方文件列表来公司的一个压缩包。在.pkpass文件中除了图标icon、缩略图thumbnail和logo外最重视的就是pass.json、manifest.json和signature。

1.pass.json

本条文件讲述了Pass的布局、颜色设置、文本描述新闻等,也就是说具体Pass包怎么着呈现实在就是透过那些JSON文件来布署的,关于pass.json的现实性配置项在此不再一一介绍,大家可以查看苹果官方接济文档“Pass
Design and
Creation
”。那里根本说一下里面重大的多少个布局项:

passTypeIdentifier:pass唯一标识,这些值类似于App
ID,须要从开发者中央创建,并且那个标识必须以“pass”早先(例如上面的示范中取名为“pass.com.cmjstudio.mypassbook”)。

teamIdentifier:团队标识,申请苹果开发者账号时会分配一个唯一的集体标识(可以在苹果开发者主旨–查看账户新闻中查阅”Team
ID“)。

barcode:二维码音信配置,主要指定二维码内容、类型、编码格式。

locations:地理地方新闻,可以安顿相关职责的公文音信。

2.manifest.json

manifest.json从名称可以看到那一个文件重大用来讲述当前Pass包中的文件目录协会结构。这么些文件记录了除“manifest.json”、“signature”外的文件和呼应的sha1哈希值(注意:哈希值可以经过”openssl
sha1 [ 文件路径]
“命令得到)。

3.signature

signature是一个签名文件。就算manifest.json存储了哈希值,可是大家都领悟hash算法是当众的,怎么样保管一个pass包是合法的,未经修改的呢?那就是应用一个签名文件来验证。

打探了上述内容后基本上对于怎么定义一个pass包有了简便易行的定义。有了pass包之后对于添加pass到passbook应用是相比简单的。但实际上经常大家收看的passbook应用中丰富的pass包并不是手动社团的,而是通进度序来落成pass包制作的。举例来说:即使您在美团上选购一张电影票之后,会报告你一个让利码,这一个让利码会呈现到pass中。由于这一个降价码是动态变化的,所以直接手动制作出一个pass包是不具体的。经常状态下pass包的生路易港是通过后台服务器动态变化,然后重返给iOS客户端来读取和添加的,手动制作pass包的景观是比较少的,除非您的契约音讯是形影不离的。当然为了演示Passbook应用,那里仍然会以手动格局演示一个pass包的变化进程,明白了那么些进程之后相信在劳动器端通过一些后台程序生成一个pass包也无足挂齿(上面的更动进度均可通过劳务器端编程来落成)。

同别的Apple服务付出近乎,做Passbook开发同样须要一些准备干活:

  1. 在苹果开发者主题新建Pass Type
    ID(例如那里新建一个“pass.com.cmjstudio.mypassbook”),并且根据这些Pass
    Type
    ID创设一个Passbook证书(在mac上找到钥匙串,选取”从证书颁发机构请求证书“,生成一个证书请求文件;将此文件上传到相应的Pass
    Type
    ID下生成证书文件)如下图:图片 40

    下载证书后,将此证书导入Mac中(此处配置的Pass Type
    ID对应pass.json中的”passTypeIdentitifier“,此证书用于转移签名文件signature。)。

  2. 在Xcode中-Targets-Capabilities启用Pasbook服务,那里须要留意的是”Allow
    all team pass
    types“选项,假若勾选了这一项,那么pass.json中的passTypeIdentifier和teamIdentifier就可以是其余团体创建的别样Pass项目了,那里运用前边创设的门类,所以选拔”Allow
    subset of pass
    types“。图片 41

有了上面的备选干活,上面看一下什么样创制一个Pass:

  1. 根据所接纳的Passbook类型准备图片素材,由于此地以一个Store
    Card举例,所以要求未雨绸缪icon、logo和strip三类图片。
  2. 布局pass.json,这里照旧强调一下passTypeIdentifier和teamIdentifier,前者就是地方在开发者中央创立的Pass
    Type
    ID(”pass.com.cmjstudio.mypassbook“),后者是对应的团协会标识,其余新闻依据真实情形计划。

    {
        "formatVersion":1,
        "passTypeIdentifier":"pass.com.cmjstudio.mypassbook",
        "serialNumber":"54afe978584e3",
        "teamIdentifier":"JB74M3J7RY",
        "authenticationToken":"bc83dde3304d766d5b1aea631827f84c",
        "barcode":{"message":"userName KenshinCui","altText":"会员详情见背面","format":"PKBarcodeFormatQR","messageEncoding":"iso-8859-1"},
        "locations":[
                     {"longitude":-122.3748889,"latitude":37.6189722},{"longitude":-122.03118,"latitude":37.33182}],
        "organizationName":"CMJ Coffee",
        "logoText":"CMJ Coffee",
        "description":"",
        "foregroundColor":"rgb(2,2,4)",
        "backgroundColor":"rgb(244,244,254)",
        "storeCard":{
            "headerFields":[{"key":"date","label":"余额","value":"¥8888.50"}],
            "secondaryFields":[{"key":"more","label":"VIP会员","value":"Kenshin Cui"}],
            "backFields":[
                          {"key":"records","label":"消费记录(最近10次)","value":" 9/23    ¥107.00     无糖冰美式\n 9/21    ¥58.00      黑魔卡\n 8/25    ¥44.00      魔卡\n 8/23    ¥107.00     无糖冰美式\n 8/18    ¥107.00     无糖冰美式\n 7/29    ¥58.00      黑魔卡\n 7/26    ¥44.00      魔卡\n 7/13    ¥58.00      黑魔卡\n 7/11    ¥44.00      魔卡\n 6/20    ¥44.00      魔卡\n"},
                          {"key":"phone","label":"联系方式","value":"4008-888-88"},
                          {"key":"terms","label":"会员规则","value":"(1)本电子票涉及多个环节,均为人工操作,用户下单后,1-2个工作日内下发,电子票并不一定能立即收到,建议千品用户提前1天购买,如急需使用,请谨慎下单; \n(2)此劵为电子劵,属特殊产品,一经购买不支持退款(敬请谅解); \n(3)特别注意:下单时请将您需要接收电子票的手机号码,填入收件人信息,如号码填写错误,损失自负;购买成功后,商家于周一至周五每天中午11点和下午17点发2维码/短信到您手机(周六至周日当天晚上发1次),请用户提前购买,凭此信息前往影院前台兑换即可; \n(4)订购成功后,(您在购买下单后的当天,给您发送电子券,系统会自动识别;如果您的手机能接收二维码,那收到的就是彩信,不能接收二维码的话,系统将会自动转成短信发送给您),短信为16位数,如:1028**********; 每个手机号码只可购买6张,如需购买6张以上的请在订单附言填写不同的手机号码,并注明张数(例如团购10张,1350755****号码4张,1860755****号码6张);\n(5)电子票有效期至2016年2月30日,不与其他优惠券同时使用"},
                          {"key":"support","label":"技术支持","value":"http://www.cmjstudio.com\n\n                                            \n                                            \n                                          "}]
        },
        "labelColor":"rgb(87,88,93)"
    }
    
  3. 按照pass所需文件创立manifest.json文件,可以通过”openssl sha1
    [文件路径]“分别计算出所有文件的哈希值:

    {
        "pass.json":"3292f96c4676aefe7122abb47f86be0d95a6faaf",
        "icon@2x.png":"83438c13dfd7c4a5819a12f6df6dc74b71060289",
        "icon.png":"83438c13dfd7c4a5819a12f6df6dc74b71060289",
        "logo@2x.png":"83438c13dfd7c4a5819a12f6df6dc74b71060289",
        "logo.png":"83438c13dfd7c4a5819a12f6df6dc74b71060289",
        "strip@2x.png":"885ff9639c90147a239a7a77e7adc870d5e047e2",
        "strip.png":"885ff9639c90147a239a7a77e7adc870d5e047e2"
    }
    
  4. 接下去下来准备生成signature文件:
    a.通过后面导入的Pass Type证书(Pass Type
    ID:pass.com.cmjstudio.mypassbook)导出个人音信交流(.p12)文件并指定密码(假诺密码为456789),保存成”mypassbook.p12“(注意是导出证书而不是导出证书下的专用秘钥)。
    b.在钥匙串中找到”Apple Worldwide Developer Relations Certification
    Authority“证书导出增强保密邮件(.pem),保存成”AWDRCA.pem“。
    c.将.p12注明转化为.pem证书mypassbook.pem(须求输入导出时设置的密码456789),输入如下命令:

    openssl pkcs12 -in mypassbook.p12 -clcerts -nokeys -out mypassbook.pem -passin pass:456789
    

    d.从.p12导出秘钥文件mypassbookkey.pem(那里安装密码为123456):

    openssl pkcs12 -in mypassbook.p12 -nocerts -out mypassbookkey.pem -passin pass:456789 -passout pass:123456
    
e.根据AWDRCA.pem、mypassbook.pem、mypassbookkey.pem、manifest.json生成signature文件(按照提示输入mypassbookkey.pem导出时设置的密码123456):

    openssl smime -binary -sign -certfile AWDRCA.pem -signer mypassbook.pem -inkey mypassbookkey.pem -in manifest.json -out signature -outform DER
  1. 将icon.png、icon@2x.png、logo.png、logo@2x.png、strip.png、strip@2x.png 、pass.json、manifest.json、signature压缩成pass包(那里命名为”mypassbook.pkpass“)。

    zip -r mypassbook.pkpass manifest.json pass.json signature logo.png logo@2x.png icon.png icon@2x.png strip.png strip@2x.png
    

到此处一个pass制作落成了,此处可以在mac中开拓预览:

图片 42

到此处一个Pass就只做形成了,上边就看一下在iOS中怎么样添加这几个Pass到Passbook,那里直接将上边制作形成的Pass放到Bundle中做到拉长。当然那么些都是一步步手动达成的,后面也说了实际支付中那一个Pass是服务器端来动态变化的,在加上时会从服务器端下载,那么些进度在演示中就不再演示。iOS中提供了Pass基特.framework框架来拓展Passbook开发,上边的代码演示了添加Pass到Passbook应用的进程:

//
//  ViewController.m
//  Passbook
//
//  Created by Kenshin Cui on 14/4/5.
//  Copyright (c) 2015年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <PassKit/PassKit.h>

@interface ViewController ()<PKAddPassesViewControllerDelegate>
@property (strong,nonatomic) PKPass *pass;//票据
@property (strong,nonatomic) PKAddPassesViewController *addPassesController;//票据添加控制器
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}
#pragma mark - UI事件
- (IBAction)addPassClick:(UIBarButtonItem *)sender {
    //确保pass合法,否则无法添加
    [self addPass];
}

#pragma mark - 属性
/**
 *  创建Pass对象
 *
 *  @return Pass对象
 */
-(PKPass *)pass{
    if (!_pass) {
        NSString *passPath=[[NSBundle mainBundle] pathForResource:@"mypassbook.pkpass" ofType:nil];
        NSData *passData=[NSData dataWithContentsOfFile:passPath];
        NSError *error=nil;
        _pass=[[PKPass alloc]initWithData:passData error:&error];
        if (error) {
            NSLog(@"创建Pass过程中发生错误,错误信息:%@",error.localizedDescription);
            return nil;
        }
    }
    return _pass;
}

/**
 *  创建添加Pass的控制器
 *
 *  @return <#return value description#>
 */
-(PKAddPassesViewController *)addPassesController{
    if (!_addPassesController) {
        _addPassesController=[[PKAddPassesViewController alloc]initWithPass:self.pass];
        _addPassesController.delegate=self;//设置代理
    }
    return _addPassesController;
}

#pragma mark - 私有方法
-(void)addPass{
    if (![PKAddPassesViewController canAddPasses]) {
        NSLog(@"无法添加Pass.");
        return;
    }

    [self presentViewController:self.addPassesController animated:YES completion:nil];
}

#pragma mark - PKAddPassesViewController代理方法
-(void)addPassesViewControllerDidFinish:(PKAddPassesViewController *)controller{
    NSLog(@"添加成功.");
    [self.addPassesController dismissViewControllerAnimated:YES completion:nil];
    //添加成功后转到Passbook应用并展示添加的Pass
    NSLog(@"%@",self.pass.passURL);
    [[UIApplication sharedApplication] openURL:self.pass.passURL];
}
@end

运转效果:

图片 43

留神:若是我们对此Pass不是太熟稔,刚初阶可以动用部分创造工具来完结Pass制作,例如:PassSource、国内的PassQuan等都帮助在线制作Pass包。

假定换成国内某“金哨”——只会吹胡子瞪眼那种的,恐怕比赛为止后,双方各自只剩余几个人握手了。

由于涉及到亚冠名额,那一个足协杯亚军依旧很有含金量的,在联赛大局已定的前提下,申花到南美洲赛场的唯一一条路,就是攻占足协杯亚军。

而对于新加坡上港来说,那一个亚军是好高骛远的博阿斯在2017赛季最后的遮挡,假诺得到那几个亚军,某些官员的年底统计报告仍能将就看,若是拿不到,嘿嘿……

除此以外,默默为博阿斯祈祷的还有佩莱格里尼,他的吉林中华能不可以搭上欧洲末班车,全看上港能否争夺第一名。毕竟,大家都有官员,年初都要写报告的。

理所当然,就全国看球的观众来说,如果申花能维持这么的打法和气度,那进去亚冠再好可是,毕竟二十年的强暴,“圣鲁迅公园”的看台,都是为难复制的。

说实话,若不是草坪管理层的恶行,这个赛季断然不至于如此为难。

幸运的是在吴金贵治下,东京申花就如找到了属于自己的灵魂,二十多年的霸气血液,也在渐渐流淌起来。

传说,博卡老总也来临了现场,为的是和申花交涉一下特维斯回阿根廷的事,对于被“球星”折磨了一赛季的蓝魔们,那倒是个好音信。

以阿根廷人做工作的鸡贼,加上绿地管理层的平庸,那笔交易肯定是赔了,但起码送走了“球星”,以申花如今的社团,引援得力的话,下赛季复兴可期。

不顾,小胜竞技才是硬道理。两遍合的较量,只走了一半,还有九十分钟,要在八万人篮体育场见分晓,让我们拭目以待。

这几年,申花与上港始终在争一件事:到底什么人更有资格代表新加坡?

比方从前天的本场交锋来看的话,半场保持着积极的压迫力的申花,显明更有资格,至少他们是用作一支球队存在的。

而对面,即便,在第93分钟的时候,奥斯卡(奥斯卡(Oscar))拿球时的轻易与骄傲,也都让你搞不清哪边在超过,我想他大致是明亮错了新加坡式的腔调。

对每一个陶冶来说,没有毛剑卿柏佳骏式的倾尽全力,胡尔克奥斯卡(Oscar)式的天赋与才情将会半文不值。

提议博阿斯读一下张爱玲的《流言》,第一篇“天才梦”是张爱玲十六岁时写的,四十岁的博阿斯还在梦里没有苏醒。

第二篇“到底是巴黎人”,可以让博阿斯明白一下那座城池,以及这座城池里的大千世界,他们的唱腔以及她们的荣幸。

很扎眼,你的那种足球,是配不上他们的梦想的。

究竟,是香港人呀。

2017.11.20

(所有图片源于于网络,如侵删。)