新普金娱乐网址


IOS框架和劳动

拥抱不完美:认回自己的故事疗愈之同

地理2017面试小结

  • 十月 18, 2018
  • 地理
  • 没有评论

部分面试题分享


  1. 干什么离职?
  2. iOS中的多线程的得失? NSThread,NSOperation,GCD.
  3. NSOperation和GCD对比,什么状态下用NSOperation做起来较好,但是GCD做起来不极端好?相反的口舌出邪?
  4. 发生一个格外酷的数目,不关注数组遍历的相继,OC中发出几种植遍历的逐一?->
    dispath_apply
  5. CoreData的最底层实现原理,和办事流程,是怎么工作之? Ream,WCDB,
    FMDB这点儿单了解也? SQLite优缺点对比~~
  6. KVO实现的脚原理。
  7. 你们项目里面用到了加密,你了解RSA加密方法是啊为?
    还是有MD5,和base64是什么?
  8. Http和 (TCP、UDP)有啊界别?然后TCP、UDP之间有什么区别?
  9. Socket了解多少?有使为?、应用场景?
  10. layer层和UIView的区别?
  11. iOS在柜项目的军事管制是怎管理之?
  12. Self和super两单重要字介绍?
  13. 消息发送机制讲一下?

1.线程锁有了解也?
2.提一下循环引用,怎么造成的轮回引用,然后怎么解决?
3.为什么__weak可以跟解决循环引用? __Strong呢?
__weak和__Strong有什么分别的吧?
4.Weak每当自由的时候怎么处理?会对援计数器加一为?
5.runtime底体制了解也?

6.平淡无奇遇到奔溃怎么解决? 如果一定不交,怎么解决?
有没来纪念了怎么形容一个友好的上传奔溃日志的成效。


  1. Block的底部实现原理?
  2. 代办,block,通知之怎么取舍去行使?
  3. 岂去检查循环引用?
  4. weak的实现原理,weak会计数器增加吗?assign会计数器增加吗?
  5. NSURLSession,NSURLConnection的区别?
  6. NSThread,NSOPeration和GCD怎么去摘用哪个?
  7. Charles抓包,https还好为?
  8. 网络通讯,TCP、UDP、Socket了解也?
  9. 发因此Socket做了一些物吧?
  10. 网络安全者/接口方面做了什么处理?

  1. 做了怎么有风味的品类? 或者项目里面怎么有特点之事物?
  2. UItableView举行了哪些优化?
  3. FMDB本身读取数据,有什么样优化? /主键
  4. 数据库,查询语句,有无产生查询比较复杂的类似于查询两单特性的及凡多少这样
  5. 胡开要,用AFN代码,为底非用另外的框架为?
  6. 网要设置协议头,知道怎么设置也?
    比如请求图片,请求json是啊协议头?
  7. Get,post有啊分别?
  8. http和https的区别? Https: SSL
  9. 做网络有数据安全处理?安全的片段,比如数据要,Get,post,数据传等等这些有,怎么长安全性?
    -> https不够,然后还要?
  10. Https苹果推荐,然后应该深入了解一下?
  11. NOTification是线程同步还是异步的?
  12. NOTification必须用remove吗?会生什么问题吗?
    比如说页面被生10个Nofication,那么当这销毁的时刻,用removeObserve移除所有的通会见出啊问题啊?还是单个一个一个之remove掉?
  13. block声明的时,和动的时都产生啊要留意的?
  14. xib和StoryBoard
  15. const和#define的区别?

  1. iOS开发三年解决的一个无比困难的一个问题?/
    工作吃谁代码改进要么技术实现叫您充分有成就感?
  2. 类框架在数据层,在网络规模产生包吗? 做此框架为最老之问题?
  3. 协调举行下的斯东西,比如说你包装的是BaseViewController,有无起评估了您是方案做的好不好?业内大家都是怎么开的?性能如何?
    比如说滴滴,美团,百度,今日头修之好之做法,他们是怎开的。—》
    侧重问你,反编译。黑盒试探,黑盒探测
  4. 君怎么保您开出来的事物,你本之开源社区开源的那些同类型框架而好???-》研究是什么?
  5. 柜呆了简单年半,然后为什么换工作? 对生同样份工作来什么想?
    为什么进大之铺会上?
  6. 说技术之成人,你平常会晤开呀事情,提升自己之技术?
    8.页面上有子视图,比如说100独,上边来圆角,怎么化解卡顿?(用图片替换圆角并无是那的好?)-
    照片墙,每一个照的尺码未一致,100张,并且有圆角,很卡顿,那么我们怎么处理及时一百布置图片与一百只圆角?
  7. coreGrahpis,coreAnaimation,coewImage这三单仓库的别?
    绘制圆角用底是甚库??
  8. 万一圆角的题目迎刃而解,但是横向很快的拖动的早晚,页面要加载很多图形的当儿会卡顿,怎么解决????
  9. 淘宝换肤的效果,主题包括如何东西(文件,应用字体样式,小的Icon等等)等视野视野可见的素都使转移,怎么替换?
    —貌似runtime不可行。
  10. atutoRelease作用什么?只以ARC中生?在MRC中从不?为什么?
    atutoRelease怎么掌握此目标的生命周期可以结束,可以给放飞?(怎么准确了解不克早,早了不畏是野指针,晚了就算是内存泄露。)
  11. Runloop是召开啊的?
  12. 信机制,消息转发和谁有关联?归Runloop还是Runtime?
    信转发的老三只点子,做了哟?具体说一下。
  13. OC是动态语言,表现在哪些方面? 方法,属性,
  14. Runtime具体是独什么事物?他当什么会发挥作用?
  15. Runtime除了消息机制意外,还是发生怎样应用场景?
  16. weak属性有是那特性?置为nil怎么落实的?
    哈希表的key和value分别存的哎?举个例证: 比如 100只weak
    指针指向一个对象A, 那么value存的是啊?
  17. KVO用了为?怎么落实的?怎么当性变化之时节将到通知的?
    (A是观察者,观察属性B的浮动,B的习性了,A怎么掌握的?谁去通知之?如何打招呼之?针对NSKVONotifying_A做了争东西?)
  18. iOS消息推送怎么落实的?(我于您犯个微信,微信为你作个消息)
    ANPS是呀?怎么形成推送实时性,立马会收?断网的事态下能够收吗?长链接是手机及每个应用还保持与苹果服务器保持增长连为?
    信息推送在安卓手机上是怎么落实之? 安卓手机上闹APNS这个事物吗?
    苹果做信息推送的功利是呀?
  19. 收受信推送之后,怎么开展跨反到一个新页面?
  20. iOS10后头对信息推送做了争心得酷炫的物?
    24.反转一个二叉树?代码写出来 | 实现一个二叉树?
    25.反转一个单链表? 代码写出来

