iPhone开发一些读书笔记

手机应用分类
1.教育工具
2.生活工具
3.社交应用
4.定位工具
5.游戏
6.报纸和杂志的阅读器
7.移动办公应用
8.财经工具
9.手机购物应用
10.风景区相关应用
11.旅游相关的应用
12.导航工具
13.企业应用

Delegation模式——delegation(委托)模式就是使用回调机制

NSData、NSMutableData——存放二进制数据的数据类型

对于画图,你首先需要重载drawRect方法。UIKit提供了如下方法:
UIRectFill(CGRect rect);//填充整个框
UIRectFrame(CGRect rect);//指定框的颜色

UINavigationController(导航控制器)是UIViewController的子类,由UIKit提供。
导航控制器所管理的视图控制器之间是分层关系。它使用堆栈的方式来管理多个视图控制器

UITabBarController(标签栏控制器)也是控制一些视图控制器。与导航控制器不同,标签栏控制器是用数组管理视图控制器。
这些被管理的视图控制器既可以是导航控制器,也可以是一般的视图控制器。另外,这些视图控制器之间是平等关系,而不像导航控制器所管理的视图控制器之间的上下级关系

两个页面之间传递数据:
常规的做法是在下一个视图控制器上声明所需要的属性。然后当上一个视图控制器调用下一个视图控制器时,就可以设置这些属性值
如果要从第二个视图那里回传数据给第一个视图,这是委托类的功能

UIWebView常用方法:
1.装载URL所指定的网页:
- (void)loadHTMLString:(NSString *)string baseURL:(NSURL *)baseURL;
- (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType;
2.装载URL请求:
- (void)loadRequest:(NSURLRequest *)request;
3.网页相关的方法:
- (void)reload;//重新装载
- (void)stopLoading;//停止装载
- (void)goBack;//返回前一个网页
- (void)goForward;//前进到下一个网页(如果存在的话)

常用属性:
@property BOOL loading;//是否在装载
@property BOOL canGoBack;//是否可以返回前一个
@property BOOL canGoForwar;//是否可以前进到下一个
@property BOOL scalesPageToFit;//是否自动调整网页到UIWebView所在的屏幕
@property BOOL detectsPhoneNumbers;//是否侦测网页上的电话号码。如果是,当用户点击该号码时,就可以用iPhone拨打这个电话

下面是delegate(委托)类提供的一些回调方法:
//在装载网页之前调用。
- (void)webViewDidStartLoad:(UIWebView*)webView;
//在装载完网页之后调用
- (void)webViewDidFinishLoad:(UIWebView*)webView;
//处理装载网页失败的方法
- (void)webView:(UIWebView*)webView didFailLoadWithError:(NSError *)error;
//控制导航的方法。比如:用户单击网页上的链接时,该方法可以决定是否让用户导航到该链接。
//navigationType是指:单击链接、重新装载网页、提交内容、返回到前一个网页、前进到下一个网页等
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;

设备之间通信——两种方法:Bonjour和GameKit

苹果推服务(Apple Push Notification Service)
第一步:应用服务器(如股票网站)需要从苹果获得数字证书,并把证书放在应用服务器上。从而应用服务器和苹果推服务平台就可以通信
第二步:应用程序向苹果注册服务。下面的方法设置应用程序接收的通知类型:

- (void)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)options
{
UIRemoteNotificationType myTypes = UIRemoteNotificationTypeSounds | UIRemoteNotificationTypeBadges;//通知类型:声音和Badge
[application registerForRemoteNotificationTypes:myTypes];//注册
}

第三步:从iPhone操作系统获取Token,并发送给应用服务器(如股票网站):Token就是一个标识这个特定手机的字符和数字的组合串

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)token
{
//...获得Token,发送Token给应用服务器
}
//注册失败的处理
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{
....
}

第四步:使用UIApplicationDelegate的didReceiveRemoteNotification方法来接收远程通知并做一些处理(所接收的通知是JSON数据)

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo

如果你想知道手机应用正在接收什么通知,可以调用下面的方法:

-  (UIRemoteNotificationType)enabledRemoteNotificationTypes

应用设置
在XCode下,应用设置是通过添加一个Setting Bundle文件完成的。在添加之后,系统会创建两个项目:Root.plist和en.lproj。后一个用于本地化设置,前一个就是第一iPhone下的设置信息。
在Root.plist下你可以设置各个属性和值。在代码中,你使用NSUserDefaults读取这些属性值

iPhone与iPad开发实战读书笔记

第二章:Object-c和iPhone OS SDK

1.事件响应
事件主要以3种方式出现在iPhone和iPad上:通过空事件(或动作),通过委托,或者通过通知。事件响应来自UIResponder对象,通知来自于NSNotificationCenter

响应链是一组对象链接集,其中大多数是通过视图层次结构向上延伸的。任何输入都是由第一响应者先捕捉到的,该响应者通常是与用户直接交互的对象。如果此对象不能解析,那么它将会将输入向上发送到其超视图,不断向上。如果输入沿着视图层次结构一直向上至窗口对象,那么之后,它会被发送到应用程序本身,并最终传递到应用程序委托
这些对象中的任何一个都可以选择处理一个事件,这将会停止在响应链中向上传递。按照标准的MVC模型,你通常要将事件响应构建到UIViewController对象中,此对象在响应者链中相当远
对于任一种UIController对象,如按钮、滑块和切换开关,事件通常会转变成动作。事件报告屏幕的触摸,而动作则报告控件的操作。动作所遵循的响应层次结构略有不同

除了通过第一响应者之外,还有另外一种方式将事件发送到对象:通过委托。也就是一个对象(通常是视图控制器)处理另外一个对象(通常是视图)的事件。它是数据源的近亲,数据源也是一个对象(通常也是视图控制器),处理另外一个对象(通常也是视图)的数据设置和控制

委托和数据源分别由一个协议控制,协议是委托和数据源同意响应的一组方法。例如,表格的委托可能必须响应表格行呗选定时发出警告的方法。类似的,表格的数据源可能用于描述所有表格行的外观
委托和数据源可以完美地适应Object-c使用的MVC模型,因为它们允许视图将其工作转交给其控制器而不必担心这些对象在响应链中得位置

标准事件响应和委托代表了就标准事件(例如用手指触摸屏幕)向对象发出警告的两种方式。还可以使用第三种方式——通知

2.生命周期管理:

方法 对象 总结
applicationDidFinishLaunching: UIApplicationDelegate 应用程序已经加载,你应该创建初始窗口或将程序启动
applicationDidReceiveMemoryWarning: UIApplicationDelegate 应用程序收到低内存警告,你应当释放内存
applicationWillTerminate: UIApplicationDelegate 应用程序即将结束,你应该释放内存并保存状态
init NSObject 创建对象,你应该在此初始化它
dealloc NSObject 对象释放了其内存,你应该释放所有没有自动释放的对象

第三章:使用XCode

1.理解应用程序委托——应用程序委托用于答复许多应用程序的消息
要访问应用程序委托,使用UIApplication类方法:[UIApplication sharedApplication];
应用程序委托必须响应生命周期消息,最重要的是applicationDidFinishLaunching:消息,因为是它运行了程序的真正内容
一个应用程序委托应该能完成如下工作:
A.在启动时,必须能创建应用程序的窗口并向用户显示
B.必须能初始化数据
C.必须能响应“quit”(退出)消息
D.必须能处理低内存警告

第五章:创建基本视图控制器

1.单页面控制器主要支持MVC模型的控制器角色,而多页面控制器主要支持导航,甚至将MVC任务委托给其下属的一个更简单地视图控制器

基本视图控制器:视图控制器是一个位于(任何种类的)视图之上的UIViewController对象。而它作为树状结构的一部分,又位于其他对象的下面,最终会回到应用程序的主窗口。
基本视图控制器通常会位于高级视图控制器之下,负责高级视图控制器允许导航的几个独立页面

视图控制器只有一个视图,但这个视图可以具有多个不同的子视图,展开成一个层次结构。

UIView(不是控制器)包含两个影响如何调整大小的属性。autoresizesSubviews属性是一个布尔值,用于确定是否自动调整大小,默认为YES。如果设置了这个属性,就是说将调整大小,那么视图会查看它的autoresizingMask属性,以确定应该如何调整大小

属性 概述
modalViewController 对临时视图控制器的引用,比如地址薄和相册的控制器
navigationController 对导航控制器类型的父对象的引用
parentViewController 对直接父视图控制器的引用,或者为nil(当没有嵌套的视图控制器时)
tabBarController 对标签栏控制器类型的父对象的引用
tabBarItem 对与这个特定视图相关的标签栏条目的引用
view 对控制器的托管视图的引用。视图的subviews属性可用于在层次结构中进一步向下扩展

2.UIViewController具有几个自己的生命周期方法:

方法 概述
loadView: 创建视图控制器的视图,如果没有从.xib文件加载
viewDidLoad: 提示你视图已经加载完成。如果从.xib文件加载的话,这是放置额外启动代码的地方
viewWillAppear: 刚好在视图加载之前运行
viewWillDisappear: 刚好在视图消失(因为它被遮盖了)之前运行
willRotateToInterfaceOrientation:duration: 在旋转开始时运行
didRotateToInterfaceOrientation: 在旋转结束时运行

loadView和viewDidLoad方法它们作为视图控制器设置例程的一部分来运行,用于添加额外的子视图。然后发送viewWillAppear消息。其余消息在适当的时候发送

3.表视图控制器
UITableViewController控制UITableView,后者是一个对象,包含一些排列在一列中得UITableViewCell对象。
默认情况下,控制器既是UITableView的委托,又是它的数据源。这些属性有助于视图将事件和动作委托给它的控制器

第六章:监控事件和动作

1.响应者链
响应者链的正常流动将被委托截断。一个特点的对象(通常是视图)委托另外一个对象(通常是视图控制器)来为它完成操作
如果一个事件通过响应者链的所有环节,到达窗口,并且该窗口不能处理事件,那么事件将上行到UIApplication本身。UIApplication经常将事件交给它自己的委托——应用程序委托

首要响应者和键盘
由于首要响应者是一个可以接受输入的对象,所以它有时会完成一个特殊的动作,表明它已做好准备,可以接收输入了。对于UITextField和UITextView这样的文本就是如此。在变成首要响应者时,如果它们可编辑的话,会弹出一个键盘。这有两个直接结果:
如果你想要为文本对象弹出一个键盘,那么方法是将它变成首要响应者:
[myText becomeFirstResponder];
类似的,如果你要去掉键盘,就必须告诉文本对象不再作为首要响应者:
[myText resignFirstResponder];

2.触摸和事件
通过将大量触摸(由多个UITouch对象表示)组合成一个事件(由一个UIEvent对象表示),SDK抽象了事件

UITouch:UITouch对象是在一个手指放在屏幕上、在屏幕上移动或者离开屏幕时创建的。下面的几个属性和实例方法可以给出关于触摸的额外消息:

方法或属性 类型 概述
phase 属性 返回一个触摸阶段常量,指出触摸是开始、移动、结束还是取消:
UITouchPhaseBegan、UITouchPhaseMoved、UITouchPhaseStationary、UITouchedPhaseEnded或UITouchPhaseCancelled
tapCount 属性 屏幕被敲击的次数
timestamp 属性 触摸何时发生或改变
view 属性 触摸开始时所在的视图
window 属性 触摸开始时所在的窗口
locationInView: 方法 给出触摸在指定视图中的当前位置
previousLocationInView: 方法 给出触摸在指定视图中得前一位置

UIEvent:为了更容易看出发生的单次触摸如何作为更复杂的手势的一部分,iPhone SDK将多个UITouch组织成一个UIEvent。它包含大量的属性和方法,用来获得关于事件的更多消息:

方法或属性 类型 概述
timestamp 属性 事件的时间
allTouches 方法 与接收方相关的所有事件触摸
touchesForView: 方法 与视图相关的所有事件触摸
touchesForWindow: 方法 与窗口相关的所有事件触摸

响应者方法:那么是如何实际访问这些触摸和事件的呢?是通过4个不同的UIResponder方法来做到的:

方法 概述
touchesBegan:withEvent: 当手指触摸屏幕时,报告UITouchPhaseBegan事件
touchesMoved:withEvent: 当手指在屏幕上移动时,报告UITouchPhaseMoved事件
touchesEnded:withEvent: 当手指离开屏幕时,报告UITouchPhaseEnded事件
touchesCancelled:withEvent: 当手机放到耳边时,或者发生其他会导致外部取消的事件时,报告UITouchPhaseCancelled事件

这些方法都有两个参数:NSSet(当前阶段发生的触摸的集合)和UIEvent(提供到整个事件的相关触摸的链接)

实例:

//reportView.m
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//使用UIViewController来响应触摸消息,这种方法更好
reportViewController *controller = [UIViewController alloc] init];
[controller manageTouches:touches];
//使用响应者链来处理触摸消息
[self.nextResponder manageTouches:touches];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
[self.nextResponder manageTouches:touches];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
[self.nextResponder manageTouches:touches];
}
//下面一个方法是利用在ReportView中获取到的touchs集合去控制程序中的各个元素做各种事情。下面的代码实现了一个类似于鼠标跟随的触摸跟随特效;
-(void)manageTouches:(NSSet*)touches{
for (UITouch *touch in touches) {
if (touch.phase == UITouchPhaseBegan) {
CGPoint touchPos = [touch locationInView:self.view];
startField.text =[NSString stringWithFormat:@"Begin:%3.0f,%3.0f",touchPos.x,touchPos.y];
}else if(touch.phase == UITouchPhaseMoved){
bottomLabel.text = @"Touch 正在移动中...";
CGPoint touchPos = [touch locationInView:self.view];
moveField.text = [NSString stringWithFormat:@"move:%3.0f,%3.0f",touchPos.x,touchPos.y]; [self touchMoveWithPoint:touchPos]; }else if(touch.phase == UITouchPhaseEnded){
if (touch.tapCount>) {
bottomLabel.text = [NSString stringWithFormat:@"Taps:%2i",touch.tapCount];
}else{
bottomLabel.text = @"等待交互...";
}
CGPoint touchPos = [touch locationInView:self.view];
endField.text = [NSString stringWithFormat:@"End:%3.0f,%3.0f",touchPos.x,touchPos.y]; }
}
}

