iOS开发:通讯录、蓝牙、内购、GameCenter、iCloud、Passbook系统服务开发汇总

–系统应用与系统服务

iOS开发过程中有时候难免会使用iOS内置的一些应用软件和服务,例如QQ通讯录、微信电话本会使用iOS的通讯录,一些第三方软件会在应用内发送短信等。今天将和大家一起学习如何使用系统应用、使用系统服务:

系统应用

在开发某些应用时可能希望能够调用iOS系统内置的电话、短信、邮件、浏览器应用,此时你可以直接使用UIApplication的OpenURL:方法指定特定的协议来打开不同的系统应用。常用的协议如下:

打电话:tel:或者tel://、telprompt:或telprompt://(拨打电话前有提示)

发短信:sms:或者sms://

发送邮件:mailto:或者mailto://

启动浏览器:http:或者http://

下面以一个简单的demo演示如何调用上面几种系统应用:

不难发现当openURL:方法只要指定一个URL Schame并且已经安装了对应的应用程序就可以打开此应用。当然,如果是自己开发的应用也可以调用openURL方法来打开。假设你现在开发了一个应用A,如果用户机器上已经安装了此应用,并且在应用B中希望能够直接打开A。那么首先需要确保应用A已经配置了Url Types,具体方法就是在plist文件中添加URL types节点并配置URL Schemas作为具体协议,配置URL identifier作为这个URL的唯一标识,如下图:

iOSApplication_URLTypes

然后就可以调用openURL方法像打开系统应用一样打开第三方应用程序了:

就像调用系统应用一样,协议后面可以传递一些参数(例如上面传递的myparams),这样一来在应用中可以在AppDelegate的-(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation代理方法中接收参数并解析。

系统服务

短信与邮件

调用系统内置的应用来发送短信、邮件相当简单,但是这么操作也存在着一些弊端:当你点击了发送短信(或邮件)操作之后直接启动了系统的短信(或邮件)应用程序,我们的应用其实此时已经处于一种挂起状态,发送完(短信或邮件)之后无法自动回到应用界面。如果想要在应用程序内部完成这些操作则可以利用iOS中的MessageUI.framework,它提供了关于短信和邮件的UI接口供开发者在应用程序内部调用。从框架名称不难看出这是一套UI接口,提供有现成的短信和邮件的编辑界面,开发人员只需要通过编程的方式给短信和邮件控制器设置对应的参数即可。

在MessageUI.framework中主要有两个控制器类分别用于发送短信(MFMessageComposeViewController)和邮件(MFMailComposeViewController),它们均继承于UINavigationController。由于两个类使用方法十分类似,这里主要介绍一下MFMessageComposeViewController使用步骤:

  1. 创建MFMessageComposeViewController对象。
  2. 设置收件人recipients、信息正文body,如果运行商支持主题和附件的话可以设置主题subject、附件attachments(可以通过canSendSubject、canSendAttachments方法判断是否支持)
  3. 设置代理messageComposeDelegate(注意这里不是delegate属性,因为delegate属性已经留给UINavigationController,MFMessageComposeViewController没有覆盖此属性而是重新定义了一个代理),实现代理方法获得发送状态。

下面自定义一个发送短信的界面演示MFMessageComposeViewController的使用:

MFMessageComposeViewController_Layout

用户通过在此界面输入短信信息点击“发送信息”调用MFMessageComposeViewController界面来展示或进一步编辑信息,点击MFMessageComposeViewController中的“发送”来完成短信发送工作,当然用户也可能点击“取消”按钮回到前一个短信编辑页面。

MFMessageComposeViewController

实现代码:

这里需要强调一下:

  • MFMessageComposeViewController的代理不是通过delegate属性指定的而是通过messageComposeDelegate指定的。
  • 可以通过几种方式来指定发送的附件,在这个过程中请务必指定文件的后缀,否则在发送后无法正确识别文件类别(例如如果发送的是一张jpg图片,在发送后无法正确查看图片)。
  • 无论发送成功与否代理方法-(void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result都会执行,通过代理参数中的result来获得发送状态。

其实只要熟悉了MFMessageComposeViewController之后,那么用于发送邮件的MFMailComposeViewController用法和步骤完全一致,只是功能不同。下面看一下MFMailComposeViewController的使用:

运行效果:

MFMailComposeViewController_Layout     MFMailComposeViewController

通讯录

AddressBook

iOS中带有一个Contacts应用程序来管理联系人,但是有些时候我们希望自己的应用能够访问或者修改这些信息,这个时候就要用到AddressBook.framework框架。iOS中的通讯录是存储在数据库中的,由于iOS的权限设计,开发人员是不允许直接访问通讯录数据库的,必须依靠AddressBook提供的标准API来实现通讯录操作。通过AddressBook.framework开发者可以从底层去操作AddressBook.framework的所有信息,但是需要注意的是这个框架是基于C语言编写的,无法使用ARC来管理内存,开发者需要自己管理内存。下面大致介绍一下通讯录操作中常用的类型:

  • ABAddressBookRef:代表通讯录对象,通过该对象开发人员不用过多的关注通讯录的存储方式,可以直接以透明的方式去访问、保存(在使用AddressBook.framework操作联系人时,所有的增加、删除、修改后都必须执行保存操作,类似于Core Data)等。
  • ABRecordRef:代表一个通用的记录对象,可以是一条联系人信息,也可以是一个群组,可以通过ABRecordGetRecordType()函数获得具体类型。如果作为联系人(事实上也经常使用它作为联系人),那么这个记录记录了一个完整的联系人信息(姓名、性别、电话、邮件等),每条记录都有一个唯一的ID标示这条记录(可以通过ABRecordGetRecordID()函数获得)。
  • ABPersonRef:代表联系人信息,很少直接使用,实际开发过程中通常会使用类型为“kABPersonType”的ABRecordRef来表示联系人(由此可见ABPersonRef其实是一种类型为“kABPersonType”的ABRecordRef)
  • ABGroupRef:代表群组,与ABPersonRef类似,很少直接使用ABGroupRef,而是使用类型为“kABGroupType”的ABRecordRef来表示群组,一个群组可以包含多个联系人,一个联系人也同样可以多个群组。

由于通讯录操作的关键是对ABRecordRef的操作,首先看一下常用的操作通讯录记录的方法:

ABPersonCreate():创建一个类型为“kABPersonType”的ABRecordRef。

ABRecordCopyValue():取得指定属性的值。

ABRecordCopyCompositeName():取得联系人(或群组)的复合信息(对于联系人则包括:姓、名、公司等信息,对于群组则返回组名称)。

ABRecordSetValue():设置ABRecordRef的属性值。注意在设置ABRecordRef的值时又分为单值属性和多值属性:单值属性设置只要通过ABRecordSetValue()方法指定属性名和值即可;多值属性则要先通过创建一个ABMutableMultiValueRef类型的变量,然后通过ABMultiValueAddValueAndLabel()方法依次添加属性值,最后通过ABRecordSetValue()方法将ABMutableMultiValueRef类型的变量设置为记录值。

ABRecordRemoveValue():删除指定的属性值。

注意:

由于联系人访问时(读取、设置、删除时)牵扯到大量联系人属性,可以到ABPerson.h中查询或者直接到帮助文档“Personal Information Properties

通讯录的访问步骤一般如下:

  1. 调用ABAddressBookCreateWithOptions()方法创建通讯录对象ABAddressBookRef。
  2. 调用ABAddressBookRequestAccessWithCompletion()方法获得用户授权访问通讯录。
  3. 调用ABAddressBookCopyArrayOfAllPeople()、ABAddressBookCopyPeopleWithName()方法查询联系人信息。
  4. 读取联系人后如果要显示联系人信息则可以调用ABRecord相关方法读取相应的数据;如果要进行修改联系人信息,则可以使用对应的方法修改ABRecord信息,然后调用ABAddressBookSave()方法提交修改;如果要删除联系人,则可以调用ABAddressBookRemoveRecord()方法删除,然后调用ABAddressBookSave()提交修改操作。
  5. 也就是说如果要修改或者删除都需要首先查询对应的联系人,然后修改或删除后提交更改。如果用户要增加一个联系人则不用进行查询,直接调用ABPersonCreate()方法创建一个ABRecord然后设置具体的属性,调用ABAddressBookAddRecord方法添加即可。

下面就通过一个示例演示一下如何通过ABAddressBook.framework访问通讯录,这个例子中通过一个UITableViewController模拟一下通讯录的查看、删除、添加操作。

主控制器视图,用于显示联系人,修改删除联系人:

KCContactViewController.h

KCContactViewController.m

新增或修改控制器视图,用于显示一个联系人的信息或者新增一个联系人:

KCAddPersonViewController.h

KCAddPersonViewController.m

运行效果:

AddressBook

备注:

1.上文中所指的以Ref结尾的对象事实上是该对象的指针(或引用),在C语言的框架中多数类型会以Ref结尾,这个类型本身就是一个指针,定义时不需要加“*”。

2.通常方法中包含copy、create、new、retain等关键字的方法创建的变量使用之后需要调用对应的release方法释放。例如:使用ABPersonCreate();创建完ABRecordRef变量后使用CFRelease()方法释放。

3.在与很多C语言框架交互时可以都存在Obj-C和C语言类型之间的转化(特别是Obj-C和Core Foundation框架中的一些转化),此时可能会用到桥接,只要在强转之后前面加上”__bridge”即可,经过桥接转化后的类型不需要再去手动维护内存,也就不需要使用对应的release方法释放内存。

4.AddressBook框架中很多类型的创建、属性设置等都是以这个类型名开发头的方法来创建的,事实上如果大家熟悉了其他框架会发现也都是类似的,这是Apple开发中约定俗成的命名规则(特别是C语言框架)。例如:要给ABRecordRef类型的变量设置属性则可以通过ABRecordSetValue()方法完成。

AddressBookUI

使用AddressBook.framework来操作通讯录特点就是可以对通讯录有更加精确的控制,但是缺点就是面对大量C语言API稍嫌麻烦,于是Apple官方提供了另一套框架供开发者使用,那就是AddressBookUI.framework。例如前面查看、新增、修改人员的界面这个框架就提供了现成的控制器视图供开发者使用。下面是这个框架中提供的控制器视图:

  • ABPersonViewController:用于查看联系人信息(可设置编辑)。需要设置displayedPerson属性来设置要显示或编辑的联系人。
  • ABNewPersonViewController:用于新增联系人信息。
  • ABUnknownPersonViewController:用于显示一个未知联系人(尚未保存的联系人)信息。需要设置displayedPerson属性来设置要显示的未知联系人。

以上三个控制器视图均继承于UIViewController,在使用过程中必须使用一个UINavigationController进行包装,否则只能看到视图内容无法进行操作(例如对于ABNewPersonViewController如果不使用UINavigationController进行包装则没有新增和取消按钮),同时注意包装后的控制器视图不需要处理具体新增、修改逻辑(增加和修改的处理逻辑对应的控制器视图内部已经完成),但是必须处理控制器的关闭操作(调用dismissViewControllerAnimated::方法),并且可以通过代理方法获得新增、修改的联系人。下面看一下三个控制器视图的代理方法:

1.ABPersonViewController的displayViewDelegate代理方法:

-(BOOL)personViewController:(ABPersonViewController *)personViewController shouldPerformDefaultActionForPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier:此方法会在选择了一个联系人属性后触发,四个参数分别代表:使用的控制器视图、所查看的联系人、所选则的联系人属性、该属性是否是多值属性。

2.ABNewPersonViewController的newPersonViewDelegate代理方法:

-(void)newPersonViewController:(ABNewPersonViewController *)newPersonView didCompleteWithNewPerson:(ABRecordRef)person:点击取消或完成后触发,如果参数中的person为NULL说明点击了取消,否则说明点击了完成。无论是取消还是完成操作,此方法调用时保存操作已经进行完毕,不需要在此方法中自己保存联系人信息。

3.ABUnkownPersonViewcontroller的unkownPersonViewDelegate代理方法:

-(void)unknownPersonViewController:(ABUnknownPersonViewController *)unknownCardViewController didResolveToPerson:(ABRecordRef)person:保存此联系人时调用,调用后将此联系人返回。

-(BOOL)unknownPersonViewController:(ABUnknownPersonViewController *)personViewController shouldPerformDefaultActionForPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier:选择一个位置联系人属性之后执行,返回值代表是否执行默认的选择操作(例如如果是手机号,默认操作会拨打此电话)

除了上面三类控制器视图在AddressBookUI中还提供了另外一个控制器视图ABPeoplePickerNavigationController,它与之前介绍的UIImagePickerController、MPMediaPickerController类似,只是他是用来选择一个联系人的。这个控制器视图本身继承于UINavigationController,视图自身的“组”、“取消”按钮操作不需要开发者来完成(例如开发者不用在点击取消是关闭当前控制器视图,它自身已经实现了关闭方法),当然这里主要说一下这个控制器视图的peoplePickerDelegate代理方法:

-(void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person:选择一个联系人后执行。此代理方法实现后代理方法“-(void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier”不会再执行。并且一旦实现了这个代理方法用户只能选择到联系人视图,无法查看具体联系人的信息。

-(void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker:用户点击取消后执行。

-(void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier:选择联系人具体的属性后执行,注意如果要执行此方法则不能实现-(void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person代理方法,此时如果点击一个具体联系人会导航到联系人详细信息界面,用户点击具体的属性后触发此方法。

下面就看一下上面四个控制器视图的使用方法,在下面的程序中定义了四个按钮,点击不同的按钮调用不同的控制器视图用于演示:

运行效果:

ABPersonViewController     ABNewPersonViewController

ABUnkownPersonViewController     ABPeoplePickerNaviagationController

注意:

为了让大家可以更加清楚的看到几个控制器视图的使用,这里并没有结合前面的UITableViewController来使用,事实上大家结合前面UITableViewController可以做一个完善的通讯录应用。

蓝牙

随着蓝牙低功耗技术BLE(Bluetooth Low Energy)的发展,蓝牙技术正在一步步成熟,如今的大部分移动设备都配备有蓝牙4.0,相比之前的蓝牙技术耗电量大大降低。从iOS的发展史也不难看出苹果目前对蓝牙技术也是越来越关注,例如苹果于2013年9月发布的iOS7就配备了iBeacon技术,这项技术完全基于蓝牙传输。但是众所周知苹果的设备对于权限要求也是比较高的,因此在iOS中并不能像Android一样随意使用蓝牙进行文件传输(除非你已经越狱)。在iOS中进行蓝牙传输应用开发常用的框架有如下几种:

GameKit.framework:iOS7之前的蓝牙通讯框架,从iOS7开始过期,但是目前多数应用还是基于此框架。

MultipeerConnectivity.framework:iOS7开始引入的新的蓝牙通讯开发框架,用于取代GameKit。

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

前两个框架使用起来比较简单,但是缺点也比较明显:仅仅支持iOS设备,传输内容仅限于沙盒或者照片库中用户选择的文件,并且第一个框架只能在同一个应用之间进行传输(一个iOS设备安装应用A,另一个iOS设备上安装应用B是无法传输的)。当然CoreBluetooth就摆脱了这些束缚,它不再局限于iOS设备之间进行传输,你可以通过iOS设备向Android、Windows Phone以及其他安装有蓝牙4.0芯片的智能设备传输,因此也是目前智能家居、无线支付等热门智能设备所推崇的技术。

GameKit

其实从名称来看这个框架并不是专门为了支持蓝牙传输而设计的,它是为游戏设计的。而很多游戏中会用到基于蓝牙的点对点信息传输,因此这个框架中集成了蓝牙传输模块。前面也说了这个框架本身有很多限制,但是在iOS7之前的很多蓝牙传输都是基于此框架的,所以有必要对它进行了解。GameKit中的蓝牙使用设计很简单,并没有给开发者留有太多的复杂接口,而多数连接细节开发者是不需要关注的。GameKit中提供了两个关键类来操作蓝牙连接:

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

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

其实理解了上面两个类之后,使用起来就比较简单了,下面就以一个图片发送程序来演示GameKit中蓝牙的使用。此程序一个客户端运行在模拟器上作为客户端A,另一个运行在iPhone真机上作为客户端B(注意A、B必须运行同一个程序,GameKit蓝牙开发是不支持两个不同的应用传输数据的)。两个程序运行之后均调用GKPeerPickerController来发现周围蓝牙设备,一旦A发现了B之后就开始连接B,然后iOS会询问用户是否接受连接,一旦接受之后就会调用GKPeerPickerController的-(void)peerPickerController:(GKPeerPickerController *)picker didConnectPeer:(NSString *)peerID toSession:(GKSession *)session代理方法,在此方法中可以获得连接的设备id(peerID)和连接会话(session);此时可以设置会话的数据接收句柄(相当于一个代理)并保存会话以便发送数据时使用;一旦一端(假设是A)调用会话的sendDataToAllPeers: withDataMode: error:方法发送数据,此时另一端(假设是B)就会调用句柄的– (void) receiveData:(NSData *)data fromPeer:(NSString *)peer inSession: (GKSession *)session context:(void *)context方法,在此方法可以获得发送数据并处理。下面是程序代码:

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

MultipeerConnectivity

前面已经说了GameKit相关的蓝牙操作类从iOS7已经全部过期,苹果官方推荐使用MultipeerConnectivity代替。但是应该了解,MultipeerConnectivity.framework并不仅仅支持蓝牙连接,准确的说它是一种支持Wi-Fi网络、P2P Wi-Fi已经蓝牙个人局域网的通信框架,它屏蔽了具体的连接技术,让开发人员有统一的接口编程方法。通过MultipeerConnectivity连接的节点之间可以安全的传递信息、流或者其他文件资源而不必通过网络服务。此外使用MultipeerConnectivity进行近场通信也不再局限于同一个应用之间传输,而是可以在不同的应用之间进行数据传输(当然如果有必要的话你仍然可以选择在一个应用程序之间传输)。

要了解MultipeerConnectivity的使用必须要清楚一个概念:广播(Advertisting)和发现(Disconvering),这很类似于一种Client-Server模式。假设有两台设备A、B,B作为广播去发送自身服务,A作为发现的客户端。一旦A发现了B就试图建立连接,经过B同意二者建立连接就可以相互发送数据。在使用GameKit框架时,A和B既作为广播又作为发现,当然这种情况在MultipeerConnectivity中也很常见。

A.广播

无论是作为服务器端去广播还是作为客户端去发现广播服务,那么两个(或更多)不同的设备之间必须要有区分,通常情况下使用MCPeerID对象来区分一台设备,在这个设备中可以指定显示给对方查看的名称(display name)。另外不管是哪一方,还必须建立一个会话MCSession用于发送和接受数据。通常情况下会在会话的-(void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state代理方法中跟踪会话状态(已连接、正在连接、未连接);在会话的-(void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID代理方法中接收数据;同时还会调用会话的-(void)sendData: toPeers:withMode: error:方法去发送数据。

广播作为一个服务器去发布自身服务,供周边设备发现连接。在MultipeerConnectivity中使用MCAdvertiserAssistant来表示一个广播,通常创建广播时指定一个会话MCSession对象将广播服务和会话关联起来。一旦调用广播的start方法周边的设备就可以发现该广播并可以连接到此服务。在MCSession的代理方法中可以随时更新连接状态,一旦建立了连接之后就可以通过MCSession的connectedPeers获得已经连接的设备。

B.发现

前面已经说过作为发现的客户端同样需要一个MCPeerID来标志一个客户端,同时会拥有一个MCSession来监听连接状态并发送、接受数据。除此之外,要发现广播服务,客户端就必须要随时查找服务来连接,在MultipeerConnectivity中提供了一个控制器MCBrowserViewController来展示可连接和已连接的设备(这类似于GameKit中的GKPeerPickerController),当然如果想要自己定制一个界面来展示设备连接的情况你可以选择自己开发一套UI界面。一旦通过MCBroserViewController选择一个节点去连接,那么作为广播的节点就会收到通知,询问用户是否允许连接。由于初始化MCBrowserViewController的过程已经指定了会话MCSession,所以连接过程中会随时更新会话状态,一旦建立了连接,就可以通过会话的connected属性获得已连接设备并且可以使用会话发送、接受数据。

下面用两个不同的应用程序来演示使用MultipeerConnectivity的使用过程,其中一个应用运行在模拟器中作为广播节点,另一个运行在iPhone真机上作为发现节点,并且实现两个节点的图片互传。

首先看一下作为广播节点的程序:

界面:

MultipeerConnectivity_Advertiser

点击“开始广播”来发布服务,一旦有节点连接此服务就可以使用“选择照片”来从照片库中选取一张图片并发送到所有已连接节点。

程序:

再看一下作为发现节点的程序:

界面:

MultipeerConnectivity_Discover

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

程序:

在两个程序中无论是MCBrowserViewController还是MCAdvertiserAssistant在初始化的时候都指定了一个服务类型“cmj-photo”,这是唯一标识一个服务类型的标记,可以按照官方的要求命名,应该尽可能表达服务的作用。需要特别指出的是,如果广播命名为“cmj-photo”那么发现节点只有在MCBrowserViewController中指定为“cmj-photo”才能发现此服务。

运行效果:

CoreBluetooth

无论是GameKit还是MultipeerConnectivity,都只能在iOS设备之间进行数据传输,这就大大降低了蓝牙的使用范围,于是从iOS6开始苹果推出了CoreBluetooth.framework,这个框架最大的特点就是完全基于BLE4.0标准并且支持非iOS设备。当前BLE应用相当广泛,不再仅仅是两个设备之间的数据传输,它还有很多其他应用市场,例如室内定位、无线支付、智能家居等等,这也使得CoreBluetooth成为当前最热门的蓝牙技术。

CoreBluetooth设计同样也是类似于客户端-服务器端的设计,作为服务器端的设备称为外围设备(Peripheral),作为客户端的设备叫做中央设备(Central),CoreBlueTooth整个框架就是基于这两个概念来设计的。

Central_Peripheral

外围设备和中央设备在CoreBluetooth中使用CBPeripheralManager和CBCentralManager表示。

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

CBCentralManager:中央设备使用外围设备的数据。中央设备扫描到外围设备后会就会试图建立连接,一旦连接成功就可以使用这些服务和特征。

外围设备和中央设备之间交互的桥梁是服务(CBService)和特征(CBCharacteristic),二者都有一个唯一的标识UUID(CBUUID类型)来唯一确定一个服务或者特征,每个服务可以拥有多个特征,下面是他们之间的关系:

Peripheral_Central_Service_Characteristic

一台iOS设备(注意iPhone4以下设备不支持BLE,另外iOS7.0、8.0模拟器也无法模拟BLE)既可以作为外围设备又可以作为中央设备,但是不能同时即是外围设备又是中央设备,同时注意建立连接的过程不需要用户手动选择允许,这一点和前面两个框架是不同的,这主要是因为BLE应用场景不再局限于两台设备之间资源共享了。

A.外围设备

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

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

下面是简单的程序示例,程序有两个按钮“启动”和“更新”,点击启动按钮则创建外围设备、添加服务和特征并开始广播,一旦发现有中央设备连接并订阅了此服务的特征则通过更新按钮更新特征数据,此时已订阅的中央设备就会收到更新数据。

界面设计:

PeripheralPeer

程序设计:

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

PeripheralFlow

B.中央设备

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

  1. 创建中央设备管理对象CBCentralManager并指定代理。
  2. 扫描外围设备,一般发现可用外围设备则连接并保存外围设备。
  3. 查找外围设备服务和特征,查找到可用特征则读取特征数据。

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

界面设计:

CentralPeer

程序设计:

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

CentralFlow

有了上面两个程序就可以分别运行在两台支持BLE的iOS设备上,当两个应用建立连接后,一旦外围设备更新特征之后,中央设备就可以立即获取到更新后的值。需要强调的是使用CoreBluetooth开发的应用不仅仅可以和其他iOS设备进行蓝牙通信,还可以同其他第三方遵循BLE规范的设备进行蓝牙通讯,这里就不再赘述。

注意:本节部分图片来自于互联网,版权归原作者所有。

社交

Social

现在很多应用都内置“社交分享”功能,可以将看到的新闻、博客、广告等内容分享到微博、微信、QQ、空间等,其实从iOS6.0开始苹果官方就内置了Social.framework专门来实现社交分享功能,利用这个框架开发者只需要几句代码就可以实现内容分享。下面就以一个分享到新浪微博的功能为例来演示Social框架的应用,整个过程分为:创建内容编辑控制器,设置分享内容(文本内容、图片、超链接等),设置发送(或取消)后的回调事件,展示控制器。

程序代码:

运行效果:

Social

发送成功之后:

Social_SinaWeibo

在这个过程中开发人员不需要知道新浪微博的更多分享细节,Social框架中已经统一了分享的接口,你可以通过ServiceType设置是分享到Facebook、Twitter、新浪微博、腾讯微博,而不关心具体的细节实现。那么当运行上面的示例时它是怎么知道用哪个账户来发送微博呢?其实在iOS的设置中有专门设置Facebook、Twitter、微博的地方:

Social_SinaSetting

必须首先在这里设置微博账户才能完成上面的发送,不然Social框架也不可能知道具体使用哪个账户来发送。

第三方框架

当然,通过上面的设置界面应该可以看到,苹果官方默认支持的分享并不太多,特别是对于国内的应用只支持新浪微博和腾讯微博(事实上从iOS7苹果才考虑支持腾讯微博),那么如果要分享到微信、人人、开心等等国内较为知名的社交网络怎么办呢?目前最好的选择就是使用第三方框架,因为如果要自己实现各个应用的接口还是比较复杂的。当前使用较多的就是友盟社会化组件ShareSDK,而且现在百度也出了社会化分享组件。今天无法对所有组件都进行一一介绍,这里就以友盟社交化组件为例简单做一下介绍:

  1. 注册友盟账号并新建应用获得AppKey。
  2. 下载友盟SDK并将下载的文件放到项目中(注意下载的过程中可以选择所需要的分享服务)。
  3. 在应用程序中设置友盟的AppKey。
  4. 分享时调用presentSnsIconSheetView: appKey: shareText: shareImage: shareToSnsNames: delegate:方法或者presentSnsController: appKey: shareText: shareImage: shareToSnsNames: delegate:方法显示分享列表(注意这个过程中要使用某些服务需要到对应的平台去申请并对应扩展框架进行设置,否则分享列表中不会显示对应的分享按钮)。

下面是一个简单的示例:

运行效果:

Social_UM

注意:在第一次使用某个分享服务是需要输入相应的账号获得授权才能分享。

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)。 GameCenter_AppID
  2. 基于“com.cmjstudio.kctest”创建开发者配置文件(或描述文件)并导入对应的设备(创建过程中选择支持Game Center服务的App ID,这样iOS设备在运行指定Boundle ID应用程序就知道此应用支持Game Center服务)。 GameCenter_AppProfiler
  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)。GameCenter_AppConfig
  6. 在iOS“设置”中找到Game Center允许沙盒,否则真机无法调试(如果是模拟器不需要此项设置)。 GameCenter_DeviceConfig

有了以上准备就可以在应用程序中增加积分、添加成就了,当然在实际开发过程积分和成就都是基于玩家所通过的关卡来完成的,为了简化这个过程这里就直接通过几个按钮手动触发这些事件。Game Center开发需要使用GameKit框架,首先熟悉一下常用的几个类:

GKLocalPlayer:表示本地玩家,在GameKit中还有一个GKPlayer表示联机玩家,为了保证非联网用户也可以正常使用游戏功能,一般使用GKLocalPlayer。

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

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

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

GKAchievementDescription:成就描述信息,包含成就的标题、获得前描述、获得后描述、是否可重复获得成就等信息。

GKGameCenterViewController:排行榜、成就查看视图控制器。如果应用本身不需要自己开发排行榜、成就查看试图可以直接调用此控制器。

下面就以一个简单的示例来完成排行榜、成就设置和查看,在这个演示程序中通过两种方式来查看排行和成就:一种是直接使用框架自带的GKGameCenterViewContrller调用系统视图查看,另一种是通过API自己读取排行榜、成就信息并显示。此外在应用中有两个添加按钮分别用于设置得分和成就。应用大致布局如下(图片较大可点击查看大图):

GameCenter_Layout

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

主视图控制器调用GKLeaderboard的loadLeaderboardsWithCompletionHandler:方法加载了所有排行榜,这个过程需要注意每个排行榜(GKLeaderboard)中的scores属性是没有值的,如果要让每个排行榜的scores属性有值必须调用一次排行榜的loadScoresWithCompletionHandler:方法。

调用GKAchievement的loadAchievementsWithCompletionHandler:方法加载加载成就,注意这个方法只能获得完成度不为0的成就,如果完成度为0是获得不到的;然后调用GKAchievementDesciption的loadAchievementDescriptionsWithCompletionHandler:方法加载了所有成就描述,这里加载的是所有成就描述(不管完成度是否为0);紧接着调用了每个成就描述的loadImageWithCompletionHandler:方法加载成就图片。

将获得的排行榜、成就、成就描述、成就图片信息保存,并在导航到详情视图时传递给排行榜视图控制器和成就视图控制器以便在子控制器视图中展示。

在主视图控制器左上方添加查看游戏中心控制按钮,点击按钮调用GKGameCenterViewController来展示排行榜、成就、玩家信息,这是系统自带的一个游戏中心视图方便和后面我们自己获得的信息对比。

程序如下

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

在排行榜控制器视图中定义一个leaderboards属性用于接收主视图控制器传递的排行榜信息并且通过一个UITableView展示排行榜名称、得分等。

在排行榜控制器视图中通过GKScore的reportScores: withCompletionHandler:设置排行榜得分,注意每个GKScore对象必须设置value属性来表示得分(GKScore是通过identifier来和排行榜关联起来的)。

程序如下

3.最后就是成就视图控制器KCAchievementTableViewController:

在成就视图控制器定义achievements、achievementDescriptions、achievementImages三个属性分别表示成就、成就描述、成就图片,这三个属性均从主视图控制器中传递进来,然后使用UITableView展示成就、成就图片、成就进度。

创建GKAchievemnt对象(通过identifier属性来表示具体的成就)并指定完成度,通过调用GKAchievement的reportAchievements: withCompletionHandler:方法提交完成度到Game Center服务器。

程序如下

运行效果:

GameCenter_Effect

注意首次使用游戏时由于没有对Game Center授权,会提示用户登录Game Center。

内购

大家都知道做iOS开发本身的收入有三种来源:出售应用、内购和广告。国内用户通常很少直接购买应用,因此对于开发者而言(特别是个人开发者),内购和广告收入就成了主要的收入来源。内购营销模式,通常软件本身是不收费的,但是要获得某些特权就必须购买一些道具,而内购的过程是由苹果官方统一来管理的,所以和Game Center一样,在开发内购程序之前要做一些准备工作(下面的准备工作主要是针对真机的,模拟器省略Provisioning Profile配置过程):

  1. 前四步和Game Center基本完全一致,只是在选择服务时不是选择Game Center而是要选择内购服务(In-App Purchase)。
  2. 到iTuens Connect中设置“App 内购买项目”,这里仍然以上面的“KCTest”项目为例,假设这个足球竞技游戏中有三种道具,分别为“强力手套”(增强防御)、“金球”(增加金球率)和“能量瓶”(提供足够体力),前两者是非消耗品只用一次性购买,后者是消耗品用完一次必须再次购买。In-App_Purchase_Config
  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方法反馈恢复的交易(也就是已购买的非消耗品交易,注意这个过程中如果没有非消耗品可恢复,是不会调用此方法的)。

下面通过一个示例程序演示内购和恢复的整个过程,程序界面大致如下:

主界面中展示了所有可购买产品和售价,以及购买情况。

选择一个产品点”购买“可以购买此商品,购买完成后刷新购买状态(如果是非消耗品则显示已购买,如果是消耗品则显示购买个数)。

程序卸载后重新安装可以点击”恢复购买“来恢复已购买的非消耗品。

In-App_Purchase_Layout

程序代码:

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

In_AppPurchase_Effect

扩展–广告

上面也提到做iOS开发另一收益来源就是广告,在iOS上有很多广告服务可以集成,使用比较多的就是苹果的iAd、谷歌的Admob,下面简单演示一下如何使用iAd来集成广告。使用iAd集成广告的过程比较简单,首先引入iAd.framework框架,然后创建ADBannerView来展示广告,通常会设置ADBannerView的代理方法来监听广告点击并在广告加载失败时隐藏广告展示控件。下面的代码简单的演示了这个过程:

运行效果:

iAd

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”:

iCloud_CapabilitiesConfig

在项目中会自动生成一个”kctest.entitlements”配置文件,这个文档配置了文档存储容器标识、键值对存储容器标识等信息。

iCloud_EntiementsFile

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::打开当前文档。