蓝牙

乘势蓝牙低功耗技术BLE(Bluetooth Low
Energy)的上扬,蓝牙技巧在一步步秋,如今的绝大多数移动设备还配置有蓝牙4.0,相比前的天蓝牙技术耗电量大大降低。从iOS的发展史也不难看出苹果时针对蓝牙技术吧是越关心,例如苹果被2013年9月宣告之iOS7哪怕安排了iBeacon技术,这项技术完全依据蓝牙传输。但是明显苹果之设备对权力要求呢是比大之,因此在iOS中并无克如Android一样自由动用蓝牙进行文件传输(除非您都越狱)。在iOS中进行蓝牙传输应用开发常用之框架来如下几栽:

GameKit.framework:iOS7事先的蓝牙通讯框架,从iOS7初步过,但是时大部分运用或因这个框架。

MultipeerConnectivity.framework:iOS7方始引入的新的蓝牙通讯支出框架,用于代替GameKit。

CoreBluetooth.framework:功能强大的天蓝牙开框架,要求配备必须支持蓝牙4.0。

前方片只框架下起来比较简单,但是缺点也于显著:仅仅支持iOS设备,传输内容才限于沙盒或者照片库中用户选择的文书,并且第一只框架只能当与一个运中开展传输(一个iOS设备安装应用A,另一个iOS设备上安装应用B是无法传输的)。当然CoreBluetooth就摆脱了这些束缚,它不再局限为iOS设备中进行传输,你得透过iOS设备向Android、Windows
Phone以及任何装有蓝牙4.0芯片的智能设备传输,因此呢是当前智能家居、无线支付等叫座智能设备所尊重的技巧。

面试题集锦推荐

1.《招聘一个乘谱的
iOS》—参考答案
2.
iOS相关腾讯,阿里P6生厂面试题
3.《2017年iOS面试题总结》
4.《iOS面试汇总》
5.
《2017年5月iOS招人心得(附面试题》》

除此以外,
面试大厂的话,我道起码得把大厂的明技术分享博客看一样合吧,比如美团点评团的美团点评技术集团
腾讯Bugly等等。当然,即经常莫面试大厂,日常上,你吗应看这些。

  1. 调用系统运用
  2. 动系统服务
    1. 短信及邮件
    2. 通讯录
    3. 蓝牙
    4. 社交
    5. Game
      Center
    6. 下内请
    7. iCloud
    8. Passbook
  3. 目 录
  • “面试,有早晚之技艺在中,但是再次主要的在于平时的积聚。” ——
    美团面试官的同一句话。
  • “临阵磨枪可以于您找到同样份工作,但是进入大厂的总人口,绝对免是临阵磨枪,刷面试题进去的。”
    —— 我本着上面这句话的解读。

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)。 地理 1
  2. 根据“com.cmjstudio.kctest”创建开发者配置文件(或描述文件)并导入对应的装备(创建过程被选择支持Game
    Center服务之App ID,这样iOS设备在运行指定Boundle
    ID应用程序就理解者采取支撑Game
    Center服务)。 地理 2
  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分别吗“AdidasGoldBall”、“AdidasGoldBoot”代表金球奖与金靴奖成就,点数分别吗80、100)。地理 3
  6. 当iOS“设置”中找到Game
    Center允许沙盒,否则真机无法调试(如果是模拟器不需之桩设置)。 地理 4

出矣上述准备便好当应用程序中追加积分、添加成就了,当然在实质上支付进程积分和得还是依据玩家所经之关卡来就的,为了简化这过程这里就是直通过几个按钮手动触发这些事件。Game
Center开发需要使用GameKit框架,首先熟悉一下常用之几独八九不离十:

GKLocalPlayer:表示当地玩家,在GameKit中还有一个GKPlayer表示并玩家,为了保证非联网用户也得以正常使用游戏效果,一般用GKLocalPlayer。

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

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

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

GKAchievementDescription:成就描述信息,包含成就的题目、获得前描述、获得后描述、是否可还获得成功等消息。

GKGameCenterViewController:排行榜、成就查看视图控制器。如果使用本身不欲自己付出排行榜、成就查看试图可以直接调用此控制器。

下就是以一个简约的以身作则来形成排行榜、成就设置及查,在这个演示程序中通过简单种植方式来查看排行暨完成:一栽是直采用框架自带的GKGameCenterViewContrller调用系统视图查看,另一样种是通过API自己读取排行榜、成就信息并出示。此外在动用中有个别只长按钮分别用于安装得分与形成。应用大致布局如下(图片于生可点击查阅大图):

地理 5

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

运行效果:

地理 6

注意首涂鸦采用游戏时由并未指向Game Center授权,会唤醒用户登录Game
Center。

备忘 (你可忽略)

  1. nssring,nsarray的特性用copy,就相当于是将传播的多少copy了一如既往份。传入的多寡在别处修改的时节,不会见潜移默化性的变化。??
    属性加property和 [mutableArray copy]这种无是一个意
    property+copy是发出一个不可变的数组。
    iOS开发 深拷贝与浅拷贝 https://www.jianshu.com/p/f01d490401f9

社交

正题

2017年当是当做过渡的均等年,到过年年后开摸索下下。没悟出11月8如泣如诉猝不及防的风吹草动,打乱了本来的计划。主动请辞之后,赶上了京城iOS还有人如果的好上。
自己毕业的上,内推的上家公司,也并不曾面试的涉。虽然后期招人面试了人家,但是到温馨摸索工作的时节,自己立即面试太简单了。而且,一年左右,市场对用人的要求为还胜。故此,把面试两圆满之经历以及参考的有些比好的文章分享给大家。