3.其他事件功能

有很多方式可以修改如何报告事件(以及是否报告)。可以看到,以下3种不同的对象让你可以完成这类规则,即UIResponder、UIView和UIApplication:

方法或属性 类型 概述
nextResponder UIResponder方法 默认返回响应链中的下一个响应者,但是可以进行修改
hitTest:withEvent: UIView方法 默认返回包含一个点的最深子视图,但是可以进行修改
exclusiveTouch UIView属性 一个布尔值,默认为NO,控制同一窗口中的其他视图是否被接收事件阻塞
multipleTouchEnabled UIView属性 一个布尔值,默认为NO,控制是否在第一次触摸之后拒绝多点触摸
beginIgnoringInteractionEvents UIApplication方法 关闭触摸事件处理
endIgnoringInteractionEvents UIApplication方法 打开触摸事件处理
isIgnoringInteractionEvents UIApplication方法 说明应用程序是否在忽略触摸事件

a.UIResponder规则——UIResponder是捕获事件的方法的来源。大多数与响应者链相关的方法都不能直接被代码使用,而通常出现在框架中。
becomeFirstResponder和resignFirstResponder(它们控制第一响应者是谁),以及canBecomeFirstResponder、canResignFirstResponder和isFirstResponder(它们返回与所关心的信息相关的布尔值),都是典型的这类方法。对于nextResponder(只是返回下一个响应者),可以用来传递触摸

b.UIView规则——进入UIView类方法时,可以通过改写hitTest:withEvent:来采取对应的方法。该方法接受一个CGPoint和一个事件作为参数,默认返回包含这个点的最深子视图。通过编写一个新方法,可以让你的响应者链从另外的点开始
两个UIView属性:exclusiveTouch声明,当前视图是唯一可接收事件的视图,同时multipleTouchEnabled启动报告多点触摸事件,否则多点触摸事件将被忽略

c.UIApplication规则——这些方法位于正常的对象层次结构之外,因此不能从我们得视图对象使用这些方法,而是需要直接从UIApplication对象调用它们:
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];

4.动作
如果你通常不会直接使用事件编程,那么如何访问用户输入呢?答案是使用动作(action)。
通常依赖于预先存在的文本视图、按钮和其他小部件来运行程序。使用这些对象时,根本不用考虑原始事件,而是可以用UIControl生成的控制事件和动作来构建程序

UIControl是UIView的子类(从而也是UIResponder的子类)。它是一些重要用户界面控件(如UIButton、UISwitch、UIPageControl、UISegementedControl、UISlider和UITextField)的父类
UIControl类包含几个用于控制它的基本设置的属性,如enabled(是否打开)、highlighted(可视状态)和selected。你也可以使用beginTrackingWithTouch:withEvent:、continueTrackingWithTouch:withEvent:和endTrackingWithTouch:withEvent:直接访问控件的触摸事件,这些方法类似UIResponder的事件响应函数。但是我们根本不会用到它们,因为它们不能带来使用控件对象时你会看到的优势

控件事件和动作
UIControl对象接受触摸事件,最终将它们转换为简单的动作。
控件事件分为三类:触摸事件、编辑事件和滑块事件。触摸事件描述用户的手指如何与控件交互,编辑事件描述对UITextField的更改,UIContorlEventValueChanged事件描述对UISlider的更改

概述
UIControlEventTouchDown 一次手指触摸
UIControlEventTouchDownRepeat 一次重复的手指触摸(tapCount>1)
UIControlEventTouchDragInside 一次结束于控件之中的手指移动
UIControlEventTouchDragOutside 一次结束于控件之外的手指移动
UIControlEventTouchDragEnter 一次进入控件的手指移动
UIControlEventTouchDragExit 一次退出控件的手指移动
UIControlEventTouchUpInside 手指在控件内从屏幕上离开
UIControlEventTouchUpOutside 手指在控件外从屏幕离开
UIControlEventTouchCancel 一个系统事件取消了触摸
UIControlEventValueChanged 一个滑块(或其他类似的)对象改变了它的值
UIControlEventEditingDidBegin 已经在UITextField中开始编辑
UIControlEventEditingChanged 已经在UITextField中更改编辑
UIControlEventEditingDidEnd 由于对象外面的一次触摸,已经在UITextField中结束编辑
UIControlEventEditingEndOnExit 由于一次触摸,已经在UITextField中结束编辑
UIControlEventAllTouchEvents 针对所有与触摸相关的事件的复合控件事件
UIControlEventAllEdtingEvents 针对与编辑相关的事件的复合控件事件
UIControlEventAllEvents 针对所有事件的复合控件事件

一旦一个标准事件转变成了控件事件,就会调用一系列额外的方法。首先,UIControl对象调用sendActionsForControlEvents:。该方法又分解发送给它的事件,并调用sendAction:to:forEvent:,每个事件调用一次。这里,控件事件转变成动作,动作是一个将特定目标对象中运行的特定方法。最后,UIApplication方法sendAction:to:fromSender:forEvent:由控件调用,还是每个事件调用一次
注意:如果动作被发送到的目标已经列出为nil,那么动作会被发送到第一响应者,从那里开始沿着响应者链前进

5.向应用程序添加按钮:两种方法
使用addTarget:action:forControlEvents:方法——[myBtn addTarget:self action:@selector(repeat:) forControlEvents:UIControlEventTouchUpInside];
使用Interface Builder的IBAction方法

第七章:创建高级视图控制器

1.标签栏视图控制器——需要一种至少包含6个对象的层次结构:
一个UITabBarContoller+最少两个UIViewController+一个UITabBar+最少两个UITabBarItem

2.导航控制器——一个最基本的导航控制器中有4个对象:UINavigationController、一个包含UIViewController的栈、UINavigationBar、和UINavigationItem(它放置在UINavigationBar中),以及可选的UIBarButtonItem

3.翻转控制器——要创建翻转控制器,则在启动新项目时选择Utility Application模板。翻转控制器包含3个视图控制器和两个视图

4.分割视图控制器——特定于iPad,创建时选择项目模板:SplitView-Based Application

5.弹出式视图控制器和模式视图控制器——弹出式和翻转控制器类似,选择Utility Application模板

第八章:数据:动作、首选项和文件

1.接收用户动作——接收用户新数据最简单的方法就是使用UIControl

2.保存用户首选项的三种方法:
A.在一个文件中保存首选项——可以使用纯文本或者更规范的格式(如XML)
B.在数据库保存首选项
C.使用NSUserDefault保存首选项
D.使用setting bundle(使用XCode时,可能遇到3种软件包:框架软件包、应用程序软件包和设置软件包)

Setting bundle的root.plist是一个XML文件。所有的其余设置都出现在PreferencesSpecifiers类别下。
可以在Setting plist文件里输入7种类型的数据,每一种数据都可以在Settings页面上创建一种特定工具

NSUserDefaults是一种特定于用户首选项的存储机制。NSUserDefaults是一个持久共享的对象,可以从一个会话移动到另外一个会话时用来记住用户的首选项:

方法 概述
standardUserDefaults 创建共享默认对象的类方法
objectForKey: 返回键对象的实例方法
setObjectForKey: 设置键对象的实例方法
resetStandardUserDefaults 保存共享对象发生得所有更改的类方法

3.访问软件包:访问软件包中得文件通常分为两个步骤,如以下数据库示例所示:

NSString *paths = [[NSBundle mainBundle] resourcePath];//mainBundle返回对应于应用程序软件包的目录路径,resourcePath则返回程序资源的目录路径
NSString *bundlePath = [paths stringByAppendingPathComponent:@"dbFile"];//使用stringByAppendingPathComponent方法向路径添加特定文件。NSString方法确保能根据需要使用斜杠(//)构建路径

得到的结果是一个完整路径,可以根据需要传递给其他对象。可以用于UIImage的imageWithContentsOfFile:方法或者NSFileHandle的fileHandleForReadingAtPath方法

4.访问其他目录:处理软件包类目录时,必须考虑两件事件:如何访问这些文件,如何在多个目录之间移动文件
A.检索文件:当文件位于Documents目录时,就可以像从软件包目录检索文件一样来检索它(NSArray中得第一条路径通常是我们需要的):

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [paths objectAtIndex:];
NSString *docPath = [documentsDirectory stringByAppendingPathComponent:dbFile];

B.复制文件:使用NSFileManager(文件管理器)

NSFileManager *fileManager = [NSFileManager defaultManager];
success = [fileManager copyItemAtPath:bundlePath toPath:docPath error:&error];

C.操纵文件:一些使用SDK操纵文件的方法

方法 概述
NSFileHandle fileHandleForReadingAtPath:
fileHandleForWritingAtPath:
fileHandleForUpdatingAtPath:
可以用于打开文件的类方法
NSFileHandle readDataofLength: 返回一个NSData,其中包含文件中指定字节数目的字节
NSFileHandle readDataToEndOfFile 返回带有文件其余内容的NSData
NSFileHandle closeFile 关闭NSHandle
NSFileManager contentsAtPath: 返回带有完整文件内容的NSData
NSData initWithContentsOfFile: 创建带有完整文件内容的NSData
NSData writeToFile:atomically: 将NSData写入文件
NSString stringWithContentsOfFile:encoding:error: 返回带有完整文件内容的NSString

NSString

NSString

initWithData:encoding:

writeToFile:atomically:encoding:error:

返回带有NSData内容的NSString

将NSString写入文件

第九章:数据:高级技术

1.SQLite数据库:下面展示了最关键的API.它们通常设计两个重要的概念:数据库句柄(sqlite3_open的返回值,可在任何地方使用)和已备语句(sqlite3_prepare的返回值,用于运行查询)

函数 参数 概述
sqlite3_open 文件名,数据库地址 打开数据库
sqlite3_prepare 数据库,UTF-8格式的SQL,要读取的最大长度,语句的地址,未读取结果的地址 将UTF-8格式的SQL语句转换为指向已备语句的指针,该语句可以传递给其他函数
sqlite3_step 已备语句 处理已备语句的一行结果,或者返回一个错误
sqlite3_column_int 已备语句,列号 返回活动行中的一个整数,还有一些其他函数,返回活动行的特定列
sqlite3_column_string 已备语句,列号 返回活动行的一个字符*,即一个字符串。还有一些其他函数,返回活动行的特定列
sqlite3_finalize 已备语句 删除一条已备语句
sqlite3_close 数据库 关闭数据库

这些函数(按顺序)展示SQLite数据库的常见生命周期:
打开数据库->准备语句,一次一条->单步调试语句,读取列->完成语句->关闭数据库

SQLite包含两个便捷函数——sqlite3_exec()和sqlite3_get_table(),他们可以简化上面的步骤

2.地址薄——Address Book

3.Core Data——构建于SQLite之上

第十章:定位:加速计、位置和罗盘

1.加速计和方向
加速计最常用于确定设备的当前方向。使用视图控制器的interfaceOrientation属性

访问UIDevice:UIDevice *currentDevice = [UIDevice currentDevice];
方向通知:UIDevice类还可以让你在方向发送变化时立即进行访问。这是通过通知时限的:

[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceDidRotate:) name:@"UIDeviceOrientationDidChangeNotification" object:nil];

相对应的方法:endGeneratingDeviceOrientationNotifications

2.加速计和移动
使用设备在三维空间中的移动以作为新的用户输入设备
A.访问UIAccelerometer——使用UIAccelerometer类可以接收与加速相关的数据。像UIApplication和UIDevice一样,它也是一个共享对象。用法:

- (void)viewDidLoad{
UIAccelerometer *myAccel = [UIAccelerometer sharedAccelerometer];
myAccel.updateInterval = .;
myAccel.delegate = self;
[super viewDidLoad];
}

updateInterval属性是更新间隔。这受硬件限制,默认值为每秒100次更新。如果用加速计创建游戏,这个频率刚好,但是对于其他用途则太频繁了。调整为每秒10次更新:即为0.1。应该总数将它设置为可以接受的最低值以减少设备的耗电量
还必须为加速计设置一个委托,它表示你怎样接收有关加速计更改方面的数据。委托只需要对一种方法响应:accelerometer:didAccelerate:,该方法发送在加速(达到updateInterval的限度)时发送包含UIAcceleration对象的消息。需要在接口声明UIAccelerometerDelegate协议

B.访问UIAcceleration——使用UIAcceleration可以实现两项测量:设备与重力的关系以及设备在三维空间中的移动。通过3个属性x、y和z(表示三维数轴)的集合可以实现这两个测量
x轴的方向沿着iPhone或iPad的短边,y轴沿着长边,z轴的方向沿着iPhone或iPad的厚度。所有的测量值单位都是"g",表示重力。值为1g表示地球海平面的重力值

访问加速计时要注意,它测量两种适用于设备的力:任何方向上得推力和重力,单位为g。这表示iPhone或iPad始终会朝地心方向显示1g的加速。如果要执行更复杂的操作,则有可能需要进行滤波

C.查看重力——当加速计静止时,它们检测的是重力。可以用来检测设备当前所处的准确方向,超出了orientation变量支持的4种或6种状态

a.读取加速信息——下面的代码展示了如何使用加速计修改redBall,它是一个画着红球的图片UIImage,使用IB创建,最初设置在屏幕中间:

- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
CGPoint curCenter = [redBall center];
float newX = * acceleration.x + curCenter.x;
float newY = * acceleration.y + curCenter.y;
if (newX < ) newX = ;
if (newY < ) newY = ;
if (newX > ) newX = ;
if (newY >) newY = ;
redBall.center = CGPointMake(newX,newY);
}

所有的加速计程序都是以accelerometer:didAccelerate:方法开始,通过将当前程序设置为Accelerometer共享动作的委托可以访问该方法
要访问加速计,要做的就是查看UIAcceleration对象的x和y坐标,然后再基于该坐标修改redBall的位置。上面是加速了3倍

b.过滤出移动——要创建一个允许重力通过但不允许移动通过的低通滤波器,你需要对收到的加速信息计算平均值,以便在任何时候你的大部分输入都来自稳定的重力。下面代码修改了前面的示例:

gravX = (acceleration.x * kFilteringFactor) + (gravX * ( - kFilteringFactor));
gravY = (acceleration.y * kFilteringFactor) + (gravY * ( - kFilteringFactor));
float newX = * gravX + curCenter.x;
float newY = * gravY + curCenter.y;

本例依赖一个预定义的变量:kFilteringFactor,这里设置为0.1,表示只有10%的移动可以在任何时间使用;gravX和gravY在程序运行时各自维护一个轴方向的累积平均移动量
我们过滤出的东西是通过将活动移动量的10%和平均值的90%进行平均后得到的。这将抚平任何不平的地方,也就是说突然的加速将被忽略掉大部分。本例对x轴和y轴这样做,因为本例中仅使用了这两个轴。如果你关心z轴,你也需要对该轴进行过滤

D.查看移动——上一个示例中,创建了一个简单地低通滤波器,可以分离出加速计数据的重力部分,有了该数据,创建一个高通滤波器就很容易了。所需要做的只是从加速值中除去低通滤波数据,结果是一个纯移动数据:

gravX = (acceleration.x * kFilteringFactor) + (gravX * ( - kFilteringFactor));
gravY = (acceleration.y * kFilteringFactor) + (gravY * ( - kFilteringFactor));
float MoveX = acceleration.x - gravX;
float moveY = acceleration.y - gravY;

E.识别简单的加速计运动——如果你想使用加速手势编写程序,建议你从苹果开发人员网站下载 Accelerometer Graph程序

- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
//accelX/Y/Z 这是收集滤出信息:创建一个低通滤波器
accelX = ((acceleration.x * kFilteringFactor) + (accelX * ( - kFilteringFactor)));
accelY = ((acceleration.y * kFilteringFactor) + (accelY * ( - kFilteringFactor)));
accelZ = ((acceleration.z * kFilteringFactor) + (accelZ * ( - kFilteringFactor)));
//测量移动:获取相对干净的移动数据
float moveX = acceleration.x - accelX;
float moveY = acceleration.y - accelY;
float moveZ = acceleration.z - accelZ;
//标记开始时间:一开始不需要接收发送地任何加速数据
if (!starttime){
starttime = acceleration.timestamp;
}
//当其中一个加速计上升到0.3g以上时,开始查看移动。发生这种情况时,保存最高移动量所在的方向,在移动量低于0.3g之前继续测量。要确保至少经历了1/10秒,以便了解移动时没有间断
if (acceleration.timestamp > starttime + &&
(fabs(moveX) >= . ||
fabs(moveY) >= . ||
fabs(moveZ) >= .)){
//保存最大移动量
if (fabs(moveX) > fabs(moveVector)) {
moveVector = moveX;
moveDir = (moveVector > ? @"Right" : @"Left");
}
if (fabs(moveY) > fabs(moveVector)) {
moveVector = moveY;
moveDir = (moveVector > ? @"Up" : @"Down");
}
if (fabs(moveZ) > fabs(moveVector)) {
moveVector = moveZ;
moveDir = (moveVector > ? @"Forward" : @"Back");
}
lasttime = acceleration.timestamp;
}
else if (moveVector && acceleration.timestamp > lasttime + .)
{
myReport.text = [movDir stringByAppendingFormat:@": %f.", moveVector];
moveDir = [NSString string];
moveVector = ;
}
}

3.加速计和手势——要识别三维手势,必须要做两件事:首先必须准确地跟踪组成手势的移动。其次必须确保这样做是不是在识别并非手势的随机操作
我们定义一个摇动手势。假设程序以纵向模式运行时,它显示摇动具有这些特征:
a.移动的主要方向是沿着x轴,但有些是沿y轴,甚至有少量移动沿着z轴
b.移动至少有3次高峰,正向力和反向力交替出现
c.所有高峰至少是+-1g,对于相对较强的摇动一次高峰至少+-2g

- (BOOL)didShake:(UIAcceleration *)acceleration
{
accelX = ((acceleration.x * kFilteringFactor) + (accelX * ( - kFilteringFactor)));
accelY = ((acceleration.y * kFilteringFactor) + (accelX * ( - kFilteringFactor)));
float moveX = acceleration.x - accelX;
float moveY = acceleration.y - accelY;
if (lasttime && acceleration.timestamp > lasttime + .)//摇动一次之后等待<1>
{
BOOL result;
if (shakecount >= && biggestshake >= 1.25) {
result = YES;
}
else{
result = NO;
}
lasttime = ;
shakecount = ;
biggestshake = ;
return result;
}
else
{
if (fabs(moveX) >= fabs(moveY)) //检查x轴的移动<2>
{
if ((fabs(moveX) > . ) && (moveX * lastX <= ))
{
lasttime = acceleration.timestamp;
shakecount++;
lastX = moveX;
if (fabs(moveX) > biggestshake) biggestshake = fabs(moveX);
}
}
else
{
if ((fabs(moveY) > . ) && (moveY * lastY <= ))//检查y轴的移动<3>
{
lasttime = acceleration.timestamp;
shakecount++;
lastY = moveY;
if (fabs(moveY) > biggestshake) biggestshake = fabs(moveY);
}
}
return NO;
}
}

这段代码中,我们遵循Accelerometer Graph里使用的逻辑,但灵敏度更高。didShake:方法在检测到3个或3个以上、至少为0.75g的移动时(至少有一个为1.25g,移动的方向相反)会记录为一次摇动。
首先从加速计数据中移除重力。这与上个示例相同。这一次不需要担心初始数据的异常,它不会记录为摇动,因为它远小于1g
该函数的主要工作反应在后半部分,只要发生移动就会调用这部分。首先,检查最大的移动量是否沿x轴方向<2>。如果是,就查看其是否超过0.75g,并查看其方向是否与上一次x轴移动相反,如果是,则记录这次移动。后一个查看是看发生在该轴的最近两次移动的乘积是否为负,如果是,则其中一个必为正另外一个必为负,表示它们方向相反

下面代码演示了一个简单地用法,可以在发生摇动时改变屏幕颜色。

- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
if ([self didShake:(UIAcceleration *)acceleration]){
self.view.backgroundColor = [self nextColor];
}
}

4.Core Location——检测用户的位置。使用模拟器只能近似地测试Core Location。经纬度都没问题,但它们总是报告在苹果的总部。也无法显示海拔。可以提出www.geonames.org此网站或者地点的海拔

A.位置类——位置感知内置在两个SDK类和一个协议中。CLLocationManager使你能以各种方式访问位置信息。它包括一个委托协议CLLocationManagerDelegate,该协议定义的方法可以告诉你新的位置信息何时到达。最后,位置信息本身以CLLocation对象的形式出现,每个对象定义在某个具体时间的具体位置

方法/属性 类型 概述
类:CLLocationManager    
delegate 属性 定义响应CLLocationManagerDelegate的对象
desiredAccuracy 属性 设置所需的位置准确度作为CLLocationAccuracy对象
distanceFilter 属性 指定要发生多少侧向移动才会导致位置更新事件
location 属性 指定最近的位置
startUpdatingLocation 方法 开始生成更新事件

stopUpdatingLocation

startUpdatingHeading

stopUpdatingHeading

方法

停止生成更新事件

开始生成航向更新事件

停止生成航向更新事件

headingFilter 属性 生成航向事件所需的最小角度
headingAvailable 属性 如果可以生成航向事件,返回true
类:CLLocationManagerDelegate    
locationManager:didUpdateToLocation:fromLocation: 方法 发生更新事件时进行报告的委托方法
locationManager:didFailWithError: 方法 更新事件无法出现时进行报告的委托方法
类:CLLocation    
altitude 属性 指定位置的高度(以米为单位)
coordinate 属性 以CLLocationCoordinate2D变量形式返回位置坐标
timestamp 属性 在测量位置时指定NSDate
     

B.使用海拔——使用下面的代码确定海拔是否可用:
if (signbit(newLocation.verticalAccuracy)){}//如果返回非0,就不用视图检查海拔信息了

C.使用罗盘——Core Location的CLHeading类,可以确定你的磁航向以及真航向。磁航向使用内置的磁力计确定并且指向磁北,而真航向则使用目前的位置并且指向真正的北方

属性 描述
magneticHeading 指向磁北的航向。该值使用内置的磁力计确定,值的范围是0到360
trueHeading 表示指向真北的航向。该属性依赖于当前的位置,所以无法始终保证是有效的。其值范围是0到360
headingAccuracy 该值表示magneticHeading的错误程度,以度数为单位。较低的值意味着航向相对比较准确。负值意味着航向是无效值,不能信任
timestamp 找到航向时的时间戳

访问罗盘信息与访问GPS信息类似,首先要获得CLLocationManager对象的引用,然后就可以开始收集数据了:

- (void)viewDidLoad
{
CLLocationManager *locationManager = [[[CLLocationManager alloc init] autorelease];
if (locationManager.headingAvailable == YES) {//确保设备支持罗盘
locationManager.delegate = self;
[locationManager startUpdatingHeading];
}
}

要从罗盘获得数据,必须实现CLLocationManagerDelegate方法locationManager:didUpdatingHeading:,每次设备上的罗盘航向改变时会自动调用该方法。传递给该方法的航向变量包含上面那个表中的全部数据,下面的示例演示了如何实现该方法:

- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)heading
{
self.heading = heading;
}

heading变量的两个最重要的属性是magneticHeading和trueHeading。它们类型是CLLocationDirection,这是一个typedef double值。该值范围是0-360°,0°意味设备指向北方,90°意味设备指向东方,180°是南方,270°是西方。如果该值是负数,则是无效值

第十一章:媒体:图像和照相机

1.图像——图像可以显示在UIImageView或UIView中
1.1 加载UIImage——UIImage类提供了7种不同方式创建图像实例。其中4个工厂方法最容易使用。还有些与之等同的init方法:

工厂方法 概述
imageNamed: 基于主软件包(main bundle)中的文件创建UIImage
imageWithCGImage: 从Quartz 2D对象创建UIImage,这与initWithCGImage:相同
imageWithContentsOfFile: 从你指定的完整文件路径创建UIImage,这与initWithContentsOfFile:相同
imageWithData: 从NSData创建UIImage,这与initWithData:相同

图像数据可以是几种文件类型:BMP、CUR、GIF、JPEG、PNG和TIFF。其中JPEG最小,PNG在iPhone硬件上更好看,且被加速
创建一个UIImage时有一个隐含的限制:图像不应该大于1024X1024

1.2 绘制UIImageView(用这个类来显示图像)
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"xx.jpg"]];
可以使用基本的initWithFrame:方法,并手动修改对象的属性

方法或属性 类型 概述
animationDuration 属性 指定多长时间运行一次动画循环
animationImages 属性 识别图像的NSArray,以加载到UIImageView中
animationRepeatCount 属性 指定运行多少次动画循环
image 属性 识别单个图像以加载到UIImageView中
startAnimating 方法 开启动画
stopAnimating 方法 停止动画
- (void)viewDidLoad{
UIImage *image1 = [UIImage imageNamed:@"1.jpg"];
UIImage *image2 = [UIImage imageNamed:@"2.jpg"];
UIImage *image3 = [UIImage imageNamed:@"3.jpg"];
UIImage *image4 = [UIImage imageNamed:@"4.jpg"];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen] bounds];
imageView.animationImages = [NSArray arrayWithObjects:image1,image2,image3,image4,nil];
imageView.animationDuration = ;
[imageView startAnimating];
[self.view addSubView:imageView];
[super viewDidLoad];
}

2.利用Core Graphics绘制简单图像——尽管不能访问整个Core Graphics库,但是UIImage类却包含5个简单地方法,可以充分利用Core Graphics的工作方式

方法 概述
drawAsPatternInRect: 在矩形中绘制图像,不缩放,但是在必要时平铺
drawAtPoint: 利用CGPoint作为左上角,绘制完整的不缩放的图像
drawAtPoint:blendMode:alpha: drawAtPoint:的一种更复杂的形式
drawInRect: 在CGRect中绘制完整的图像,适当地缩放
drawInRect:blendMode:alpha: drawInRect:的一种更复杂的形式

问题是,这些方法依赖于图形上下文而工作。图形上下文就是你绘制的东西要到达的目标,比如窗口、PDF文件或打印机
在iPhone和iPad中,UIView自动创建图形上下文作为其CALayer的一部分,CALayer是与每个UIView相关的Core Animation层。可以通过为UIView(或者更确切的说,为你已经创建的新的子类)编写drawRect方法来访问Core Animation层。下面展示了如何使用该方法拼合一些图片:

- (void)drawRect:(CGRect)rect
{
UIImage *image1 = [UIImage imageNamed:@"1.jpg"];
UIImage *image2 = [UIImage imageNamed:@"2.jpg"];
UIImage *image3 = [UIImage imageNamed:@"3.jpg"];
[image1 drawAtPoint:CGPointMake(,) blendMode:kCGBlendModeNormal alpha:.];
[image2 drawInRect:CGRectMake(,,,)];
[image3 drawInRect:CGRectMake(,,,)];
}

drawAtPoint:可以让你完成更复杂的事情,比如说混合图片(使用类似photoshop的选项,比如颜色减淡和强光)和让图片变得更透明。这里使用的是普通混合,但是只有50%的透明度

3.访问照片——可以使用SDK从iPhone或iPad的相册、照片库等访问图片。也可以允许用户拍摄新照片。这些都是UIImagePickerController完成的

3.1 使用图形选取器
要在iPhone上显示选取器,可以使用一下代码:

UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = self;
imagePicker.allowsImageEditing = NO;
[self presentModalViewController:imagePicker animated:YES];

要在iPad中显示选取器,需要你在UIPopoverController内显示UIImagePickerController。这么做的好处是你可以指定选取器在屏幕上得哪个位置出现:

UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = self;
imagePicker.allowsEditing = NO; UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:imagePicker];
[popover presentPopoverFromRect:CGRectMake(,,,) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];

一旦创建了UIImagePickerController,你就需要让其委托来响应以下两个方法:imagePickerController:didFinishPickingMediaWithInfo:和imagePickerControllerDidCancel:。对于第一个方法,你应该解除模式视图控制器(或者在iPad上隐藏弹出式界面),并适当地响应用户的图片选择。对于第二个方法,则只需要解除控制器