总得来说,对于三年之iOS开发,大厂对iOS底层,基础(内存),项目之习性优化及工具使用,数据结构和算法,逆向,基本上iOS除了动画之外的持有情节还见面产生要求。
当然,小公司本不会见要求这么多,但是自己当还是你应有尝试着进好厂。
算法,逆向这些下不涉,你可以更搜索。

iOS开发过程被有时难免会以iOS内置的组成部分使软件和劳务,例如QQ通讯录、微信电话仍会动iOS的通讯录,一些叔着软件会当使内发送短信等。今天将和大家一齐上学怎样行使系统运用、使用系统服务:

较好的篇章

GCD中的dispatch_apply的用法及作用
http://www.cnblogs.com/denz/p/5218187.html
iOS 多线程开发GCD如何控制最充分并发数
http://www.jianshu.com/p/9d5417ae07d2
《招聘一个指谱的 iOS》—参考答案(上)
https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8A%EF%BC%89.md\#9-property%E4%B8%AD%E6%9C%89%E5%93%AA%E4%BA%9B%E5%B1%9E%E6%80%A7%E5%85%B3%E9%94%AE%E5%AD%97-property-%E5%90%8E%E9%9D%A2%E5%8F%AF%E4%BB%A5%E6%9C%89%E5%93%AA%E4%BA%9B%E4%BF%AE%E9%A5%B0%E7%AC%A6
iOS面试题:腾讯二面以及参照思路
http://www.jianshu.com/p/dd17bdcff9f7
深切理解Objective-C:Category:
https://tech.meituan.com/DiveIntoCategory.html
深切明RunLoop
https://blog.ibireme.com/2015/05/18/runloop/
iOS 保持界面流畅的技巧:
https://blog.ibireme.com/2015/11/12/smooth\_user\_interfaces\_for\_ios/
Objective-C 对象模型:
http://blog.leichunfeng.com/blog/2015/04/25/objective-c-object-model
iOS 程序执行顺序及 UIViewController 的生命周期 (整理):
https://juejin.im/entry/58b93b89a22b9d005ed8c58a
iOS单例模式 or NSUserDefaults:
http://yulingtianxia.com/blog/2014/04/07/iosdan-li-mo-shi-ornsuserdefaults/
Objective-C Runtime:
http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/
Objective-C中的信转发在切实可行实战中发出什么用?:
https://www.zhihu.com/question/55582425
iOS-离屏渲染详解.:
http://www.jianshu.com/p/57e2ec17585b
iOS 高效添加圆角力量实战讲解:
http://www.jianshu.com/p/f970872fdc22
安是地形容好一个界面:http://oncenote.com/2015/12/08/How-to-build-UI/、
绘制像从到屏幕上:
https://objccn.io/issue-3-1/
UIKit性能调优实战讲解:
http://www.jianshu.com/p/619cf14640f3
外存恶鬼drawRect:
http://bihongbo.com/2016/01/03/memoryGhostdrawRect/
UITableView的优化:
http://www.jianshu.com/p/93085c0de4c9
Core Graphics快速入门——从一行代码说由:
http://blog.csdn.net/abc649395594/article/details/46386219
iOS Core Graphics基础入门(3):
http://blog.csdn.net/MerryGOOT/article/details/51933899
iOS之用NSInvocation调用方法:
http://www.jianshu.com/p/e24b3420f1b4

笔试面试知识整理:https://hit-alibaba.github.io/interview/iOS/ObjC-Basic/Block.html
关押了还非懂HTTPS我直播吃翔:http://www.jianshu.com/p/ca7df01a9041
iOS中为什么不要用runtime才会当分拣中扩大属性呢
http://blog.csdn.net/u014041012/article/details/57406196
什么样科学采取const,static,extern:
http://www.jianshu.com/p/2fd58ed2cf55
HTTP协议被GET和POST方法的区别: 1)
https://www.zhihu.com/question/28586791
2)
https://sunshinevvv.coding.me/blog/2017/02/09/HttpGETv.s.POST/

NSURLSession与NSURLConnection区别:
http://www.jianshu.com/p/056b1817d25a

清晰理解Objective-C元类:
http://blog.csdn.net/beclosedtomyheart/article/details/50164353
iOS 之GCD串行和并发队排的掌握:
https://www.cnblogs.com/Ohero/p/4727000.html
2017年iOS面试题总结:http://www.jianshu.com/p/f9eb6b315c08
基础知识总结(一):
http://www.jianshu.com/p/5bec0aeb3137
Swift与OC真正去解Block解决循环引用的技术:
http://www.code4app.com/blog-864937-1150.html
runtime详解:
http://www.jianshu.com/p/46dd81402f63

招一个靠谱iOS:
https://dayon.gitbooks.io/-ios/content/chapter11.html
iOS 中 weak 的实现原理:
http://www.jianshu.com/p/2bf505398f51
iOS 底层解析weak的贯彻原理(包含weak对象的初始化,引用,释放的分析):
http://www.cocoachina.com/ios/20170328/18962.html
iOS 底层解析weak的贯彻原理(包含weak对象的初始化,引用,释放的解析):
http://www.jianshu.com/p/13c4fb1cedea:

socket编程:
https://segmentfault.com/q/1010000006771916
面试时,你叫提问到了 TCP/IP 协议也?:
https://juejin.im/post/58e36d35b123db15eb748856
不再安全之 OSSpinLock:
https://blog.ibireme.com/2016/01/16/spinlock\_is\_unsafe\_in\_ios/
YYCache 设计思路:
https://blog.ibireme.com/2015/10/26/yycache/

loadView,viewDidLoad及viewDidUnload:
http://www.jianshu.com/p/adfdd0fdb1f5
Runtime之Method Swizzling:
http://www.jianshu.com/p/934e6a98afba
Realm、WCDB和SQLite移动数据库性能比测试:
http://blog.csdn.net/cloudox\_/article/details/75012746

NS_ENUM 和 NS_OPTIONS 之间的差距是啊?:
http://www.itstrike.cn/Question/dc2f1712-927e-444f-9fdf-e4e51e4a103d.html
地理编码的定义:
https://www.cnblogs.com/xiwang/p/5831782.html

iOS安全支付防护摘要:
https://zhuanlan.zhihu.com/iOSRe/19646002
HTTP 是基于 TCP 还是 UDP
的?:https://www.zhihu.com/question/20085992
RunLoop总结:RunLoop的使用场景1,2,3,4,5:
https://www.jianshu.com/p/fee0c5155b8e
iOS循环引用: https://www.jianshu.com/p/13313baac740
iOS中之SEl和IMP到底是啊: https://www.jianshu.com/p/4a09d5ebdc2c
传层TCP和UDP的分分析以及运场景
:
http://blog.csdn.net/u013777351/article/details/49226101
iOS中缘何未要用runtime才能够当分拣中扩大属性呢
https://slpowercoder.github.io/2017/02/11/iOS%E4%B8%AD%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9D%9E%E8%A6%81%E7%94%A8runtime%E6%89%8D%E8%83%BD%E5%9C%A8%E5%88%86%E7%B1%BB%E4%B8%AD%E6%89%A9%E5%B1%95%E5%B1%9E%E6%80%A7%E5%91%A2/
iOS-内存管理之内存泄露爬坑记QAQ
:
http://lysongzi.com/2016/07/16/iOS-%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E4%B9%8B%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2%E7%88%AC%E5%9D%91%E8%AE%B0QAQ/
NSTimer定时器进阶——详细介绍,循环引用分析及解决
NSRunLoop原理详解——不再发盲点
各种线程锁: https://www.jianshu.com/p/35dd92bcfe8c
iOS底层-KVC使用实行与贯彻原理:
https://www.jianshu.com/p/fbd1e7c93fd0
iOS开发 — KVO的落实原理及具象行使
: https://www.jianshu.com/p/e59bb8f59302
RunLoop总结:RunLoop 与GCD 、Autorelease Pool之间的涉嫌:
https://www.jianshu.com/p/e259bf7ab297
iOS 中 weak 的贯彻原理: https://www.jianshu.com/p/2bf505398f51
iOS 底层解析weak的实现原理(包含weak对象的初始化,引用,释放的剖析):
https://www.jianshu.com/p/13c4fb1cedea
loadView,viewDidLoad及viewDidUnload:
https://www.jianshu.com/p/adfdd0fdb1f5
【如何科学采取const,static,extern】|那些人追逐的干货:
https://www.jianshu.com/p/2fd58ed2cf55
自在上之一--Objective-C消息转发:
https://www.jianshu.com/p/1bde36ad9938
iOS之动NSInvocation调用方法:https://www.jianshu.com/p/e24b3420f1b4
[iOS 知识总结二] 为什么说Objective-C 是同一派系动态的言语:
https://www.jianshu.com/p/7b395a8c3bd3
杂谈: MVC/MVP/MVVM: https://www.jianshu.com/p/eedbc820d40a
大抵线程编程1-NSThread
大抵线程编程2-NSOperation
基本上线程编程3 – NSOperationQueue .
http://blog.csdn.net/q199109106q/article/details/8566222
差不多线程编程4 – GCD
http://blog.csdn.net/q199109106q/article/details/8566300
[爆栈热门 iOS 问题] atomic 和 nonatomic 有什么分别?
https://www.baidu.com/link?url=C6q8vEXNajRNph60ustwwYuv6Q7IhSm9cCP5G3OQIDSNbEgTk4AII9ui5JO250l2&wd=&eqid=e8ff62480002d96a000000045a048728
iOS基础之 UIKit框架 全解析 . https://www.jianshu.com/p/a0e0075a75fe
离屏渲染:
http://blog.csdn.net/qq\_29846663/article/details/68960512
iOS-离屏渲染详解. https://www.jianshu.com/p/57e2ec17585b
@synthesize和@dynamic区别
Swift与OC真正去解Block解决循环引用的技术https://www.jianshu.com/p/bf2b8f278a81
因而@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能致什么问题?:
https://www.jianshu.com/p/f974cf4891ac
问题[※※]:objc中朝一个nil对象发送信息将会见有什么?
https://www.jianshu.com/p/b7cda433e4f5
__weak修饰详解及缓解循环引用使用
https://www.jianshu.com/p/a252fdd42603
iOS中block的循环引用问题: https://www.jianshu.com/p/492be28d63c4
block 循环引用问题的一些发散
https://www.swiftcafe.io/2017/02/02/weak-block/
iOS | 小心NSTimer中之巡回引用:
https://www.jianshu.com/p/2fe076e5e255
iOS 循环引用的季栽情况 https://www.jianshu.com/p/13313baac740
浅大白话来了解TCP协议的老三不善握手及季不善分手
https://github.com/jawil/blog/issues/14
Runloop笔试面试知识整理
https://hit-alibaba.github.io/interview/iOS/ObjC-Basic/Runloop.html
清晰理解Objective-C元类
http://blog.csdn.net/beclosedtomyheart/article/details/50164353
深入理解Objective-C:Category
https://tech.meituan.com/DiveIntoCategory.html
快速的圆角解决办法
iOS的系统层级结构和系的框架
http://blog.csdn.net/lxl\_815520/article/details/51172917
https://www.jianshu.com/p/c40f4d43adb7
iOS 底层解析weak的贯彻原理(包含weak对象的初始化,引用,释放的分析)
https://www.jianshu.com/p/13c4fb1cedea
iOS–KVO的兑现原理同现实用
Cocoa Touch 框架 https://www.jianshu.com/p/a26fcbb3281a
iOS底层-KVC使用实行以及落实原理
https://www.jianshu.com/p/fbd1e7c93fd0
iOS 中几乎栽常用的锁总结 https://www.jianshu.com/p/1e59f0970bf5
iOS多线程-各种线程锁之简介绍
https://www.jianshu.com/p/35dd92bcfe8c
不再安全的 OSSpinLock
https://blog.ibireme.com/2016/01/16/spinlock\_is\_unsafe\_in\_ios/
NSCache优于NSDictionary的几点?
http://blog.csdn.net/bowei1105/article/details/73320997
构建缓存时选用NSCache而非NSDictionary
http://ju.outofmemory.cn/entry/236986
NSRunLoopCommonModes和Timer https://www.jianshu.com/p/360156006195
探究 Block (一) (手把手讲解Block
底层实现原理)
关押罢还非懂HTTPS我直播吃翔 https://www.jianshu.com/p/ca7df01a9041
iOS,什么用或场景下得利用socket编程?
HTTP协议被GET和POST方法的区分
https://sunshinevvv.coding.me/blog/2017/02/09/HttpGETv.s.POST/
nsnotification是线程同步的也?https://www.google.com.hk/search?safe=strict&hl=zh-CN&source=hp&ei=4o4VWvCFDIK90AS-ko3gDw&q=nsnotification%E6%98%AF%E7%BA%BF%E7%A8%8B%E5%90%8C%E6%AD%A5%E7%9A%84%E5%90%97%EF%BC%9F&oq=nsnotification%E6%98%AF%E7%BA%BF%E7%A8%8B%E5%90%8C%E6%AD%A5%E7%9A%84%E5%90%97%EF%BC%9F&gs\_l=psy-ab.3…1179.13832.0.14184.52.36.0.0.0.0.505.4677.3-8j3j1.12.0….0…1c.1j4.64.psy-ab..41.5.1826…0.0.AmLNnbOz4YE
https://southpeak.github.io/2015/03/14/nsnotification-and-multithreading/
iOS中之SEl和IMP到底是什么 https://www.jianshu.com/p/4a09d5ebdc2c