3.2 拍照——UIImagePickerController具有3个可能的源,由以下常量表示:
UIImagePickerControllerSourceTypePhotoLibrary:照片库中的一个图片
UIImagePickerControllerSourceTypeSavedPhotosAlbum:相册中的一个图片
UIImagePickerControllerSourceTypeCamera:照相机拍摄的新图片

应该总是确保,在启动UIImagePickerController之前,源是可用得,尽管这对于照相机来说最重要。用下面的方法确定源的存在:

if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]){}

一旦验证了源的存在,就可以告诉图像选择器,结合sourceType属性一起使用这个源。例如要使用照相机,可以这样做:

imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;

3.3 保存到相册——希望将新照片保存到相册,或希望程序创建的图形存放到相册,使用UIImageWriteToSavedPhotosAlbum函数。该函数具有4个变:第一个列出图像,其他三个引用一个可选的异步通知函数,以在完成保存时调用:

UIImageWriteToSavedPhotosAlbum(yourImage,nil,nil,nil);

可以使用该函数将UIView地CALayer保存到相册。例如,相册允许你保存那些你以前直接写到CALayer的绘图命令。这也同样依赖于图形上下文:

UIGraphicsBeginImageContext(myView.bounds.size);
[myView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *collageImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageWriteToSavedPhotosAlbum(collageImage,nil,nil,nil);

第十二章:媒体:音频和录音

1.播放iPod库中的音频
1.1 从iPod媒体库中检索音频条目:

MPMediaPickerController *picker = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeMusic];
[picker setDelegate:self];
picker.allowsPickingMultipleItem = YES;//允许选择多个条目 [self presentModalViewController:picker animated:YES];

初始化MPMediaPickerController时,可以选择显示的媒体类型:

常量 概述
MPMediaTypeMusic 媒体类型是音乐,选取器仅限于使用音乐库
MPMediaTypePodcast 媒体类型是播客,选取器仅限于播客库
MPMediaTypeAudioBook 媒体类型是音频书,选取器仅限于音频书
MPMediaTypeAnyAudio 媒体类型是未指定的音频类型,选取器不局限于任何特定的音频类型
MPMediaTypeAny 类型于MPMediaTypeAnyAudio,选取器可以从库中玄奇任意音频条目

创建MPMediaPickerController后,还要创建委托来响应两个方法:mediaPicker:didPickMediaItems:和mediaPikcerDidCancel:

1.2 获取MPMediaItem的信息——从iPod媒体库中选择一个MPMediaItem时,会得到所有相关联的元信息。要访问其中的信息,利用valueForProperty:方法即可。下表给出一些常用的键:

常量 概述
MPMediaItemPropertyMediaType 对应于上面那个表中介绍的媒体类型之一
MPMediaItemPropertyAlbumTitle 媒体条目所属专辑的标题
MPMediaItemPropertyArtist 媒体条目的艺术家
MPMediaItemPropertyPlaybackDuration 以秒表示的当前媒体条目的长度,NSInteger类型
MPMediaItemPropertyArtwork 该媒体条目的作品图像

1.3 使用MPMusicPlayerController播放媒体条目:初始化MPMusicPlayerController时,可选择两个方法与iPod交互。第一种方法限制iPod只为应用程序回放,一旦应用程序退出,则iPod停止播放。排一种方法允许调用全局iPod应用程序,退出应用程序不会使iPod停止播放。下面的代码是初始MPMusicPlayerController:

MPMusicPlayerController *player = [MPMusicPlayerController applicationMusicPlayer];//applicationMusicPlayer:基于应用程序进行媒体播放。如要使用主iPod应用程序的媒体播放功能,使用iPodMusicPlayer方法

初始化MPMusicPlayerController之后,就需要告诉它需要播放的媒体条目:

- (void)mediaPicker:(MPMusicPlayerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection
{
[player setQueueWithItemCollection:mediaItemCollection];
}

常用iPod控制属性

常量 概述
currentPlaybackTime 当前播放时间,以秒为单位
nowPlayingItem 对队列中当前播放条目的引用
playbackState 媒体播放器的当前播放状态:停止、播放、暂停、中断、向前搜索、向后搜索
repeatMode 播放器的重复模式:默认、无、一个、全部
shuffleMode 播放器的随机播放模式:默认、关闭、歌曲和专辑
volume 播放器的音量:0.0-1.0

MPMusicPlayerController的播放控制方法

方法 概述
play 启动或恢复iPod对当前媒体条目的播放
pause 暂停当前正在播放的播放器
stop 停止当前正在播放的播放器
beginSeekingForward 比正常更快的速率向前播放
beginSeekingBackward 比正常更快的速率向后播放
endSeeking 停止搜索并恢复播放
skipToBeginning 从头开始播放当前媒体条目
skipToNextItem 开始播放队列中的下一个媒体条目。如果当前条目是最后一个则结束播放
skipToPreviousItem 开始播放队列中得上一个媒体条目。如果当前条目是第一个则结束播放

2.录制音频——使用AV Foundation框架
2.1 初始化音频录音器AVAudioRecorder——应该避免使用默认的构造函数init。这是为了减少复杂性,因为该类需要配置的内容非常多。
应该使用initWithURL:setting:error构造函数。第一个参数是录音内容的存储位置,它用NSURL表示,实际它指向磁盘上某个位置的本地路径。
第二个参数是NSDictionary,包含录音的设置。
设置录音器时可能需要考虑的一些设置:

设置键 概述
AVSampleReteKey 采样率,单位是赫兹,以NSNumber浮点值的形式表示
AVFormatIDKey 格式标识符,常用的值是kAudioFormatAppleLossless
AVNumberOfChannelsKey 通道号,以NSNumber整数值的形式表示,可将它设为1
AVEncoderAudioQualityKey 该键引用正在播放的音频的质量

下面的代码使用一些基本的设置来构建AVAudioRecorder对象:

NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/recording.caf"];

NSDictionary *recordSettings = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithFloat:44100.0], AVSampleRateKey,[NSNumber numberWithInt:kAudioFormatAppleLossless], AVFormatIDKey,[NSNumber numberWithInt:], AVNumberOfChannelsKey, [NSNumber numberWithInt:AVAudioQualityMax], AVEncoderAudioQualityKey, nil];

AVAudioRecorder *soundRecorder = [[AVAudioRecorder alloc] initWithURL:[NSURL fileURLWithPath:filePath] settings:recordSettings error:nil];

2.2 控制音频录音器的方法:

方法 概述
- (BOOL)prepareToRecord 在磁盘上指定的URL路径中创建录音文件。该方法还将让系统做好录音准备
- (BOOL)record 启动或恢复录音。该方法将隐式调用prepareToRecord方法
- (BOOL)recordForDuration:(NSTimeInterval)duration 启动录音器,并录制指定时间的内容
- (void)pause 暂停录音。要恢复录音,需再次调用record方法
- (void)stop 停止录音并关闭音频文件
- (BOOL)deleteRecording 删除当前的录音内容。使用该方法前必须先停止录音

下面的代码展示了如何使用一个简单地toggleRecord方法:

- (IBAction)toggleRecord:(id)sender
{
if (recording){
[soundRecorder stop];
}else{
[soundRecorder record];
}
recording = !recording;
}

2.3 响应AVAudioRecorder事件——为了响应AVAudioRecorder的委托操作,你的类必须实现AVAudioRecorderDelegate。下表是AVAudioRecorderDelegate可被实现的方法:

方法 概述
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag 录音器完成录音时调用该方法。该方法将接受一个录音器的引用和一个布尔值(录制成功则为YES)
- (void)audioRecorderEncodeErrorDidOccur:(AVAudioRecorder *)recorder error:(NSError*)error 录音期间发生错误时调用此方法
- (void)audioRecorderBeginInterruption:(AVAudioRecorder *)recorder 中断录音时调用此方法。最常见的中断是用户在录音时接电话
- (void)audioRecorderEndInterruption:(AVAudioRecorder *)recorder 中断结束调用该方法。

3.手动播放声音——AVAudioPlayer类

3.1 初始化AVAudioPlayer:第一种方法是initWithData:error,该方法使用一个NSData对象来初始化播放器,该NSData对象中包含要播放的音频数据。播放现有音频数据时,该方法可避免从磁盘加载音频数据。第二种方法是initWithContentsOfURL:error,该方法从磁盘路径中加载音频文件:

NSString * filePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/recording.caf"];
AVAudioPlayer *newPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWitPath:filePath] error:nil];
newPlayer.delegate = self;

3.2 构建新的AVAudioPlayer对象之后,需要设置其委托来响应它的操作。AVAudioPlayer的委托类似AVAudioRecorder委托,它们响应的事件完全相同,只需要把recorder换成player即可

3.3 控制AVAudioPlayer

属性 概述
playing 布尔值,当播放器正在播放声音文件时该值为YES,只读
volume 声音的相对音量。0.0-1.0
numberOfLoops 循环播放声音的次数。默认为0,表示只播放一次。将该值设为正数表示声音循环播放多次,设为负数,声音将进行无限次循环,直到调用stop方法
numberOfChannels 声音中得音频通道数,只读
duration 声音文件的总长度,以秒为单位,只读
currentTime 声音的当前播放事件。
URL 一个NSURL对象,表示文件的位置

data

meteringEnabled

一个NSData对象,包含声音文件的音频数据

一个布尔值,它确定当前是否启用了测定功能。默认为NO。设为YES时表示可以访问某些与声音相关的计量数据

下面代码展示了如何播放你在上面中创建的录音:

NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/recording.caf"];

AVAudioPlayer *newPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:filePath] error:nil];
newPlayer.delegate = self;
[newPlayer play];

4.使iPhone和iPad产生振动
确保AudioToolbox.framework添加到你的项目中,并导入AudioToolbox/AudioService.h。下面的代码即可使iPhone和iPad产生振动效果:

AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);

第十三章:图形:Quartz、Core Animation 和 OpenGL

1.Quartz 2D简介——Quartz的绘图功能取决于3个核心概念:上下文、路径和状态
A.上下文(contenxt):用于描述将图形写入哪里,可能是打印机、PDF文件、窗口或位图图像。由CGContextRef定义,通常你可以写入UIView或位图中
B.图层(layer):是Quartz绘图的工作区域,可以相互重叠
C.路径:是你在Quartz中绘制的内容。起初,它们是线段和弧的集合,随后通过描边或填充(可能的话,还可以使用裁剪)的方式绘制到屏幕上
D.状态:用于保存变换值、裁剪路径、填充和描边设置、alpha值、其他混合模式、文本特征等

2.Quartz 上下文——图形上下文是在栈中创建的:当你创建时,它将被压入栈的顶部;当你使用完后,它就从栈的顶部弹出。下面是创建上下文的方法:

函数 参数 概述
UIGraphicsGetCurrentContext 返回当前上下文,通常指当前UIKit对象的上下文,但也可能是手工创建的上下文
UIGraphicsBeginImageContext CGSize 创建位图上下文
UIGraphicsEndImageContext 从栈中弹出某个位图的上下文
UIGraphicsGetImageFromCurrentImageContext 返回一个UIImage *位图,仅适用于位图上下文
CGPDFContextCreate CGDataConsumerRef、CGRect、CGDictionaryRef 创建PDF上下文

2.1 在UIView上绘制图形

- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextBeginPath(ctx);
CGContextAddArc(ctx,,,,,*M_PI,);
CGContextFillPath(ctx);
}

2.2 在位图上绘制图形——主要原因是需要在程序中多次使用图形——或者是同时使用

- (void)viewDidLoad{
[super viewDidLoad];
UIGraphicsBeginImageContext(CGSizeMake(,));
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextBeginPath(ctx);
CGContextAddArc(ctx,,,,,*M_PI,);
CGContextSetRGBFillColor(ctx,,,,);
CGContextFillPath(ctx);
UIImage *redBall = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *redBallView = [[UIImageView alloc] initWithImage:redBall];
redBallView.center = CGPointMake(,);
[self.view addSubView:redBallView];
}

3.绘制路径——下表是各种基于矢量图的简单绘图函数

函数 参数 概述
CGContextBeginPath context 创建新路径

CGContextAddArc

context、x、y、radius、startangle、endangle、clockwise 使用弧度中的角度创建弧。为了创建切线弧、贝塞尔曲线以及二次贝塞尔曲线,还提供更复杂的函数:CGContextAddArcToPoint、CGContextAddCurveToPoint及CGContextAddQuadCurveToPoint
CGContextAddEllipseInRect context、CGRect 在矩形内部创建内切圆 
CGContextAddLineToPoint context、x、y  在当前点和指定终点间创建线段。更复杂的CGContextAddLines函数用于添加一些线段 
CGContextAddRect  context,CGRect  创建矩形 
CGContextMoveToPoint  context、x、y  移动到某个点而不绘制图形(在绘制路径时,笔不能离开纸面,通过此方法可以让笔脱离纸面) 

3.1 结束路径——主要有3种选择:描边路径、填充路径或将路径转变成剪切路径

函数 参数 概述
CGContextClosePath context 从路径的终点向起点绘制线段,然后关闭它。这是一个可选的结束命令,通常用在描边路径的时候
CGContextFillPath context 自动关闭路径,并通过填充绘制路径。
CGContextStrokePath context 通过描边绘制路径
CGContextClip context 将当前路径转变成裁剪路径

3.2 创建可重用路径——需要使用CGPath,而非CGContext

CGPath函数及其对应的CGContext函数
CGPath函数 CGContext函数
CGPathCreateMutable CGContextBeginPath
CGPathAddArc CGContextAddArc
CGPathAddEllipseInRect CGContextAddEllipseInRect
CGPathAddLineToPoint CGContextAddLineToPoint
CGPathAddRect CGContextAddRect
CGPathMoveToPoint CGContextMoveToPoint
CGPathCloseSubPath CGContextClosePath

3.3 绘制矩形