系服务

年根儿感言

今是2017年12月31号, 2017年的末尾一龙。

昨天情人围突然多90晚初步晒18春之像,开始觉得又是情人围的平等不良流行风。直到今天早来看讯息的推送,才发觉及1999年诞生之人头啊如充满18寒暑了。90后一代人的一世就使”过”去。

自个人要比感性,对过去及童年依然充满着怀念,每每想到都能不禁感叹,岁月流逝,时光荏苒,每个人之人生小船都以时间之巨大浪下推动着前进,即使万貌似不情愿,也绝非外退路,直到好之小艇无法航行。
我们每个人还被时间裹挟,又受日子给恩惠。

自身觉得90晚相较于80无限可怜的区分,是立即一代人可以真正算是计算机普及,随互联网成长起来的一世。现在静静的的思起来,满脑子都是小学5年级放学后当紧邻小镇及老年人儿开的黑网吧里和同伴一起从”神龙记”,”红警”,”半条命”的小日子,几贵破旧的win98电脑,也陪同我过了我小学时的终极两年。那时候发段子时光,每次去网吧,都能够听见老人的儿放三篇歌,当时虽觉得好好听,以至于高潮时候的词到现还还尚无忘记,初中后,硬是凭着歌词,知道就三首歌一样首是林忆莲的《至少还有你》,一首是孙楠的《风往北吹》,另一样篇是群星的《让世界充满爱》。在我小学那片年乐此不疲的网吧的日子里,这三篇歌唱记忆尤为深刻,简直变成了一个记,我眷恋自己当下辈子都记不清不了了。

莫亮堂大家看了《芳华》没有,说实话,这部影片自己认为打的无限好了。关于青春,有些东西是永恒不变的,那就算是光明和痛苦,欢笑与泪,满足与遗憾。

哈哈哈,当然,我们还不行年轻,所谓的步入”中年”,也才是入30年份。正是各个一代人毕业以后,在社会及摸爬滚打,激情最充溢的几乎年。只要你想,只要您努力,仍然有当阶级未完全固化的现行,有翻身的或许。尤其是位于互联网遭受的卿自己,这种几带队还会见重复老复快有。

GameKit

实际上自从名称来拘禁这个框架并无是专程以支持蓝牙传输而规划的,它是吧一日游设计之。而多游乐受见面因此到因蓝牙的接触对点信息传输,因此此框架中集成了蓝牙传输模块。前面为说了这框架本身有无数限量,但是于iOS7之前的好多蓝牙传输都是因这个框架的,所以来必不可少对它们进行打探。GameKit中的蓝牙使用规划充分简单,并没有叫开发者留有无比多的扑朔迷离接口,而大多数连连细节开发者是休需要关怀的。GameKit中提供了少数只根本类来操作蓝牙连接:

GKPeerPickerController:蓝牙查找、连接用的视图控制器,通常状态下下程序A打开后会调用此控制器的show方法来显示一个蓝牙查找的视图,一旦发现了别样一个一样当找蓝牙连接的客户客户端B就会出现在视图列表中,此时要是用户点击连接B,B客户端就会见了解用户是否允许A连接B,如果允许后A和B之间建立一个蓝牙连接。

GKSession:连接会话,主要用于发送和经受传输数据。一旦A和B建立连接GKPeerPickerController的代办方法会将A、B两者建立的对话(GKSession)对象传递给开发人员,开发人员拿到之目标足以发送和接收数据。

事实上明白了点两单近乎后,使用起来便比较简单了,下面就是盖一个图片发送程序来演示GameKit中蓝牙的应用。此程序一个客户端运行在模拟器上作客户端A,另一个运作在iPhone真机上作为客户端B(注意A、B必须运行及一个主次,GameKit蓝牙开是免支持少数只不等之运传输数据的)。两独程序运行之后都调用GKPeerPickerController来发现周围蓝牙设备,一旦A发现了B之后就是从头连接B,然后iOS会询问用户是否受连接,一旦受之后便见面调用GKPeerPickerController的-(void)peerPickerController:(GKPeerPickerController
*)picker didConnectPeer:(NSString *)peerID toSession:(GKSession
*)session
代理方,在是道吃可获得连续的设备id(peerID)和连会话(session);此时足装会话的数额接收句柄(相当给一个摄)并保留会话以便发送数据时采取;一旦一端(假设是A)调用会话的sendDataToAllPeers:
withDataMode:
error:
道发送数据,此时其余一样端(假设是B)就见面调用句柄的– (void)
receiveData:(NSData *)data fromPeer:(NSString *)peer inSession:
(GKSession *)session context:(void
*)context
艺术,在是办法可落发送数据并拍卖。下面是程序代码:

//
//  ViewController.m
//  GameKit
//
//  Created by Kenshin Cui on 14/04/05.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

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

@interface ViewController ()<GKPeerPickerControllerDelegate,UIImagePickerControllerDelegate,UINavigationBarDelegate>

@property (weak, nonatomic) IBOutlet UIImageView *imageView;//照片显示视图
@property (strong,nonatomic) GKSession *session;//蓝牙连接会话