函数 参数 概述
CGContextClearRect context、CGRect 擦除矩形。当你转变擦除窗口绘制新图形时,此函数极其有用
CGContextFillRect context、CGRect 绘制填充的矩形。更复杂的CGContextFillRects可以填充多个矩形
CGContextStrokeRect context、CGRect 绘制已描边的矩形
CGContextStrokeRectWithWidth context、CGRect、width 按照指定的宽度绘制描边的矩形

4. 设置图形状态——图形状态是指Quartz如何被绘制。状态是通过栈进行维护的
两个重要的函数是:CGContextSaveGState、CGContextRestoreGState

4.1 设置颜色——RGB(红-绿-蓝)、RGBA(红-绿-蓝-alpha)、CMYK(青-品红-黄-黑)和CGColor(底层Core Graphics颜色模式)

函数 参数 概述
CGContextSetRGBFillColor context、red、green、blue、alpha 将填充颜色设置为RGBA值
CGContextSetRGBStrokeColor context、red、green、blue、alpha 将描边颜色设置为RGBA值
CGContextSetFillColorWithColor context、CGColor 将填充颜色设置为CGColor值
CGContextSetStrokeColorWithColor context、CGColor 将描边颜色设置为CGColor值

4.2 变换
A. CTM变换——最简单的变换方式,使用某个函数来修改当前变换矩阵(Current Transformation Matrix,CTM)

函数 参数 概述
CGContextRotateCTM context、radian、rotation 旋转网格
CGContextScaleCTM context、x-scale、y-scale 缩放网格
CGContextTranslateCTM context、x-change、y-change 移动原点

B.仿射(affine)变换——正如你可以创建可重用路径一样,你也可以创建可重用变换矩阵(使用仿射变换函数),然后通过CGContextConcatCTM函数在上下文中应用该变换矩阵

函数 参数 概述
CGAffineMakeRotation radian(弧度)、rotation(旋转) 通过旋转构造矩阵数组
CGAffineMakeScale x-scale、y-scale 通过缩放构造数组
CGAffineMakeTranslation x-change、y-change 通过移动构造数组
CGAffineTransformRotate array、radian、rotation 旋转数组
CGAffineTransformScale array、x-scale、y-scale 缩放数组
CGAffineTransformTranslate array、x-change、y-change 移动数组
CGContextConcatCTM context、array 运用变换

以下代码先通过可重用的仿射矩阵进行旋转,然后再进行移动:

CGAffineTransform myAffine = CGAffineTransformMakeRotation(.*M_PI);
CGAffineTransformTranslate(myAffine, , );
CGContextConcatCTM(ctx, myAffine);

还可以通过CGAffineTransformMake函数手工创建矩阵:
CGAffineTransform flip = CGAffineTransformMake(1,0,0,-1,0,0);

4.3 设置裁剪路径——创建一个路径,然后裁剪该路径,而不是填充或描边
下面的代码使随后绘制的图像仅显示屏幕中间的大圆形内部的一部分

CGContextBeginPath(ctx);
CGContextAddArc(ctx,,,,,*M_PI,);
CGContextClip(ctx);

4.4 其他设置

函数 参数 概述
CGContextSetAlpha context、alpha 设置alpha透明度
CGContextSetBlendMode contex、CGBlendMode 将混合设置成约30个值中得一个,指定对象间如何重叠放置
CGContextSetFlatness context、flatness 定义曲线的精度
CGContextSetLineCap context、CGLineCap 定义如何绘制线段的末端
CGContextSetLineDash context、phase、lengths array、count 定义如何沿轮廓绘制虚线
CGContextSetLineJoin context、CGLineJoin 定义线段如何相交
CGContextSetLineWidth context、width 描述轮廓的宽度
CGContextSetShadow context、CGsize、blur 在所有图形后设置阴影
CGContextSetShadowWithColor context、CGSize、blur、color 在所有图形后设置颜色阴影

5. Quartz的高级绘图功能
5.1 绘制渐变——两种方式:使用CGShadingRef对象或CGGradientRef对象。前者需要你定义CGFunctionRef对象,以便精确计算渐变颜色如何显示。

函数 参数 概述
CGColorSpaceCreateWithName colorspace constant 通过名称创建颜色空间
CGGradientCreateWithColors color space、color array、location array 使用预先生成的颜色创建渐变
CGGradientCreateWithColorComponents color space、color components array、location array,color count 通过颜色数组部件创建渐变
CGContextDrawLinearGradient context、gradient、start CGPoint、endCGPoint、options 绘制线性渐变
CGContextDrawRadialGradient context、gradient、start center、start radius、end center、end radius、options 绘制径向渐变
CGColorSpaceRelease color space 释放颜色空间对象
CGGradientRelease gradient 释放渐变对象

下面代码展示了绘制三色线性渐变:

//步骤1:定义颜色空间
CGColorSpaceRef myColorSpace = CGColorSpaceCreateDeviceRGB();
//步骤2:创建渐变部件
CGFloat components[] = {,,,,,,,,,,,};
CGFloat locations[] = {,.,};
CGGradientRef myGradient = CGGradientCreateWithColorComponents(myColorSpace,components,locations,(size_t));
//步骤3:绘制渐变
CGContextDrawLinearGradient(ctx, myGradient, CGPointMake(,),CGPointMake(,), NULL);
//步骤4:释放内存空间
CGColorSpaceRelease(myColorSpace);
CGGradientRelease(myGradient);

5.2 绘制图像

函数 参数 概述
CGContextDrawImage context、CGRect、image 按矩形大小绘制图像
CGContextDrawTiledImage conext、CGRect、image 按矩形大小绘制图像,同时填充当前的裁剪区域

在位图上绘制图像——通常需要将图像转换成位图,并在屏幕上显示它之前对它进行修改,这样便可以多次使用图像

UIImage *origPic = [UIImage imageNamed:@"1.jpg"];
UIGraphicsBeginImageContext(origPic.size);
CGContextRef ctx = UIGraphicsGetCurrentContext();
//变换
CGContextRotateCTM(ctx, M_PI);
CGContextTranslateCTM(ctx, -origPic.size.width, -origPic.size.height); CGContextDrawImage(ctx, CGRectMake(,,origPic.size.width,origPic.size.height), [origPic CGImage]); CGContextSetLineWidth(ctx,);
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx,,);
CGContextAddLineToPoint(ctx,origPic.size.width,origPic.size.height);
CGContextMoveToPoint(ctx,,origPic.size.height);
CGContextAddLineToPoint(ctx,origPic.size.width,);
CGContextSetStrokeColorWithColor(ctx, [[UIColor redColor] CGColor];
CGContextStrokePath(ctx); UIImage *newPic = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

5.3 绘制文字

函数 参数 概述
CGContextSelectFont context、font name、size、text encoding 为图形状态设置字体
CGContextSetTextDrawingMode context、CGTextDrawingMode 定义如何在图形状态中绘制文本
CGContextSetTextMatrix context、affine transform 在图形状态中放置仅用于文本的变换矩阵
CGContextSetSetPosition context、x、y 在图形状态中设置绘图的位置
CGContextShowText context、string、length 在当前位置绘制文本
CGContextShowTextAtPoint context、x、y、string、length 在指定位置绘制文本
CGContextSelectFont (ctx, "Helvetica",,kCGEncodingMacRoman);
CGContextSetTextDrawingMode(ctx,kCGTextFill);
CGAffineTransform flip = CGAffineTransformMake(,,,-,,);
CGContextSetTextMatrix(ctx, flip);
CGContextShowTextAtPoint(ctx, , , "A Quartz Example",);

6. Core Animation简介
6.1 Core Animation管理着所有的滚动、旋转、缩小、放大以及其他构成用户界面的动画效果。许多UIKit类通常将animated:参数作为方法的一部分,允许你选择是否使用动画
另外,Core Animation还与Quartz紧密结合在一起。每个UIView都被链接到一个称作CALayer(它是Core Animation图层)的图形层中。你可以使用它描述图形和图像也可以通过它管理复杂的变换
我们使用Core Animation创建动画时,实质上是更改CALayer属性,然后让这些属性流畅的变化。这些属性包括:anchorPoint、backgroundColor、opacity、position、transform等等。
可以使用Core Animation对象的位置、颜色、透明度以及CGAffine变换来制作动画

图层——图层是动画发生的位置。你总是讲一个CALayer关联到每个UIView上,并通过layer属性访问它。你可以使用[CALayer layer]类消息调用附加的图层,然后使用addSublayer:方法将它们添加到现有的CALayer中。通过这种方式添加图层会产生倒置的坐标系统。可以为每个图层单独设置动画,这样,就能在大量的动画属性间建立复杂的交互。在iPhone中,通过创建多个UIKit对象(很可能是多个UIImageView对象)并为每个对象绘制动画,可以轻松创建更加复杂的动画

隐式动画——简单的动画类型。你告诉UIView需要创建该类型的动画,然后改变属性
显式动画——使用CABasicAnimation创建的动画,通过CABasicAnimation可以更明确地定义属性如何改变动画
关键帧动画——更复杂的显式类型动画。这里你不但可以定义动画的起点和终点,还可以定义某些帧之间的动画

绘制简单的隐式动画

[UIView beginAnimation:nil context:NULL];
CGAffineTransform moveTransform = CGAffineTransformMakeTranslation(,);
[plane.layer setAffineTransform:moveTransform];
plane.layer.opacity = ;
[UIView commitAnimations];

绘制简单的显式动画:使用显式动画时,不必定义CALayer地变换,也不必执行它们,而是通过CABasicAnimation类逐个定义动画。其中的每个动画都含有各自的duration、repeatCount值以及许多其他属性。然后,使用addAnimation:forKey:方法分别将每个动画应用到图层中:

CABasicAnimation *opAnim = [CABasicAnimation animationWithKeyPath:@"opacity"];
opAnim.duration = 3.0;
opAnim.fromValue = [NSNumber numberWithFloat:.];
opAnim.toValue = [NSNumber numberWithFloat:1.0];
opAnim.cumulative = YES;
cumulative.repeatCount = ;
[plane.layer addAnimation:opAnim forKey:@"animateOpacity"];
CGAffineTransform moveTransform = CGAffineTransformMakeTranslation(,);
CABasicAnimation *moveAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
moveAnim.duration = 6.0;
opAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeAffineTransform(moveTransform)];
[plane.layer addAnimation:moveAnim forKey:@"animateTransform"];

更好的解决方案是:为opacity创建3个关键帧,0秒时为0.25,3秒时为1.00,6秒时为1.75——需要创建关键帧动画

第十四章:Web:Web视图和互联网协议

1. CFHost类——允许程序请求关于互联网主机的信息。比如名称、地址以及是否可达

2. 使用URL
2.1 创建NSURL

方法 概述
fileURLWithPath: 从本地文件路径创建URL
URLWithString: 从字符串创建URL,等同于initWithSting:
URLWithString:relativeToURL: 向基本URL添加一个字符串,等同于initWithSting:relatvieToURL:

2.2 构建NSURLRequest——包含两个部分:一个URL和一个用于处理缓存响应的特定策略

方法 概述
requestWithURL: 从URL创建一个默认的请求,等同于initWithURL:
requestWithURL:cachePolicy:timeoutInterval: 创建一个具有特定缓存选项的请求,等同于initWithURL:cachePolicy:timeoutInterval:

默认情况下,所构建的NSURLRequest具有一个依赖于协议的缓存策略,超时值为60秒(对大多数编程来说足够了),如果需要得到关于加载的更特定的消息,可以调用表中第二个方法

2.3 手动操作HTML数据——要手动读取网页内容,需要访问NSURLRequest的属性。下表是一些重要的属性:

属性 概述
allHTTPHeaderFields 返回标题的NSDictionary
HTTPBody 返回带有正文的NSData
valueforHTTPHeaderField: 返回带有标题的NSString

3. 使用UIWebView

方法 概述
loadHTMLString:baseURL: 从URL和字符串加载页面
loadRequest: 从NSURLRequest加载页面

UIWebView常用方法和属性

方法/属性 类型 概述
detectsPhoneNumbers 属性 布尔值,确定电话号码是否变成链接
goBack 方法 后退一页,要首先检查canGoBack属性
goForward 方法 前进一页,要首先检查canGoForward属性
reload 方法 重新加载当前页面
scalesPageToFit 属性 布尔值,确定页面是否缩放到视区里,以及是否允许用户缩放

UIWebView的委托:

方法 概述
webView:shouldStartLoadWithRequest:navigationType: 在内容加载之前调用
webViewDidStartLoad: 在内容开始加载之后调用
webViewDidFinishLoad: 在内容完成加载之后调用
webView:didFailLoadWithError: 在内容加载失败之后调用

4. 解析XML——使用NSXMLParser类

方法 概述
initWithContentsOfFile: 通过NSURL创建解析器
initWithData: 通过NSData创建解析器
setDelegate: 为解析器定义委托
parse 运行解析器

最重要的5个NSXMLParser委托方法:

方法 概述
parser:didStartElement:namespaceURI:qualifiedName:attributes: 报告元素的开始以及元素的属性
parser:foundCharacters: 报告元素的所有或部分内容
parser:didEndElement:namespaceURI:qualifiedName: 报告元素的结束标记
parserDidEndDocument: 报告解析的结束
parser:parseErrorOccurred: 报告不可恢复的解析错误

5. 提交给Web——当你提交(POST)给web时,需要了解NSMutableURLRequest(允许你构建分段请求)和NSURLConnection(允许你提取Web中得信息)
5.1 简单的POST示例

NSURL *myURL = [NSURL URLWithString:@"http://www.example.com"];
NSMutableURLRequest *myRequest = [NSMutableURLRequest requestWithURL:myURL];
[myRequest setValue:@"text/xml" forHTTPHeaderField:@"Content-type"];
[myRequest setHTTPMethod:@"POST"];
NSData *myData = [@"someText" dataUsingEncoding:NSASCIIStringEncoding];
[myRequest setHTTPBody:myData]; NSURLResponse *response;
NSError *error;
NSData *myReturn = [NSURLConnection sendSynchronousRequest:myRequest returningResponse:&response error:&error];