@end

@implementation ViewController

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

    GKPeerPickerController *pearPickerController=[[GKPeerPickerController alloc]init];
    pearPickerController.delegate=self;

    [pearPickerController show];
}

#pragma mark - UI事件
- (IBAction)selectClick:(UIBarButtonItem *)sender {
    UIImagePickerController *imagePickerController=[[UIImagePickerController alloc]init];
    imagePickerController.delegate=self;

    [self presentViewController:imagePickerController animated:YES completion:nil];
}

- (IBAction)sendClick:(UIBarButtonItem *)sender {
    NSData *data=UIImagePNGRepresentation(self.imageView.image);
    NSError *error=nil;
    [self.session sendDataToAllPeers:data withDataMode:GKSendDataReliable error:&error];
    if (error) {
        NSLog(@"发送图片过程中发生错误,错误信息:%@",error.localizedDescription);
    }
}

#pragma mark - GKPeerPickerController代理方法
/**
 *  连接到某个设备
 *
 *  @param picker  蓝牙点对点连接控制器
 *  @param peerID  连接设备蓝牙传输ID
 *  @param session 连接会话
 */
-(void)peerPickerController:(GKPeerPickerController *)picker didConnectPeer:(NSString *)peerID toSession:(GKSession *)session{
    self.session=session;
    NSLog(@"已连接客户端设备:%@.",peerID);
    //设置数据接收处理句柄,相当于代理,一旦数据接收完成调用它的-receiveData:fromPeer:inSession:context:方法处理数据
    [self.session setDataReceiveHandler:self withContext:nil];

    [picker dismiss];//一旦连接成功关闭窗口
}

#pragma mark - 蓝牙数据接收方法
- (void) receiveData:(NSData *)data fromPeer:(NSString *)peer inSession: (GKSession *)session context:(void *)context{
        UIImage *image=[UIImage imageWithData:data];
        self.imageView.image=image;
        UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
    NSLog(@"数据发送成功!");
}
#pragma mark - UIImagePickerController代理方法
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
    self.imageView.image=[info objectForKey:UIImagePickerControllerOriginalImage];
    [self dismissViewControllerAnimated:YES completion:nil];
}

-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{
    [self dismissViewControllerAnimated:YES completion:nil];
}
@end

运行效果(左侧是真机,右侧是模拟器,程序演示了少单客户端互发图片的情景:首先是模拟器发送图片为真机,然后真机发送图片于模拟器):

地理 7

交流

地理 8


期望能跟大家交流技术
Blog:http://www.lilongcnc.cc


内购

世家还知道开iOS开发自己的低收入来三栽来自:出售应用、内购和广告。国内用户日常十分少直接购买用,因此于开发者而言(特别是个体开发者),内购和广告收益就改为了重要的入账来。内购营销模式,通常软件本身是不收费的,但是如果取得某些特权就务须购买有道具,而内购的过程是由苹果官方统一来管理之,所以跟Game
Center一样,在开发内购程序之前如果举行有准备干活(下面的预备干活重大是对真机的,模拟器省略Provisioning
Profile配置过程):

  1. 前方四步和Game Center基本完全一致,只是于选取服务经常未是挑Game
    Center而是要挑选内购服务(In-App Purchase)。
  2. 交iTuens Connect中安“App
    内购买项目”,这里仍然因点的“KCTest”项目也例,假要这个足球比赛游艺受生出三种植道具,分别吗“强力手套”(增强防卫)、“金球”(增加金球率)和“能量瓶”(提供足够体力),前双方是非消耗品只用一次性买入,后者是消耗品用完一糟必须再次购买。地理 9
  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
    方式反馈过来的市(也就是已经采购之非消耗品交易,注意这个过程中若没有非消耗品可复原,是匪会见调用此方式的)。

下通过一个演示程序演示内购和死灰复燃的漫天过程,程序界面大致如下:

主界面中展示了装有可购得产品及售价,以及采购情况。

选择一个出品点”购买“可以进此商品,购买好后刷新购买状态(如果是休消耗品则显得都采购,如果是消耗品则展示购买个数)。

次卸载后重新安装可以点击”恢复购买“来回复就进的非消耗品。

地理 10

程序代码:

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

运行效果(这是次于卸载后重新安装的周转效果,卸载前曾买”强力手套“,因此程序运行后点击了”恢复购买“):

地理 11

通讯录

 

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

运行效果:

地理 12     地理 13

地理 14     地理 15

注意:

以为大家好更明亮的见到几个控制器视图的采取,这里连无做前面的UITableViewController来使用,事实上大家做前面UITableViewController可以开一个周的通讯录应用。

扩展–广告

面也论及做iOS开发另一样收入来就是是广告,在iOS上有诸多广告服务得合二为一,使用比较多的就是是苹果之iAd、谷歌的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

运作效果:

地理 16

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

运行效果:

地理 17

备注:

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()方法就。

MultipeerConnectivity

面前早已说了GameKit相关的蓝牙操作类从iOS7早就全部超时,苹果官方推荐应用MultipeerConnectivity代替。但是应当了解,MultipeerConnectivity.framework并不只支持蓝牙连接,准确之游说其是一模一样栽支持Wi-Fi网络、P2P
Wi-Fi已经蓝牙个人局域网的通信框架,它屏蔽了实际的连日技术,让开发人员有联合之接口编程方法。通过MultipeerConnectivity连接的节点内可高枕无忧之传递信息、流或其它文件资源使无需经过网络服务。此外使用MultipeerConnectivity进行近场通信也不再局限为与一个应用中传输,而是可以以不同之运用内进行数量传(当然如果有必不可少之话语你仍然可择当一个应用程序之间传输)。

倘若打听MultipeerConnectivity的采取得使解一个定义:广播(Advertisting)和发现(Disconvering),这很相近于同一种植Client-Server模式。假设有半点华设备A、B,B作为广播去发送自身服务,A作为发现的客户端。一旦A发现了B就准备确立连接,经过B同意双方建立连接就得彼此发送数据。在使用GameKit框架时,A和B既作广播又当发现,当然这种情景于MultipeerConnectivity中呢异常广泛。

A.广播