5.1 提交表单——向网页发送表单数据的流程和其他提交数据是相同的,并且读取结果的方式也一样。唯一需要技巧的地方是封装表单数据以便于进行使用
使用表单数据最简单的方法是使用NSDictionary或NSMutableDictionary键值来创建它,因为这与HTML表单的底层结构相匹配。当你准备好处理数据时,你将词典传递给其转换成NSData的方法,NSData可以作为NSMutableURLRequest主体被发送
下面的代码显示了如何将NSString词典转换成NSData

- (NSData *)createFormData:(NSDictionary *)myDictionary withBoundary:(NSString *)myBounds
{
NSMutableData *myReturn = [[NSMutableData alloc] initWithCapacity:];
NSArray *formKeys = [dict allKeys];
for (int i = ; i < [formKeys count]; i++){
[myReturn appendData:[[NSString stringWithFormat:@"--%@\n", myBounds] dataUsingEncoding:NSASCIIStringEncoding]];
[myReturn appendData:[[NSString stringWithFormat:@"Content-Disposition:form-data; name=\"%@\"\n\n%@\n", [formKeys objectAtIndex:i],[myDictionary valueForKey:[formKeys objectAtIndex:i]]] dataUsingEncoding:NSASCIIStringEncoding]];
}
[myReturn appendData:[[NSString stringWithFormat:@"--%@--\n", myBounds] dataUsingEncoding:NSASCIIStringEncoding]]; return myReturn;
}

外部代码:

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:myURL];
NSString *content = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", myBounds];
[request setValue:content forHTTPHeaderField:@"Content-type"];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:myReturn];

第十五章:使用Game Kit进行P2P连接

1. 概述——Game Kit是一个框架,为完成各种联网任务提供了许多类,这些类构建于Bonjour协议之上,为P2P交互完成了大量繁重的任务。此框架最初主要用于游戏开发
在Game Kit中,实现P2P通信的对象称作会话(session)。每个对等点创建一个会话,并使用该会话来发现其他会话。另外会话还负责在对等点之间发送和接收数据。
三种不同的会话模式可确定会话如何与对等点进行交互。第一种会话模式是服务器模式。当会话在此模式下工作时,它们向网络上得每个节点通告其服务。第二种会话模式是客户端模式。在该模式下,会话搜索正在通告服务的服务器。最后一种是对等模式,对等模式中,会话同时充当客户端和服务端的角色

任何使用Game Kit联网的应用程序都必须完成三件事:首先是设置对等点选取器的委托方法。这些方法可响应各种事件,包括查找对等点和选择需要连接的对等点。
其次是设置会话的委托,这些方法允许你创建自定义会话并跟踪当前连接的所有会话
最后,必须考虑send和receive方法,它们负责通过网络传输所有数据

2. 使用对等点选取器创建P2P应用程序——对等点选取器提供了一个非常简单的方法来通过蓝牙或无线方式连接两个设备。对等点选取器视图易于使用,但只允许两个对等点之间进行连接。需要同时连接两个以上对等点时,必须创建自己的自定义对等点选取器
2.1 使用苹果内置对等点选取器——首先要导入Game Kit框架

//用户按下连接按钮后查找对等点
- (void)viewDidLoad{
[super viewDidLoad];
chatPicker = [[GKPeerPickerController alloc] init];
[chatPicker setDelegate:self];
[chatPicker setConnectionTypeMask:GKPeerPickerConnectionTypeNearby];//GKPeerPickerConnectionTypeOnline:在线搜索对等点
peers = [[NSMutableArray alloc] init];
} - (IBAction)connect{
[chatPicker show];
}

GKPeerPickerControllerDelegate委托方法:

委托方法 概述
peerPickerController:didSelectConnectionType: 用户选择连接类型时调用该可选方法。
peerPickerController:sessionForConnetionType: 控制器请求会话时调用该可选方法。实现该方法,可以让你更好的控制会话,包括可定制会话的显示名称和会话ID
peerPickerController:didConnectPeer:toSession: 这是个期望实现的可选方法。对等点连接时将调用该方法。此时,你应该解除对等点选取器,并取得会话所有权
peerPickerControllerDidCancel: 这是一个期望实现的可选方法。用户取消请求时将调用该方法。此时,你告诉用户会话已取消
- (void) peerPickerController:(GKPeerPickerController *)picker didSelectConnectionType:(GKPeerPickerConnectionType)type{
}
- (void) peerPickerControllerDidCancel:(GKPeerPickerController *)picker {
NSLog(@"the connection was cancelled")
}
- (void) peerPickerController:(GKPeerPickerController *)picker didConnectPeer:(NSString*)peerID toSession:(GKSession*)session
{
self.chatSession = session;
self.chatSession.delegate = self;
[self.chatSession setDataReceiveHandler:self withContext:nil];
[chatPicker dismiss];//隐藏选取器
}
- (GKSession *)peerPickerController:(GKPeerPickerController *)picker sessionForConnectionType:(GKPeerPickerConnectionType)type
{
GKSession *session = [[GKSession alloc] initWithSessionID:@"chatSession" displayName:@"peer" sessionMode:GKSessionModePeer];
[session autorelease];
return session;
}

GKSessionDelegate方法

委托方法 概述
session:peer:didChangeState: 只要对等点状态改变了,就会调用该方法
session:didReceiveConnectionRequestFromPeer: 对等点需要连接时调用该方法。如果使用内置的对等点选取器,建议忽略该方法
session:connectionWithPeerFailed:withError: 发送连接错误时调用该方法。如果使用内置对等点选取器,可忽略。因为对等点选取器会自动处理错误并显示UIAlertView
session:didFailWithError: 发送无非恢复的错误时调用此方法。在次方法中,你应该断开所有对等点的连接并通知用户

实现GKSessionDelegate方法

- (void)session:(GKSession *)session peer:(NSString *)peerID didChangeState:(GKPeerConnectionState)state
{
switch(state)
{
case GKPeerStateConnected:
[peers addObject:peerID];
[chatTextArea setText:[NSString stringWithFormat:@"%@has joined the chat.\n",[session displayName]]];
break;
case GKPeerStateDisconnected:
[peers removeObject:peerID]; NSString *text = [chatTextArea text];
[chatTextArea setText:[NSString stringWithFormat:@"%@\n%@ has left the chat.\n", text, [session displayName]]];
break;
}
}

在对等点之间发送和接收数据

- (IBAction)send:(id)sender
{
NSData *data = [[sendTextField text] dataUsingEncoding:NSACIIStringEncoding]; //GKSendDataReliable告诉会话继续发送数据,直到对等点接收到数据为止。该功能通过TCP/IP协议实现。GKSendDataUnreliable使用UDP协议,它将通知会话,你不介意数据是否被接收或按什么顺序接收,速度快
[chatSession sendData:data toPeers:peers withDataMode:GKSendDataReliable error:nil]; NSString *text = [chatTextArea text];
[chatTextArea setText:[NSString stringWithFormat:@"%@Me:%@\n",text,[sendTextField text]]];
[sendTextField setText:@""];
[sendTextField resignFirstResponder];
}
- (void)receiveData:(NSData*)data fromPeer:(NSString*)peer inSession:(GKSession*)session context:(void*)context
{
NSString *string = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSString *text = [chatTextArea text];
[chatTextArea setText:[NSString stringWithFormat:@"%@%@:%@\n",text,[session displayName], string]];
}

示例:创建一个多人乒乓球游戏

第十六章:推送通知服务

1.推送服务依赖于苹果公司提供的一个特点服务:Apple Push Notification Service(APNS)。APNS是一个Web服务,每个提供程序必须与APNS通信才能向客户端设备发送通知
提供程序(即app的服务端)——>APNS——>设备——>APP

提供程序上的某些情况发生变化时,必须生成数据并发送给APNS,该数据被称为有效负载(payload),格式为JSON:

{
"aps":{"alert":"This is a message", "badge":, "sound":"default"}
}
数据 概述
alert 用户没有运行可接收通知的应用程序时将显示该文本信息
badge 在接收通知的应用程序图标上所显示的数值。如果选择忽略该值,计数值将设为0并且不显示
sound 通知到达时在应用程序中将播放的声音文件。该文件必须为特定得格式

下面示例是一个包含标准数据和自定义词典的通知:

{
"aps":{"alert":"This is a message", "badge":, "sound":"default"},
"foo":{"bar":,"baz":"Custom text"}
}

2.在应用程序中使用APNS
2.1 设置应用程序证书
必须拥有有效的苹果开发人员账号来测试推送通知。两个所需的项目:第一个即将生成的项目是特殊的配置文件。当你在设备上部署应用程序时,将使用该配置文件来签署应用程序。第二个项目是客户端SSL证书。推送提供程序将使用它与苹果公司的推送服务器建立连接。
首先,登陆苹果公司开发人员账号,然后打开程序门户网站,进入App IDs选项卡。添加应用程序包标识符。该标识符的格式是反向的域,如:com.rightsprite.pushtest。添加应用程序后,还必须配置应用程序来接收推送通知。你可以将应用程序配置成开发证书,也可以配置成生产证书。好的做法是测试阶段使用开发证书,准备提交应用程序商店时再换成生产证书。

2.2 建立供应配置文件
既然已经创建了签名证书,你还需要创建供应配置文件来安装应用程序。同样,你不希望使用通用开发人员证书。你必须生成一个新证书,该证书特定于应用程序的完整工具包ID。
进入Provisioning选项卡,在Development选项卡上单击New Profile按钮
创建该配置文件后,还需要下载并安装该配置文件

2.3 处理推送通知的代码
处理推送通知的代码非常简单,只需要实现3个方法即可:

//在此方法中添加一行代码,告诉应用程序注册推送通知
- (void)applicationDidFinishLaunching:(UIApplication *)app
{
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
}
//还必须实现一些委托方法来响应注册调用生成的事件:
//第一个方法是通过成功注册推送通知来激活的。注册接收推送通知时,你的应用程序将与苹果公司进行通信,并接收一个唯一的设备标记。设备将使用该标记实现所有的推送通知
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)devToken
{
[self sendProviderDeviceToken:devToken;//此方法是自己创建的,功能是把设备标记发送给推送提供程序。通过web服务交互实现该操作
}
//用于错误处理。当注册推送通知出现错误时,将激活该方法。如果调用该方法,说明你的签名证书也许无效,或者设备没有互联网连接
- (void)application:(UIApplication*)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err{
NSLog(@"Error in registration. Error: %@", err);
}

既然已经注册了应用程序来接收推送通知,下一步是处理这些进入的通知。当用户在通知警告上单击view按钮时,苹果公司为你提供了很多控制通知的方法
第一种方法是在applicationDidFinishLaunching:方法中实现的代码处理所进入的传送通知。如果使用通知打开应用程序而不传送任何附加数据,应该采用该方案:

- (void)applicationDidFinishLaunching:(UIApplication *)application{
application.applicationIconBadgeNumber = ;//最重要的是这里,将标记数重置为0.否则,标记数总是保持推送通知发送时的数值
[self getUpdatedDataFromServer];
}

如果通知包含自定义词典,你必须使用application:didFinishLaunchingWithOptions:方法。其中options是一个NSDictionary
如果用户目前正在运行应用程序,你必须实现application:didReceiveRemoteNotification:方法。该方法将被自动调用,并传递一个包含所有标准信息的词典,标准信息包括标记数、消息、声音和任何发送给设备的自定义词典
实现了上面的所有方法后,你的应用程序应该准备接收推送通知了。准备工作的最后一步是格式化所有音频文件,设备将播放这些音频文件来响应推送通知

2.4 准备音频文件
注意:必须将该音频文件存储在程序的主包(main bundle)中。主包目录是存储应用程序文件的根文件夹。你的音频文件必须是AIFF、WAV或CAF格式的,并且时间限制在30秒内。
为了将音频文件转换成其中一种文件格式,你必须在Mac上使用 afconvert 命令。
首先打开终端并进入需要转换的音频文件的目录,然后键入 afconvert 命令,后面跟参数:-f caff -d LEI16 {INPUT} {OUTPUT}
下面的代码展示了使用该命令将track2.mp3文件转换成track.caf文件:
/usr/bin/afconvert -f caff -d LEI16 track2.mp3 track2.caf

3. 使用PHP场景一个推送通知提供程序
3.1 创建SSL证书——在开始编码之前,必须生成SSL证书来与苹果公司进行通信。你将在终端上使用自己的推送证书和在上面内容中生成的私钥来创建该证书。下面是生成该证书的步骤:
A.打开你的键链
B.单击My Certificates
C.单击Apple Development Push Services证书旁边的箭头展开它
D.按住ctrl键并单击证书,然后选择Export导出,保存为apns_cert.p12
E.对私钥执行相同的操作并命名为apns_key.p12。导出时,会提示你设置密码,使用简单的密码
F.合并键和证书,并将合并的文件转换成.pem格式。这样php程序可以加载他。为此,打开终端,进入证书和键的位置,并输入如下命令:
openssl pkcs12 -clcerts -nokeys -out apns_cert.pem -in apns_cert.p12
openssl pkcs12 -nocerts -out apns_key.pem -in apns_key.p12
G.要删除apns_key.p12文件中得密码,请输入命令:
openssl rsa -in apns_key.pem -out apns_key_unenc.pem
H.最后需要使用cat命令来合并这两个文件。在终端中输入:
cat apns_cert.pem apns_key_unenc.pem > apns.pem

3.2 实现PHP推送通知程序——向苹果公司的服务器发送推送通知的代码很简单。你需要建立SSL连接,并发送JSON形式的数据

<?php
$message = "Text to send";
$badgeCount = 1;
$sound = "default";
$payload['aps'] = array('alert' => $message, 'badge' = >$badgeCount, 'sound' => $default); $payload = json_encode($payload); $deviceToken = 'c902XXX556dc5581f2750XXX97ea8c496XXXa613fafXXX50cb356749XXX07cf1'; //建立连接
$apnsHost = 'gateway.sandbox.push.apple.com';//调试模式
$apnsPort = 2195;
$apnsCert = 'apns-dev.pem'; //将即将被发送地包转换成苹果公司所需的二进制形式
$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'lcoal_cert', $apnsCert); $apns = stream_socket_client('ssl://' . $apnsHost . ':' . $apnsPort, $error, $errorString, 2, STREAM_CLIENT_CONNECT, $streamContext);
//将数据转换成苹果公司所需格式
$apnsMessage = chr(0) . chr(0) . chr(32) . pack('H*', str_replace(' ', '', $deviceToken)) . chr(0) . chr(strlen($payload)) . $payload; $fwrite($apns, $apnsMessage);
fclose($apns);
?>

对生产环境来说,该提供程序代码不完善,必须在多个方面进行改进。首先,设备需要提供某种方式向服务器发送自身的标记。因此,你需要某种端点把设备标记看做是POST或GET请求,并将它存储在数据库中。其次,你需要在这些标记之间进行循环,向所有目标设备发送推送通知。最后你还需要参加提供程序界面,以便发生某种操作时提供程序能够发送通知。

第十七章:Map Kit 框架

1.可显示地图的视图称为MKMapView

任务 类型 概述
region 属性 MKCoordinateRegion类型的属性。它由两个副本表示纬度和经度的浮点值和一个表示跨度的浮点值组成。这个跨度值代表缩放级别。跨度越大,缩放级别越低
setRegion:animated: 方法 使用选项设置地图上的区域以便使用一种动画。如果animated为YES则地图通过动画形式移到新位置
centerCoordinate 属性 设置要按那种坐标居中显示地图,而且不改变当前缩放级别
setCenterCoordinate:animated: 方法 设置要按那种坐标居中显示地图,而且不改变当前缩放级别——通过动画形式移到新位置
regionThatFits: 方法 调整区域宽高比例,以便适合地图边框

以下代码展示了如何创建MKCoordinateRegion并移到地图以将其显示在屏幕上。用户按下名为apple的按钮时,调用这里创建的这个方法

- (IBAction)apple:(id)sender{
CLLocationCoordinate2D coords;
coords.latitude = 37.33188;
coords.longitude = -122.029497;
MKCoordinateSpan span = MKCoordinateSpanMake(0.002389, 0.005681); MKCoordinateRegion region = MKCoordinateRegionMake(coords, span);
[theMap setRegion:region animated:YES];
}

MKMapView用户交互属性

任务 概述
mapType 要显示的地图类型。该变量的可选项有MKMapTypeStandard、MKMapTypeSatellite和MKMapHybrid。更新该属性会自动导致地图改变其视图
zoomEnabled 决定用户是否能放大。如果为NO,则地图使用特定跨度
scrollEnabled 设为YES时,用户能再地图上滚动查看。设为NO,则地图固定在一个位置

2. 翻译地理编码——根据一个给的位置的经纬度来得到它的相关地址、区域或其他信息。用于翻译地理编码的类称为MKReverseGeoCoder:

任务 类型 概述
delegate 属性 指定翻译地图编码的委托。该委托接收来自地理编码器的消息,包括错误和位置信息
coordinate 属性 翻译地理编码将获取该坐标的数据
start 方法 调用翻译地理编码,当该方法完成时,它针对该类调用两个委托方法中得一个
querying 属性 布尔变量,指定翻译地理编码器目前是否正在获取数据
cancel 方法 取消对数据的请求

3. 标识地图
3.1 添加基本地图标识:

//按下该按钮时启动地理编码器,使用翻译地理编码器在地图中央放置一个大头针
- (IBAction)dropPin:(id)sender{
MKReverseGeoCoder *geoCoder = [[MKReverseGeoCoder alloc] initWithCoordinate:theMap.centerCoordinate];
[getCoder setDelegate:self];
[geoCoder start];
}
//向地图中添加地标
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark{
[mapView addAnimation:placemark];
}
//处理错误
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:@"Unable to get address" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alert show];
[alert release];
}
//告诉地图如何显示标识
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation{
MKPinAnnotationView *aView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"location"];
aView.animatesDrop = YES;
return aView;
}

3.2 添加自定义地图标注——添加自定义标注与创建自定义UITableViewCell没有太大区别。创建一个继承自其父类MKAnnotaionView的视图并在viewForAnnotation方法中返回该视图。
MKAnnotationView属性:

属性 概述
enabled 确定是否启用标注的布尔值。如果值为NO,则标注不会响应各种事件,如触摸
image 表示标注的图形。MKPinAnnotationView是一个视图,它的图形设为大头针图像。这可能是自定义时最有用的属性
highlighted 不应手动设置该属性。它由地图视图设置并且通过调用isHighlighted来访问。当用户触摸标注时,将其设为YES
centerOffset  告诉标注居中时显示在哪里。默认情况下,它居中显示在地图的这个点上。 当需要改变标注与地图点相关的位置时,该属性非常有用
calloutOffset  决定当用户轻击标注时引出框的偏移量。默认情况下该值是(0,0),并置于标注框的顶端中心
canShowCallout 确定当用户轻击标注时是否显示引出框 
rightCalloutAccessoryView  在引出框右端的视图。通常用于显示附加信息或到应用中其他位置的链接。这通常是一个具有UIButtonTypeDisclosure类型的UIButton 
 leftCalloutAccessoryView 在引出框左端的视图。通常用于显示附加信息或到应用中其他位置的链接 

要创建自己的MKAnnotationView,还需要创建一个实现MKAnnotation协议的类。MKAnnotation对象将用来填充MKAnnotationView的信息

下面的示例,在MKMapView上绘制google和苹果公司所在地点的视图。将地图添加到视图之后,必须创建MKAnnotation对象

//MyAnnotation.h

#import <MapKit/MapKit.h>

typedef enum AnnotationType{
Apple,
Google
} AnnotationType; //该类实现了MKAnnotation协议。要满足MKAnnotation的要求,必须具有一个名为coordinate的只读属性。这是该标注在地图上的坐标位置,并将用于决定在哪显示它
@interface MyAnnotation : NSObject<MKAnnotation> {
CLLocationCoordinate2D coordinate;
NSString *title;
NSString *subtitle;
AnnotationType annotationType;
} @property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
@property (nonatomic, retain) NSString *title;
@property (nonatomic, retain) NSString *subtitle;
@property (nonatomic) AnnotationType annotationType; - (id)initWithCoords:(CLLocationCoordinate2D)coords andType:(AnnotationType)type;
@end
//MKAnnotation.m

@implementation MyAnnotation

@synthesize title;
@synthesize subtitle;
@synthesize annotationType;//注意不要合成coordinate属性。因为它是只读的,只能在初始化对象时进行设置 - (id)initWithCoords:(CLLocationCoordinate2D)coords andType:(AnnotationType)type {
if (self = [super init]){
coordinate = coords;
self.annotationType = type;
}
return self;
}

创建了自定义的MKAnnotation对象之后,就需要创建使用该对象的视图。这个视图必须是MKAnnotationView的子类并且没有任何必需的方法或属性

//MyMKAnnotationView.h

#import <MapKit/MapKit.h>
#import "MyAnnotation.h" @interface MyAnnotationView : MKAnnotationView{}
- (id) initWithAnnotation:(id)annotation reuseIdentifier:(NSString *)reuseIdentifier;
@end
#import "MyAnnotationView.h"
@implementation MyAnnotationView - (id)initWithAnnotation:(id)annotation reuseIdentifier:(NSString *)reuseIdentifier{
MyAnnotation *myAnnotation = (MyAnnotation *)annotation; if ([myAnnotation annotationType] == Apple) {
self = [super initWithAnnotation:myAnnotation reuseIdentifier:resuseIdentifier];
self.image = [UIImage imageNamed:@"sign-apple.png";
} else if ([myAnnotation annotationType == Google]) {
self = [super initWithAnnotation:myAnnotation reuseIdentifier:resuseIdentifier;
self.image = [UIImage imageNamed:@"sign-google.png";
}
return self;
}

最后,将它们集成到你的地图中

- (void)viewDidLoad {
[super viewDidLoad];
//将地图居中显示在兴趣点上
CLLocationCoordinate2D coords;
coords.latitude = 37.331689;
coords.longitude = -122.030731;
MKCoordinateSpan span = MKCoordinateSpanMake(0.011209,0.22597); MKCoordinateRegion region = MKCoordinateRegionMake(coords, span);
[theMap setRegion:region animated:YES]; //得到标注的坐标
CLLocationCoordinate2D appleCoords;
appleCoords.latitude = 37.331689;
appleCoords.longitude = -122.030731;
MyAnnotation *apple = [[MyAnnotation alloc] initWithCoords:appleCoords andType:Apple];
[apple setTitle:@"Apple Inc."];
[apple setSubTitle:@"Cupertino, CA"];
[theMap addAnnotation:apple];
[apple release]; CLLocationCoordinate2D googleCoords;
googleCoords.latitude = 37.421793;
googleCoords.longitude = -122.084434;
MyAnnotation *google = [[MyAnnotation alloc] initWithCoords:googleCoords andType:Apple];
[google setTitle:@"Apple Inc."];
[google setSubTitle:@"Cupertino, CA"];
[theMap addAnnotation:google];
[google release];
}

既然已经向地图添加了标注,那么最后要做的事情是实现MKMapViewDelegate的viewForAnnotation

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
MyAnnotation *aView = [[MyAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"location"];
[aView setEnabled:YES];
[aView setCanShowCallout:YES];
return aView;
}

第十八章:使用Store Kit实现应用内购买

Store Kit API允许你在应用程序内销售各种物品。这些物品统称为产品。

1.建立沙盒测试环境——在应用程序中构建商店之前,必须在iTunes Connect上搭建生产和测试环境。这样允许你模拟支付过程,而不必每次都收取iTunes账户费用
设置产品的重要前提条件就是首先必须将想要测试的应用程序添加到iTunes Connect(http://itunesconnect.apple.com)。为此,你必须执行所有步骤提交你的应用并获得苹果商店的批准。注意,不应上传二进制文件,因此在准备好之前,你的应用程序不会意外地提交到苹果公司
添加你的包ID时,使用反向域很重要,如果你的网站是www.foobar.com,并且你的应用程序称为baz,那么包标识符应该是com.foobar.baz

1.1 创建iTunes测试用户
为了在沙盒环境中进行测试,首先必须在iTunes Connect中建立一个测试iTunes账户:进入iTunes Connect的Manager User 部分,确保选择In App Purchase Test User。创建的这个用户只用于测试沙盒商店。对于每个要测试的国家,都必须要创建一个新测试用户

1.2 添加产品
你可以在商店中销售3种类型的产品
A. 消费品——每次用户需要时所购买的产品,比如游戏中的增强道具
B. 非消费品——用户只购买一次并保留的产品,比如可下载的歌曲或图像
C. 订阅——消费品或非消费品。这样就能使用户按照你想要的频率更新他们的订阅。为此,苹果公司并不提供订阅是否有效的记录,你必须在自己的服务器上提供这种记录

假设要创建非消费品。当用户购买了一个背景之后,他们将永远使用它而无需任何其他的费用。按照以下这些步骤操作:
A.在iTunes Connect主页上选择Manager Your In App Purchases
B.在下一个屏幕上,单击Create New按钮,你会看到所有已添加到iTunes Connect的应用程序
C.选择将使用应用内购买的应用程序,这样就进入下一个页面,在这里添加第一个产品
D.你需要填写3个部分的内容。第一部分包含定价信息和产品类型。第一个字段是Reference Name,这是一个纯文本名,标识了iTunes Connect中的产品。这只是供你参考的,用户永远看不到。Product ID与应用程序标识符类似,对于每个产品而言必须是唯一的。对于Type值,选择Non-Consumable,对于你目前创建的应用程序类型而言,这是最适合的。
E.创建产品标题和说明
F.添加屏幕截图。只有当你准备将应用内购买提交到苹果公司进行审批时,这才是必不可少的

2. 创建一个简单的商店界面
添加Store Kit框架到项目中

//RootViewController.h
#import <StoreKit/StoreKit.h> @interface RootViewController : UITableViewController<SKProductsRequestDelegate, SKPaymentTransactionObserver> {
NSMutableArray *products;//产品数组
NSMutableArray * transactionHistory;
}
@property (nonatomic, retain) NSMutableArray * transactionHistory;
- (void) requestProductData;
- (void) completeTransaction:(SKPaymentTransaction *)transaction;
- (void)restoreTransaction:(SKPaymentTransaction *)transaction;
- (void)failedTransaction:(SKPaymentTransaction *)transaction;
- (void)recordTransaction:(SKPaymentTransaction *)transaction;
- (void)provideContent:(NSString *)productIdentifier;
//RootViewController.m
#import "RootViewController.h"
#improt "WallpaperViewController.h" @implementation RootViewController @synthesize transactionHistory; - (void)viewDidLoad{
[super viewDidLoad]; //将支付观察器设为本类。这意味着该类要实现SKPaymentTransactionObserver协议以及委托方法。针对该协议必须实现的方法有completeTransaction、restoreTransaction和failedTransaction
[[SKPaymentQueue defaultQueue] addTransactionObserver:self;
//初始化产品数组
products = [[NSMutableArray alloc] init];
//构建交易历史文件的路径
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:];
NSString *path = [documentsDirectory stringByAppendingPathComponent:@"history.plist"];
//初始化交易历史
self.transactionHistory = [NSMutableArray arrayWithContentsOfFile:path];
if (!transactionHistory) {
NSMutableArray *_transactionHistory = [[NSMutableArray alloc] init];
self.transactionHistory = _transactionHistory;
}
[self requestProductData];
}
//首先创建产品请求。这些都是前面注册的产品ID
//创建请求之后,就开始从iTunes中获取产品信息。
//获取信息之后,使用该数据调用委托方法didReceiveResponse。对这些产品循环操作并将其添加到全局产品数组。这样就可以使用它们的名称和说明来填充UITableView
- (void)requestProductData{
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifier:[NSSet setWithObjects:@"com.rightsprite.wallpaper.01","com.rightsprite.wallpaper.02","com.rightsprite.wallpaper.03",nil]];
request.delegate = self;
[request start];
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSArray * myProducts = response.products;
for (SKProduct *product in myProducts) {
[products addObject:product];
}
[request autorelease];
[self.tableView reloadData];
}
//UITableView的委托方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [products count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReuseableCellWithIdentifier:CellIdentifier];
if (cell = nil){
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
SKProduct *product = [products objectAtIndex:indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:@"$%.2f %@", product.price.doubleValue, product.localizeTitle];
cell.detailTextLabel.text = product.localizedDescription;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SKProduct *product = [products objectAtIndex:indexPath.row];
WallpaperViewController *wpViewController = [[WallpaperViewController alloc] initWithNibName:@"WallpaperViewController" bundle:[NSBundle mainBundle]]; wpViewController.product = product;
[self.navigationController pushViewController:wpViewController animated:YES];
[wpViewController release];
}

创建购买页面

//WallpaperViewController.h
#import <UIKit/UIKit.h>
#import <StoreKit/StoreKit.h> @interface WallpaperViewController : UIViewController {
IBOutlet UIImageView *imageView;
SKProduct *product;
}
@property (nonatomic, retain)IBOutlet UIImageView *imageView;
@property (nonatomic, retain)SKProduct *product; - (IBAction)buttonClicked:(id)sender;
@end //WallpaperViewController.m
#import "WallpaperViewController.h"
@implementation WallpaperViewController @synthesize product,imageView; - (void)viewDidLoad{
[super viewDidLoad];
imageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"%@.jpeg", self.product.productIdentifier]];
} - (IBAction)buttonClicked:(id)sender{
SKPayment *payment = [SKPayment paymentWithProductIdentifier:self.product.productIdentifier];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}