任当劳动器端去广播还是作为客户端去发现广播服务,那么简单个(或更多)不同的配备中要使起分,通常状态下以MCPeerID对象来分别一光装备,在这个装置受到可以指定显示为对方查看的名(display
name)。另外无论是啦一样正值,还得建立一个会话MCSession用于发送和承受多少。通常状态下会在对话的-(void)session:(MCSession
*)session peer:(MCPeerID *)peerID
didChangeState:(MCSessionState)state
代理方吃跟踪会话状态(已连接、正在连接、未连接);在对话的-(void)session:(MCSession
*)session didReceiveData:(NSData *)data fromPeer:(MCPeerID
*)peerID
代办方吃接收数据;同时还见面调用会话的-(void)sendData:
toPeers:withMode: error:
方式去发送数据。

播音作为一个服务器去宣布自己服务,供周边设备发现连续。在MultipeerConnectivity中应用MCAdvertiserAssistant来表示一个播,通常创建广播时指定一个会话MCSession对象将播服务以及对话关联起来。一旦调用广播的start方法周边的设备就足以发现该广播并可以连接至这服务。在MCSession的代办方中好随时更新连接状态,一旦确立了连年之后就是可以通过MCSession的connectedPeers获得已连续的装置。

B.发现

眼前已经说过当发现的客户端同样需一个MCPeerID来表明一个客户端,同时会持有一个MCSession来监听连接状态并发送、接受多少。除此之外,要发现广播服务,客户端就务须使时时查找服务来连接,在MultipeerConnectivity中提供了一个控制器MCBrowserViewController来展示可连接和早已接连的装置(这类似于GameKit中之GKPeerPickerController),当然要想使团结定制一个界面来显示设备连的情状而可择好支付同模仿UI界面。一旦通过MCBroserViewController选择一个节点去老是,那么作为广播的节点就会收取通知,询问用户是否同意连接。由于初始化MCBrowserViewController的长河就指定了会话MCSession,所以总是过程遭到会时时更新会话状态,一旦成立了连,就得经对话的connected属性获得已连续装置又可以以会说话发送、接受多少。

脚用简单单不同的应用程序来演示使用MultipeerConnectivity的运过程,其中一个采取运行在模拟器中作广播节点,另一个周转于iPhone真机上作发现节点,并且实现两单节点的图形互传。

率先看一下作广播节点的先后:

界面:

地理 18

点击“开始播报”来发布服务,一旦产生节点连接此服务就是得下“选择像”来起照片库中摘一张图并发送到具备曾经一连节点。

程序:

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

又看一下作发现节点的主次:

界面:

地理 19

点击“查找设备”浏览可用服务,点击服务建立连接;一旦成立了连之后便足以点击“选择像”会自照片库中甄选一样摆图纸并发送给曾经连续的节点。

程序:

//
//  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”才会觉察这服务。

运行效果:

地理 20

CoreBluetooth

任凭GameKit还是MultipeerConnectivity,都只好以iOS设备中进行多少传,这便大大降低了蓝牙的运用范围,于是起iOS6起苹果推出了CoreBluetooth.framework,这个框架最酷之特征就是全基于BLE4.0标准而支持非iOS设备。当前BLE应用相当广阔,不再只是片只装备内的多寡传,它还有很多任何以市场,例如室内定位、无线支付、智能家居等等,这吗使CoreBluetooth成为当前极端紧俏之天蓝牙技术。

CoreBluetooth设计相同为是接近于客户端-服务器端的设计,作为劳动器端的装置称为外围设备(Peripheral),作为客户端的配备叫做中央设备(Central),CoreBlueTooth整个框架就是因这点儿只概念来规划的。

地理 21

外围设备和中央设备以CoreBluetooth中应用CBPeripheralManager和CBCentralManager表示。

CBPeripheralManager:外围设备通常用于发布服务、生成数据、保存数据。外围设备发布并播放服务,告诉周围的中央设备它的可用服务同特点。

CBCentralManager:中央设备以外围设备的数码。中央设备扫描到外围设备后会见便会准备建立连接,一旦连续成就是足以下这些服务与特性。

外围设备和中央设备内互相的桥梁是劳务(CBService)和特性(CBCharacteristic),二者都生一个唯一的标识UUID(CBUUID类型)来唯一确定一个服务或者特征,每个服务得有所多只性状,下面是他们中的涉及:

地理 22

平等大iOS设备(注意iPhone4以下设备未支持BLE,另外iOS7.0、8.0模拟器也束手无策模拟BLE)既好看作外围设备又足以看作中央设备,但是未可知而就凡是外围设备又是中央设备,同时注意建立连接的过程不需要用户手动选项允许,这或多或少暨前边两独框架是差的,这主要是坐BLE应用场景不再局限为少数令设备中资源共享了。

A.外围设备

创一个外围设备通常分为以下几个步骤:

  1. 创立之外配备CBPeripheralManager对象并点名代理。
  2. 开创特征CBCharacteristic、服务CBSerivce并加加到外围设备
  3. 外围设备开始广播服务(startAdvertisting:)。
  4. 同中央设备CBCentral进行交互。

脚是略的先后示例,程序来星星点点只按钮“启动”和“更新”,点击启动按钮则开创之外配备、添加服务以及特色并开始广播,一旦发觉发生中央设备连并订阅了这服务的风味则透过创新按钮更新特征数据,此时早就订阅的中央设备就是会见接更新数据。

界面设计:

地理 23

程序设计:

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

地方程序运行的流程如下(图中蓝色代表外围设备操作,绿色部分代表中央设备操作):

地理 24

B.中央设备

中央设备的创立一般可分为如下几个步骤:

  1. 创立中央设备管理对象CBCentralManager并指定代理。
  2. 举目四望外围设备,一般发现可用外围设备则总是并保留外围设备。
  3. 找寻外围设备服务同特征,查找到可用特征则读取特征数据。

下是一个简的中央服务器端实现,点击“启动”按钮则始于扫描周围的外围设备,一旦发觉了可用的外围设备则树立连接并设置外围设备的代理,之后开始查找其劳动同特性。一旦外围设备的特征值做了履新,则可以代理方被读取更新后底特征值。

界面设计:

地理 25

程序设计:

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

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

地理 26