Store Kit的委托方法

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray*)transactions {
for (SKPaymentTransaction *transaction in transactions) {
swith (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction];
default:
break;
}
}
}

用户尝试购买某个物品时,Store Kit就调用该方法,并且基于购买状态将其作为一个控制器。购买行为有3种可能:
SKPaymentTransactionStatePurchased:交易成功时产生。这时应当将内容交付给用户并记录交易历史
SKPaymentTransactionStateFailed:交易失败:资金不足或网络问题。出现这种情况,需要通知用户没有完成购买操作
SKPaymentTransactionStateRestored:当用户已经购买了一项物品时产生。如果出现这种交易状态,就应当将交易内容交付给用户,就像这是一个新交易

//完成交易并交付内容
- (void)completeTransaction:(SKPaymentTransaction*)transaction
{
[self.navigationController popViewControllerAnimated:YES];
[self recordTransaction:transaction];
[self provideContent:transaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
//恢复交易.完成交易和恢复交易通常是相似的。
- (void)restoreTransaction:(SKPaymentTransaction*)transaction
{
[self completeTransaction:transaction];
}
//通知用户交易失败
- (void) failedTransaction:(SKPaymentTransaction*)transaction
{
if (transaction.error.code != SKErrorPaymentCancelled)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error in purchase" message:transaction.error.description delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alert show];
[alert release];
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

记录交易历史,保存在一个plist文件中

- (void)recordTransaction:(SKPaymentTransaction *)transaction {
if ([self.transactionHistory containsObject:transaction.payment.productIdentifier])
return; //将历史记录保存到磁盘
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:];
NSString *path = [documentsDirectory stringByAppendingPathComponent:@"history.plist"];
[self.transactionHistory addObject:transaction.payment.productIdentifier];
[self.transactionHistory writeToFile:path atomically:YES];
}
//将图像保存到用户的相册
- (void)provideContent:(NSString *)productIdentifier {
UIImageWriteToSavedPhotosAlbum([UIImage imageNamed:[NSString stringWithFormat:@"%@.jpeg", productIdentifier]], self, @selector(image:didFinishSavingWithError:contextInfo:), nil];
}
//通知用户
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Purchase Complete" message:@"The wallpaper has been saved to your camera roll." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alert show];
[alert release];
}

第十九章:iPhone SDK增强功能

1.自定义键盘附件——如iPhone中的Message应用就可以看到键盘附件的示例
1.1 扩展UITextField——为了能够访问UITextField的inputAccessoryView,必须创建UITextField的一个子类并实现inputAccessoryView的获取方法。默认情况,UITextField的inputAccessoryView属性设为nil
首先在XCode中创建一个新类。我们使用NSObject的子类,因为它几乎没有什么内容。对于本例,调用类MyCustomAccessoryTextField.m:

- (UIView *)inputAccessoryView {
CGRect accessFrame = CGRectMake(,,,);
UIToolbar *toolbar = [[[UIToolbar alloc] initWithFrame:accessFrame autorelease]; UIBarButtonItem *buttonSmile = [[[UIBarButtonItem alloc] initWithTitle:@":)" style:UIBarButtonItemStyleBordered target:self action:@selector(sendSmile:)] autorelease]; UIBarButtonItem *buttonSad = [[[UIBarButtonItem alloc] initWithTitle:@":(" style:UIBarButtonItemStyleBordered target:self action:@selector(sendSmile:)] autorelease]; [toolbar setItems:[NSArray arrayWithObjects:buttonSmile, buttonSad, nil]];
return toolbar;
}

注意:不必重写inputAccessoryView属性,只需要实现该属性的获取方法即可,这样它返回一个UIView

1.2 自定义键盘——创建自定义键盘没有太多区别。你必须重写UITextField的inputView方法,而不是重写inputAccessoryView:

- (UIView *)inputView {
CGRect accessFrame = CGRectMake(,,,);
UIToolbar *toolbar = [[[UIToolbar alloc] initWithFrame:accessFrame] autorelease]; UIBarButtonItem *buttonSmile.........//跟上面的代码一样
}

附录A : iPhone OS 类参考
A.1 UIKit框架类

父类 概述
UIActionSheet UIView 包含可选项的弹出式窗口,和UIAlertView类似
UIActivityIndicatorView UIView 不确定的进度显示
UIAlertView UIView 包含可选项的弹出式窗口,和UIActionSheet类似
UIApplication UIResponder 应用信息和控制的主要来源
UIButton UIControl 下压按钮
UIColor NSOjbect 颜色输出类
UIControl UIView 抽象类,是许多用户控件的父类
UIDatePicker UIControl 滚动式日期选择设备
UIDevice NSObject 保存设备自身信息的类
UIEvent NSObject 触摸容器,事件模型的一部分
UIFont NSObject 字体输出类
UIImage NSObject 非显示图像保持器
UIImagePickerController UINavigationController 图像选择模式控制器
UIImageView UIView 持有一个或多个UIImage对象的图像显示
UILabel UIView 小的、不可编辑的文本显示
UINavigationcontroller UIViewController 层次结构控制器,通常与UITableViewController链接来产生层次式菜单
UIPageControl UIControl 使用点号在页面间导航的工具栏
UIPickerView UIView 基于滚轮的选择机制
UIProgressView UIView 确定的进度显示
UIResponder NSObject 抽象类,用于定义所有可以接收和响应事件的类
UIScreen NSObject 包含设备整个屏幕的类
UIScrollView UIView 查看多个页面内容的父类
UISearchBar UIView 专门用于搜索的文本输入机制
UISegmentedControl UIControl 用于控制在多个选项中选择其中之一
UISlider UIControl 设置分离值控制
UISwitch UIControl 选择二进制值控制
UITabBarController UIViewController 控制器,用于在多个屏幕间移动
UITableViewController UIViewController 控制器,用于显示目录
UITextField UIControl 用于控制输入短小文本
UITextView UIScrollView 显示任意大小的文本
UITouch NSObject 在设备屏幕上单次触摸
UIView UIResponder 存在于多数UIKit对象核心中得抽象类
UIViewController UIResponder 简单视图控制器
UIWebView UIView 类似于Safari的Web浏览器
UIWindow UIView 视图层次结构的根

A.2 Foundation框架类

父类 概述
NSArray NSObject 数组
NSAutoreleasePool NSObject 内存管理类
NSBundle NSObject 指向项目文件系统主页的指针
NSCharacterSet NSObject 管理字符的方法
NSCountedSet NSMutableSet 元素的无序集合
NSData NSObject 字节缓冲区包装器
NSDictonary NSObject 关联数组
NSError NSObject 封装的错误信息
NSFileHandler NSObject 控制文件的方法
NSFileManager NSObject 文件系统工作的管理者
NSIndexPath NSObject 节点路径
NSLog NSObject 调试对象,为系统日志记录格式化字符串
NSMutableArray NSArray 可变数组
NSMutableCharacterSet NSCharacterSet 可变的字符集合
NSMutableData NSData 可变的数据
NSMutableDictionary NSDictionary 可变词典
NSMutableSet NSSet 可变集合
NSMutableString NSString 可变字符串
NSMutableURLRequest NSURLRequest 可变得URL请求
NSNotificationCenter NSObject 通知管理器
NSNumber NSValue 封装多种类型数字的方式
NSObject N/A Cocoa Touch的根类
NSString NSObject 各种字符串存储和操作的类
NSURL NSObject 简单URL对象
NSURLRequest NSObject 带有缓冲策略的URL
NSValue NSObject 数据的简单容器
NSXMLParser NSObject XML解析器

iPhone与iPad开发实战读书笔记的更多相关文章

  1. Spring 3.x 实践 第一个例子(Spring 3.x 企业应用开发实战读书笔记第二章)

    前言:工作之后一直在搞android,现在需要更多和后台的人员交涉,技术栈不一样,难免鸡同鸭讲,所以稍稍学习下. 这个例子取自于<Spring 3.x 企业应用开发实战>一书中的第二章,I ...

  2. Spring AOP (Spring 3.x 企业应用开发实战读书笔记第六章)

    从面相对象编程到面相切面编程,是一种代码组织方式的进化. 每一代的代码组织方式,其实是为了解决当时面对的问题.比如写编译器和写操作系统的时候的年代当然要pop,比如写界面的时候当然要oop,因为界面这 ...

  3. Spring实战读书笔记

    Spring实战读书笔记 Spring-core Spring之旅 - DI 和 AOP 概念 spring 的Bean容器 spring 的 核心模块 Spring的核心策略 POJO 最小侵入式编 ...

  4. 第一章 Andorid系统移植与驱动开发概述 - 读书笔记

    Android驱动月考1 第一章 Andorid系统移植与驱动开发概述 - 读书笔记 1.Android系统的架构: (1)Linux内核,Android是基于Linux内核的操作系统,并且开源,所以 ...

  5. 机器学习实战 - 读书笔记(13) - 利用PCA来简化数据

    前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习心得,这次是第13章 - 利用PCA来简化数据. 这里介绍,机器学习中的降维技术,可简化样品数据. ...

  6. 机器学习实战 - 读书笔记(12) - 使用FP-growth算法来高效发现频繁项集

    前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习心得,这次是第12章 - 使用FP-growth算法来高效发现频繁项集. 基本概念 FP-growt ...

  7. 机器学习实战 - 读书笔记(11) - 使用Apriori算法进行关联分析

    前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习心得,这次是第11章 - 使用Apriori算法进行关联分析. 基本概念 关联分析(associat ...

  8. 机器学习实战 - 读书笔记(07) - 利用AdaBoost元算法提高分类性能

    前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习笔记,这次是第7章 - 利用AdaBoost元算法提高分类性能. 核心思想 在使用某个特定的算法是, ...

  9. 《PHP与MySQL WEB开发》读书笔记

    <PHP与MySQL WEB开发>读书笔记 作者:[美]Luke Welling PHP输出的HereDoc语法: echo <<<theEnd line 1 line ...

随机推荐

  1. Codeforces Round #315 (Div. 2)【贪心/重排去掉大于n的元素和替换重复的元素】

    B. Inventory time limit per test 1 second memory limit per test 256 megabytes input standard input o ...

  2. 服务器出现大量close_wait,我们来说说到底是怎么回事?(以tomcat为例)

    一.问题描述 最近一直忙得很,好久没写博客.前两天,微信收到个好友申请,说是想问问close_wait的事情. 找他问了些详细信息,大概了解到,他们后端服务是tomcat 7, jdk 7,cento ...

  3. MapReduce1 工作机制

    本文转自:Hadoop MapReduce 工作机制 工作流程 作业配置 作业提交 作业初始化 作业分配 作业执行 进度和状态更新 作业完成 错误处理 作业调度 shule(mapreduce核心)和 ...

  4. 洛谷——P1119 灾后重建

    P1119 灾后重建 题目背景 B地区在地震过后,所有村庄都造成了一定的损毁,而这场地震却没对公路造成什么影响.但是在村庄重建好之前,所有与未重建完成的村庄的公路均无法通车.换句话说,只有连接着两个重 ...

  5. poj 1185 炮兵阵地 [经典状态压缩DP]

    题意:略. 思路:由于每个大炮射程为2,所以如果对每一行状态压缩的话,能对它造成影响的就是上面的两行. 这里用dp[row][state1][state2]表示第row行状态为state2,第row- ...

  6. z pre-pass 相关问题的讨论

    z pre-pass 是指在渲染流程中,第一个pass先画一张深度buffer出来,得到需要绘制的最前面这层深度,用这个在接下来的pass中做深度剔出,这样在第二个pass中会省略很多绘制. 这项技术 ...

  7. SEO误区之——静态化页面

    你随便去找一个做SEO的人或者一个公司,他百分之百会让你把网页弄成纯静态页面,然后告诉你这样对搜索引擎是如何如何地好,那么我告诉你,这个做 SEO的,肯定不专业. 网页静态化这个东西,纯属以讹传讹的事 ...

  8. Hibernate get load的区别

    这两个函数都是用来从数据库中加载对象,其区别说起来主要有以下两点: 1.如果数据库中不存在该对象,那么load是抛出一个ObjectNotFound的异常,而get是返回一个空指针 2.加载机制不同 ...

  9. zookeeper客户端 和服务器连接时版本问题

    在使用kafka 和zookeeper 实现实时分析程序时,由于zookeeper部署版本和分析程序导入jar包的版本不一致,导致了当实时分析程序从远程服务器连接kafka集群的zookeeper时报 ...

  10. 路飞学城Python爬虫课第一章笔记

    前言 原创文章,转载引用务必注明链接.水平有限,如有疏漏,欢迎指正. 之前看阮一峰的博客文章,介绍到路飞学城爬虫课程限免,看了眼内容还不错,就兴冲冲报了名,99块钱满足以下条件会返还并送书送视频. 缴 ...