发矣端两个次即使足以分别运行在点滴玉支持BLE的iOS设备及,当半独以建立连接后,一旦外围设备更新特征之后,中央设备就可以及时得到到创新后的值。需要强调的是动CoreBluetooth开发的利用不仅仅可以与另外iOS设备开展蓝牙通信,还好跟其他第三在遵循BLE规范的设施开展蓝牙通讯,这里就是不再赘述。

留意:本节有的图片源于于互联网,版权归原作者有。

Passbook

Passbook是苹果推出的一个管制登机牌、会员卡、电影票、优惠券等消息的工具。Passbook就像一个卡包,用于存放你的购物卡、积分卡、电影票、礼品卡等,而这些票就是一个“Pass”。和大体票据不同的凡你可以动态更新Pass的音讯,提醒用户优惠券即将过;甚至使你的Pass中含有地理位置信息之说话当你到达某公司还可以动态提醒用户最近企业有何种优惠活动;当用户以同摆设团购券添加至Passbook之后,用户到了商家之后Passbook可以自行弹出团购券,店员扫描下进行消费、积分等等都是Passbook的利用场景。Passbook可以管理几近类似票,苹果将该分割为五类:

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

苹果的分割一方面是因为不同票据功能及展示信息不同,另一方面为是为了统一票据的设计,下面是苹果官方关于五栽票据的布局设计布局:

地理 27

既然一个票据就是一个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下生成证书文件)如下图:地理 28

    下载证书后,将之证导入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“。地理 29

起了点的备干活,下面看一下什么做一个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中打开预览:

地理 30

顶此一个Pass就只有做成功了,下面就扣留一下于iOS中什么添加这个Pass到Passbook,这里一直将方做好的Pass放到Bundle中好增长。当然这些都是一步步手动完成的,前面吧说了实际上支付中这Pass是劳务器端来动态变化的,在长时见面自服务器端下载,这个进程在示范中尽管不再演示。iOS中提供了PassKit.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

运作效果:

地理 31

顾:如果大家对此Pass不是极端熟悉,刚开头好使部分造工具来就Pass制作,例如:PassSource、国内的PassQuan等都支持在线制作Pass包。

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

运转效果:

地理 32

发送成功之后:

地理 33

每当是历程遭到开发人员不欲懂得新浪微博之复多分享细节,Social框架中已统一了享受的接口,你可透过ServiceType设置是享受到Facebook、Twitter、新浪微博、腾讯微博,而不关注具体的底细实现。那么当运行方面的示范时她是怎么知道用哪个账户来发送微博为?其实当iOS的装中发出专门设置Facebook、Twitter、微博的地方:

地理 34

务必首先在此地装微博账户才能够就点的出殡,不然Social框架为不容许知道具体用谁账户来发送。

其三正值框架

本来,通过地方的安装界面应该好视,苹果官方默认支持的享受并无顶多,特别是对国内的运只支持新浪微博及腾讯微博(事实上从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

运行效果:

地理 35

只顾:在首先破用某个分享服务是要输入相应的账号获得授权才会分享。

短信与邮件

调用系统放的下来发送短信、邮件相当简单,但是这么操作为有着有弊病:当您点击了发送短信(或邮件)操作后直接开行了系的亏信(或邮件)应用程序,我们的施用其实这就处于相同种植挂于状态,发送了(短信要邮件)之后无法自行返回用界面。如果想要于应用程序内部形成这些操作则可以下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的用:

地理 36

用户通过当这界面输入短信信息点击“发送信息”调用MFMessageComposeViewController界面来显示或更编辑信息,点击MFMessageComposeViewController中之“发送”来好短信发送工作,当然用户为或点击“取消”按钮回到前一个短信编辑页面。

地理 37

兑现代码:

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

运作效果:

地理 38     地理 39

iCloud

iCloud是苹果提供的云端服务,用户可将通讯录、备忘录、邮件、照片、音乐、视频等备份到讲话服务器并于各个苹果设备中直接进行共享而无论是需关注数据并问题,甚至就是你的设备丢失后在同等贵新的装置及啊得经过Apple
ID登录同步。当然这些内容还是iOS内置的效能,那么对开放者如何行使iCloud呢?苹果已经以云端存储功能开放给开发者,利用iCloud开发者可以储存两接近数据:用户文档和采取数据、应用配置起。前者主要用来一些用户文档、文件的囤积,后者还接近于通常开放中之溺爱设置,只是这些部署信息会同步到云端。

而拓展iCloud开发同需有预备干活(下面的备工作重大是针对性真机的,模拟器省略Provisioning
Profile配置过程):

1、2步骤仍然是创办App ID启用iCloud服务、生成对应之安排(Provisioning
Profile),这个进程中Bundle ID可以运用通配符(Data
Protection、iCloud、Inter-App Audio、Passbook服务以开创App
ID时中的Bundle ID是得动用通配ID的)。

3.在Xcode中创造项目(假设项目名称为“kctest”)并当档次之Capabilities中找到iCloud并打开。这里需要注意的便是由当是采取中使以身作则文档存储和首选项存储,因此当Service中勾选“Key-value
storae”和“iCloud Documents”:

地理 40

以档次遭到会自动生成一个”kctest.entitlements”配置文件,这个文档配置了文档存储容器标识、键值对存储容器标识等消息。

地理 41

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服务之笔记软件。在脚的次第中实现笔记的增产、修改、保存、读取等操作。程序界面大致如下,点击界面右上方多按钮增加一个笔记,点击某个笔记可以查并编辑。

地理 42

当主视图控制器首先查询所有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来保存首选项,这在很多场面下一般十分有因此,特别是对开发了iPhone版又出了iPad版的采取,如果用户以同一令设备上进行了首选项配置之后至其它一样高设备上吧会使是何其完美之心得啊。相比文档存储,首选项存储要简明的差不多,在点“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

运转效果:

地理 43

注意:所有的贮存到iCloud的文档、首选项都是第一存储到地面,然后经daemon进程同步到iCloud的,保存、读取的文书都是本地同副本并不一定是真的iCloud存储文件。

系统采取

每当支付一些应用时或许希望能调用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的绝无仅有标识,如下图:

地理 44

下一场就是好调用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;//是否打开
}

相关文章

No Comments, Be The First!
近期评论
    分类目录
    功能
    网站地图xml地图