<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>iPhoneGeek 爱疯极客 &#187; 教程</title>
	<atom:link href="http://www.iphone-geek.cn/tag/%e6%95%99%e7%a8%8b/feed" rel="self" type="application/rss+xml" />
	<link>http://www.iphone-geek.cn</link>
	<description>iPhone 新闻，编程，技巧与提示，代码，教程</description>
	<lastBuildDate>Thu, 08 Dec 2011 01:18:52 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.5</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>创建iOS 5 News Stand应用程序之一 &#8211; 外观</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e5%88%9b%e5%bb%baios-5-news-stand%e5%ba%94%e7%94%a8%e7%a8%8b%e5%ba%8f%e4%b9%8b%e4%b8%80-%e5%a4%96%e8%a7%82</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e5%88%9b%e5%bb%baios-5-news-stand%e5%ba%94%e7%94%a8%e7%a8%8b%e5%ba%8f%e4%b9%8b%e4%b8%80-%e5%a4%96%e8%a7%82#comments</comments>
		<pubDate>Fri, 21 Oct 2011 02:52:06 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[用户界面]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[Newsstand]]></category>
		<category><![CDATA[提示与技巧]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=1189</guid>
		<description><![CDATA[iOS 5提供了一个新的framework &#8211; Newsstand framework，它允许把应用程序运行于News Stand中。实际上，News Stand相当于一个特制的文件夹专门放置报纸，杂志类应用程序。由于时间有限，这里我分几个部分介绍这一技术。

首先介绍的就是怎样把一个应用程序改变成一个News Stand程序，这实际上有两步工作，一是让程序运行于News Stand，二是改变程序的图标。

1. 让程序运行于News Stand内

可以在Info.plist中添加

12&#60;key&#62;UINewsstandApp&#60;/key&#62;
&#160; &#60;true/&#62;

或者直接在Xcode中更改Info.plist（如图）：



就这么简单，运行！你的程序就运行在News Stand中了。


不过，出现在News Stand中的是一个非常丑陋的白色方框。这一定不是你需要的效果。那么，我们需要第二步。

2. 为你的News Stand程序添加图标

应用程序仍需定义标准图标，这些图标用于settings，search，Push等，（而且你的程序有可能运行于iOS 5以前的版本）。Newsstand 图标可以反应应用的内容，可以动态更新，另外还可以加一些修饰，使其看上去就像真正的杂志或者报纸。

你可以直接修改Info.plist

12345678910111213141516171819&#60;key&#62;CFBundleIcons&#60;/key&#62;
&#60;dict&#62;
&#160; &#160; &#60;key&#62;CFBundlePrimaryIcon&#60;/key&#62;
&#160; &#160; &#60;dict&#62;
&#160; &#160; &#160; &#160; &#60;key&#62;CFBundleIconFiles&#60;/key&#62;
&#160; &#160; &#160; &#160; &#60;array&#62;
&#160; &#160; &#160; &#160; &#160; &#160; &#60;string&#62;Icon.png&#60;/string&#62;
&#160; &#160; &#160; &#160; &#160; &#160; &#60;string&#62;Icon@2x.png&#60;/string&#62;
&#160; &#160; &#160; &#160; &#60;/array&#62;
&#160; &#160; &#60;/dict&#62;
&#160; &#160; &#60;key&#62;UINewsstandIcon&#60;/key&#62;
&#160; &#160; &#60;dict&#62;
&#160; &#160; &#160; &#160; [...]]]></description>
			<content:encoded><![CDATA[<p>iOS 5提供了一个新的framework &#8211; Newsstand framework，它允许把应用程序运行于News Stand中。实际上，News Stand相当于一个特制的文件夹专门放置报纸，杂志类应用程序。由于时间有限，这里我分几个部分介绍这一技术。<br />
<br class="spacer_" /><br />
首先介绍的就是怎样把一个应用程序改变成一个News Stand程序，这实际上有两步工作，一是让程序运行于News Stand，二是改变程序的图标。<br />
<span id="more-1189"></span></p>
<h2>1. 让程序运行于News Stand内</h2>
<p><br class="spacer_" /><br />
可以在Info.plist中添加</p>
<blockquote>
<div class="codecolorer-container xml mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br /></div></td><td><div class="xml codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;key<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>UINewsstandApp<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/key<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
&nbsp; <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;true</span><span style="color: #000000; font-weight: bold;">/&gt;</span></span></div></td></tr></tbody></table></div>
</blockquote>
<p>或者直接在Xcode中更改Info.plist（如图）：<br />
<br class="spacer_" /><br />
<a href="http://www.iphone-geek.cn/wp-content/uploads/2011/10/s1.jpg"><img class="aligncenter size-full wp-image-1190" title="s1" src="http://www.iphone-geek.cn/wp-content/uploads/2011/10/s1.jpg" alt="s1" width="680" height="203" /></a><br />
<br class="spacer_" /><br />
就这么简单，运行！你的程序就运行在News Stand中了。</p>
<p style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2011/10/s2.jpg"><img class="aligncenter size-full wp-image-1191" title="s2" src="http://www.iphone-geek.cn/wp-content/uploads/2011/10/s2.jpg" alt="s2" width="651" height="414" /></a></p>
<p><br class="spacer_" /><br />
不过，出现在News Stand中的是一个非常丑陋的白色方框。这一定不是你需要的效果。那么，我们需要第二步。<br />
<br class="spacer_" /></p>
<h2>2. 为你的News Stand程序添加图标</h2>
<p><br class="spacer_" /><br />
应用程序仍需定义标准图标，这些图标用于settings，search，Push等，（而且你的程序有可能运行于iOS 5以前的版本）。Newsstand 图标可以反应应用的内容，可以动态更新，另外还可以加一些修饰，使其看上去就像真正的杂志或者报纸。<br />
<br class="spacer_" /><br />
你可以直接修改Info.plist</p>
<blockquote>
<div class="codecolorer-container xml mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br />19<br /></div></td><td><div class="xml codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;key<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>CFBundleIcons<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/key<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;dict<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;key<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>CFBundlePrimaryIcon<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/key<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;dict<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;key<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>CFBundleIconFiles<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/key<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;array<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;string<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>Icon.png<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/string<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;string<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>Icon@2x.png<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/string<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/array<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/dict<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;key<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>UINewsstandIcon<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/key<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;dict<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;key<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>CFBundleIconFiles<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/key<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;array<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;string<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>NewsstandIcon1.png<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/string<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;string<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>NewsstandIcon1@2x.png<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/string<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/array<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
&nbsp; &nbsp; <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/dict<span style="color: #000000; font-weight: bold;">&gt;</span></span></span><br />
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/dict<span style="color: #000000; font-weight: bold;">&gt;</span></span></span></div></td></tr></tbody></table></div>
</blockquote>
<p><br class="spacer_" /><br />
或者直接使用Xcode编辑：<br />
<br class="spacer_" /><br />
<a href="http://www.iphone-geek.cn/wp-content/uploads/2011/10/s3.jpg"><img class="aligncenter size-full wp-image-1192" title="s3" src="http://www.iphone-geek.cn/wp-content/uploads/2011/10/s3.jpg" alt="s3" width="517" height="240" /></a><br />
<br class="spacer_" /><br />
关于BindingType和BindingEdge应该很容易理解，我就不知赘述了。另外Newsstand中的图标不一定是正方形，只是不知有没有尺寸上的限制。<br />
<br class="spacer_" /><br />
<a href="http://www.iphone-geek.cn/wp-content/uploads/2011/10/s4.jpg"><img class="aligncenter size-full wp-image-1193" title="s4" src="http://www.iphone-geek.cn/wp-content/uploads/2011/10/s4.jpg" alt="s4" width="398" height="264" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e5%88%9b%e5%bb%baios-5-news-stand%e5%ba%94%e7%94%a8%e7%a8%8b%e5%ba%8f%e4%b9%8b%e4%b8%80-%e5%a4%96%e8%a7%82/feed</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>CALayer简单教程</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/calayer%e7%ae%80%e5%8d%95%e6%95%99%e7%a8%8b</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/calayer%e7%ae%80%e5%8d%95%e6%95%99%e7%a8%8b#comments</comments>
		<pubDate>Tue, 18 Jan 2011 07:08:34 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[图形图像]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=1104</guid>
		<description><![CDATA[前一阵子读到一篇介绍CALayer（这里简单地称其为层）的教程，比较简单易懂，适合初学者，我在这里就不完全翻译了，只是把要点说明一下。

首先要说的是CALayers 是屏幕上的一个具有可见内容的矩形区域，每个UIView都有一个根CALayer，其所有的绘制（视觉效果）都是在这个layer上进行的。（译者注：为验证这点，我写下了如下代码：
12345678910UILabel* lable = &#91;&#91;UILabel alloc&#93;initWithFrame:CGRectMake&#40;0, 0, 100, 30&#41;&#93;;
lable.text = @&#34;test&#34;;
&#91;self.view addSubview: lable&#93;;
lable.backgroundColor = &#91;UIColor clearColor&#93;;
&#91;lable release&#93;;

// 设定CALayer
self.view.layer.backgroundColor =&#91;UIColor orangeColor&#93;.CGColor;
self.view.layer.cornerRadius =20.0;
self.view.layer.frame = CGRectInset&#40;self.view.layer.frame, 20, 20&#41;;
请注意，我创建的UILable始终随着UIView的根CALayer的缩放而改变位置。）
其次，CALayer的可以影响其外观的特性有：

层的大小尺寸
背景色
内容（比如图像或是使用Core Graphics绘制的内容）
是否使用圆角
是否使用阴影
等等

需要说明的是CALayer的大部分属性都可以用来实现动画效果。
另外，你可以直接使用CALayer，也可以使用其子类，如CAGradientLayer，CATextLayer， CAShapeLayer等等。
示例
首先在Xcode中创建一个View-based App，CALayer是属于QuartzCore framework的，所以需要引入QuartzCore framework，另外在程序中包括QuartzCore.h。
第一个例子是创建一个带圆角的层，在你的ViewController中的ViewDidLoad中加入下面代码：
1234567// Import QuartzCore.h at the top of the file
#import &#60;QuartzCore/QuartzCore.h&#62;

// Uncomment viewDidLoad and add the following lines
self.view.layer.backgroundColor =&#91;UIColor orangeColor&#93;.CGColor;
self.view.layer.cornerRadius =20.0;
self.view.layer.frame = CGRectInset&#40;self.view.layer.frame, 20, 20&#41;;
结果如下：

然后添加一个带阴影效果的子层，加入下列代码：
12345678CALayer *sublayer = [...]]]></description>
			<content:encoded><![CDATA[<p>前一阵子读到一篇介绍CALayer（这里简单地称其为层）的教程，比较简单易懂，适合初学者，我在这里就不完全翻译了，只是把要点说明一下。</p>
<p><span id="more-1104"></span></p>
<p>首先要说的是CALayers 是屏幕上的一个具有可见内容的矩形区域，每个UIView都有一个根CALayer，其所有的绘制（视觉效果）都是在这个layer上进行的。（译者注：为验证这点，我写下了如下代码：</p>
<div class="codecolorer-container objc mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br /></div></td><td><div class="objc codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">UILabel<span style="color: #002200;">*</span> lable <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #002200;">&#91;</span>UILabel alloc<span style="color: #002200;">&#93;</span>initWithFrame<span style="color: #002200;">:</span>CGRectMake<span style="color: #002200;">&#40;</span>0, 0, 100, 30<span style="color: #002200;">&#41;</span><span style="color: #002200;">&#93;</span>;<br />
lable.text <span style="color: #002200;">=</span> <span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;test&quot;</span>;<br />
<span style="color: #002200;">&#91;</span>self.view addSubview<span style="color: #002200;">:</span> lable<span style="color: #002200;">&#93;</span>;<br />
lable.backgroundColor <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>UIColor clearColor<span style="color: #002200;">&#93;</span>;<br />
<span style="color: #002200;">&#91;</span>lable release<span style="color: #002200;">&#93;</span>;<br />
<br />
<span style="color: #11740a; font-style: italic;">// 设定CALayer</span><br />
self.view.layer.backgroundColor <span style="color: #002200;">=</span><span style="color: #002200;">&#91;</span>UIColor orangeColor<span style="color: #002200;">&#93;</span>.CGColor;<br />
self.view.layer.cornerRadius <span style="color: #002200;">=</span><span style="color: #2400d9;">20.0</span>;<br />
self.view.layer.frame <span style="color: #002200;">=</span> CGRectInset<span style="color: #002200;">&#40;</span>self.view.layer.frame, 20, 20<span style="color: #002200;">&#41;</span>;</div></td></tr></tbody></table></div>
<p>请注意，我创建的UILable始终随着UIView的根CALayer的缩放而改变位置。）</p>
<p style="padding-top: 0px; padding-right: 0px; padding-bottom: 10px; padding-left: 0px; outline-width: 0px; outline-style: initial; outline-color: initial; font-weight: inherit; font-style: inherit; font-size: 13px; font-family: inherit; vertical-align: baseline; line-height: 1.4em; margin: 0px; border: 0px initial initial;">其次，CALayer的可以影响其外观的特性有：</p>
<ul style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 40px; outline-width: 0px; outline-style: initial; outline-color: initial; font-weight: inherit; font-style: inherit; font-size: 13px; font-family: inherit; vertical-align: baseline; list-style-type: none; list-style-position: initial; list-style-image: initial; padding: 0px; border: 0px initial initial;">
<li style="margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; outline-width: 0px; outline-style: initial; outline-color: initial; font-weight: inherit; font-style: inherit; font-size: 13px; font-family: inherit; vertical-align: baseline; display: list-item; line-height: 1.4em; list-style-type: disc; padding: 0px; border: 0px initial initial;">层的大小尺寸</li>
<li style="margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; outline-width: 0px; outline-style: initial; outline-color: initial; font-weight: inherit; font-style: inherit; font-size: 13px; font-family: inherit; vertical-align: baseline; display: list-item; line-height: 1.4em; list-style-type: disc; padding: 0px; border: 0px initial initial;">背景色</li>
<li style="margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; outline-width: 0px; outline-style: initial; outline-color: initial; font-weight: inherit; font-style: inherit; font-size: 13px; font-family: inherit; vertical-align: baseline; display: list-item; line-height: 1.4em; list-style-type: disc; padding: 0px; border: 0px initial initial;">内容（比如图像或是使用<a style="outline-width: 0px; outline-style: initial; outline-color: initial; font-weight: inherit; font-style: inherit; font-size: 13px; font-family: inherit; vertical-align: baseline; color: #006837; padding: 0px; margin: 0px; border: 0px initial initial;" href="http://www.raywenderlich.com/tag/core-graphics">Core Graphics</a>绘制的内容）</li>
<li style="margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; outline-width: 0px; outline-style: initial; outline-color: initial; font-weight: inherit; font-style: inherit; font-size: 13px; font-family: inherit; vertical-align: baseline; display: list-item; line-height: 1.4em; list-style-type: disc; padding: 0px; border: 0px initial initial;">是否使用圆角</li>
<li style="margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; outline-width: 0px; outline-style: initial; outline-color: initial; font-weight: inherit; font-style: inherit; font-size: 13px; font-family: inherit; vertical-align: baseline; display: list-item; line-height: 1.4em; list-style-type: disc; padding: 0px; border: 0px initial initial;">是否使用阴影</li>
<li style="margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; outline-width: 0px; outline-style: initial; outline-color: initial; font-weight: inherit; font-style: inherit; font-size: 13px; font-family: inherit; vertical-align: baseline; display: list-item; line-height: 1.4em; list-style-type: disc; padding: 0px; border: 0px initial initial;">等等</li>
</ul>
<p>需要说明的是CALayer的大部分属性都可以用来实现动画效果。</p>
<p>另外，你可以直接使用CALayer，也可以使用其子类，如CAGradientLayer，CATextLayer， CAShapeLayer等等。</p>
<h2>示例</h2>
<p>首先在Xcode中创建一个View-based App，CALayer是属于QuartzCore framework的，所以需要引入QuartzCore framework，另外在程序中包括QuartzCore.h<span style="font-family: monospace; color: #6e371a; font-size: small;"><span style="line-height: 15px; white-space: pre; ">。</span></span></p>
<p>第一个例子是创建一个带圆角的层，在你的ViewController中的ViewDidLoad中加入下面代码：</p>
<div class="codecolorer-container objc mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br /></div></td><td><div class="objc codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #11740a; font-style: italic;">// Import QuartzCore.h at the top of the file</span><br />
<span style="color: #6e371a;">#import &lt;QuartzCore/QuartzCore.h&gt;</span><br />
<br />
<span style="color: #11740a; font-style: italic;">// Uncomment viewDidLoad and add the following lines</span><br />
self.view.layer.backgroundColor <span style="color: #002200;">=</span><span style="color: #002200;">&#91;</span>UIColor orangeColor<span style="color: #002200;">&#93;</span>.CGColor;<br />
self.view.layer.cornerRadius <span style="color: #002200;">=</span><span style="color: #2400d9;">20.0</span>;<br />
self.view.layer.frame <span style="color: #002200;">=</span> CGRectInset<span style="color: #002200;">&#40;</span>self.view.layer.frame, 20, 20<span style="color: #002200;">&#41;</span>;</div></td></tr></tbody></table></div>
<p>结果如下：</p>
<p style="text-align: center; "><a href="http://www.iphone-geek.cn/wp-content/uploads/2011/01/1.jpg"><img class="size-full wp-image-1106 aligncenter" title="Simple Layer" src="http://www.iphone-geek.cn/wp-content/uploads/2011/01/1.jpg" alt="Simple Layer" width="320" height="480" /></a></p>
<p>然后添加一个带阴影效果的子层，加入下列代码：</p>
<div class="codecolorer-container objc mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br /></div></td><td><div class="objc codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">CALayer <span style="color: #002200;">*</span>sublayer <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>CALayer layer<span style="color: #002200;">&#93;</span>;<br />
sublayer.backgroundColor <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>UIColor blueColor<span style="color: #002200;">&#93;</span>.CGColor;<br />
sublayer.shadowOffset <span style="color: #002200;">=</span> CGSizeMake<span style="color: #002200;">&#40;</span>0, 3<span style="color: #002200;">&#41;</span>;<br />
sublayer.shadowRadius <span style="color: #002200;">=</span> <span style="color: #2400d9;">5.0</span>;<br />
sublayer.shadowColor <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>UIColor blackColor<span style="color: #002200;">&#93;</span>.CGColor;<br />
sublayer.shadowOpacity <span style="color: #002200;">=</span> <span style="color: #2400d9;">0.8</span>;<br />
sublayer.frame <span style="color: #002200;">=</span> CGRectMake<span style="color: #002200;">&#40;</span>30, 30, 128, 192<span style="color: #002200;">&#41;</span>;<br />
<span style="color: #002200;">&#91;</span>self.view.layer addSublayer<span style="color: #002200;">:</span>sublayer<span style="color: #002200;">&#93;</span>;</div></td></tr></tbody></table></div>
<p>效果图：<br />
<a href="http://www.iphone-geek.cn/wp-content/uploads/2011/01/2.jpg"><img class="aligncenter size-full wp-image-1108" title="2" src="http://www.iphone-geek.cn/wp-content/uploads/2011/01/2.jpg" alt="2" width="320" height="480" /></a></p>
<p>为子层增加内容（图片），你还可以设置层的边框，代码如下：</p>
<div class="codecolorer-container objc mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br /></div></td><td><div class="objc codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">sublayer.contents <span style="color: #002200;">=</span><span style="color: #002200;">&#40;</span><span style="color: #a61390;">id</span><span style="color: #002200;">&#41;</span><span style="color: #002200;">&#91;</span>UIImage imageNamed<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;BattleMapSplashScreen.png&quot;</span><span style="color: #002200;">&#93;</span>.CGImage;<br />
sublayer.borderColor <span style="color: #002200;">=</span><span style="color: #002200;">&#91;</span>UIColor blackColor<span style="color: #002200;">&#93;</span>.CGColor;<br />
sublayer.borderWidth <span style="color: #002200;">=</span><span style="color: #2400d9;">2.0</span>;</div></td></tr></tbody></table></div>
<p>效果图：<br />
<a href="http://www.iphone-geek.cn/wp-content/uploads/2011/01/3.jpg"><img class="aligncenter size-full wp-image-1110" title="3" src="http://www.iphone-geek.cn/wp-content/uploads/2011/01/3.jpg" alt="3" width="320" height="480" /></a></p>
<p>如果你希望子层也是圆角怎么办？你可能说很容易设置cornerRadius属性就行。实际上你即算是设置了cornerRadius属性，图片仍然不会显示圆角。你还需要设置masksToBounds为YES。但是这样做还是不够的，因为如果是这样，这个层的阴影显示就没有了。简单的实现方法如下（通过两个层来实现）：</p>
<div class="codecolorer-container objc mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br /></div></td><td><div class="objc codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">CALayer <span style="color: #002200;">*</span>sublayer <span style="color: #002200;">=</span><span style="color: #002200;">&#91;</span>CALayer layer<span style="color: #002200;">&#93;</span>;<br />
sublayer.backgroundColor <span style="color: #002200;">=</span><span style="color: #002200;">&#91;</span>UIColor blueColor<span style="color: #002200;">&#93;</span>.CGColor;<br />
sublayer.shadowOffset <span style="color: #002200;">=</span> CGSizeMake<span style="color: #002200;">&#40;</span>0, 3<span style="color: #002200;">&#41;</span>;<br />
sublayer.shadowRadius <span style="color: #002200;">=</span><span style="color: #2400d9;">5.0</span>;<br />
sublayer.shadowColor <span style="color: #002200;">=</span><span style="color: #002200;">&#91;</span>UIColor blackColor<span style="color: #002200;">&#93;</span>.CGColor;<br />
sublayer.shadowOpacity <span style="color: #002200;">=</span><span style="color: #2400d9;">0.8</span>;<br />
sublayer.frame <span style="color: #002200;">=</span> CGRectMake<span style="color: #002200;">&#40;</span>30, 30, 128, 192<span style="color: #002200;">&#41;</span>;<br />
sublayer.borderColor <span style="color: #002200;">=</span><span style="color: #002200;">&#91;</span>UIColor blackColor<span style="color: #002200;">&#93;</span>.CGColor;<br />
sublayer.borderWidth <span style="color: #002200;">=</span><span style="color: #2400d9;">2.0</span>;<br />
sublayer.cornerRadius <span style="color: #002200;">=</span><span style="color: #2400d9;">10.0</span>;<br />
<span style="color: #002200;">&#91;</span>self.view.layer addSublayer<span style="color: #002200;">:</span>sublayer<span style="color: #002200;">&#93;</span>;<br />
<br />
CALayer <span style="color: #002200;">*</span>imageLayer <span style="color: #002200;">=</span><span style="color: #002200;">&#91;</span>CALayer layer<span style="color: #002200;">&#93;</span>;<br />
imageLayer.frame <span style="color: #002200;">=</span> sublayer.bounds;<br />
imageLayer.cornerRadius <span style="color: #002200;">=</span><span style="color: #2400d9;">10.0</span>;<br />
imageLayer.contents <span style="color: #002200;">=</span><span style="color: #002200;">&#40;</span><span style="color: #a61390;">id</span><span style="color: #002200;">&#41;</span><span style="color: #002200;">&#91;</span>UIImage imageNamed<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;BattleMapSplashScreen.png&quot;</span><span style="color: #002200;">&#93;</span>.CGImage;<br />
imageLayer.masksToBounds <span style="color: #002200;">=</span><span style="color: #a61390;">YES</span>;<br />
<span style="color: #002200;">&#91;</span>sublayer addSublayer<span style="color: #002200;">:</span>imageLayer<span style="color: #002200;">&#93;</span>;</div></td></tr></tbody></table></div>
<p>效果图：<br />
<a href="http://www.iphone-geek.cn/wp-content/uploads/2011/01/4.jpg"><img class="aligncenter size-full wp-image-1111" title="4" src="http://www.iphone-geek.cn/wp-content/uploads/2011/01/4.jpg" alt="4" width="320" height="480" /></a></p>
<p>最后，还介绍一下自绘图型的实现，其要点是要设置所绘制层的delegate。比如在我们的例子中使用ViewController作为delegate，那么就需要在ViewController中实现drawLayer:inContext方法，对层进行绘制工作。另外，还需要调用setNeedsDisplay，来通知层需要进行绘制了，于是层才会通过对delegate的drawLayer:inContext方法进行调用。</p>
<p>代码如下：</p>
<div class="codecolorer-container objc mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br />19<br />20<br />21<br />22<br />23<br />24<br />25<br />26<br />27<br />28<br />29<br />30<br />31<br />32<br />33<br />34<br />35<br />36<br />37<br />38<br />39<br />40<br />41<br />42<br />43<br /></div></td><td><div class="objc codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #a61390;">void</span> MyDrawColoredPattern <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">*</span>info, CGContextRef context<span style="color: #002200;">&#41;</span><span style="color: #002200;">&#123;</span><br />
&nbsp;<br />
&nbsp; &nbsp; CGColorRef dotColor <span style="color: #002200;">=</span><span style="color: #002200;">&#91;</span>UIColor colorWithHue<span style="color: #002200;">:</span>0 saturation<span style="color: #002200;">:</span>0 brightness<span style="color: #002200;">:</span>0.07 alpha<span style="color: #002200;">:</span>1.0<span style="color: #002200;">&#93;</span>.CGColor;<br />
&nbsp; &nbsp; CGColorRef shadowColor <span style="color: #002200;">=</span><span style="color: #002200;">&#91;</span>UIColor colorWithRed<span style="color: #002200;">:</span>1 green<span style="color: #002200;">:</span>1 blue<span style="color: #002200;">:</span>1 alpha<span style="color: #002200;">:</span>0.1<span style="color: #002200;">&#93;</span>.CGColor;<br />
&nbsp;<br />
&nbsp; &nbsp; CGContextSetFillColorWithColor<span style="color: #002200;">&#40;</span>context, dotColor<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; CGContextSetShadowWithColor<span style="color: #002200;">&#40;</span>context, CGSizeMake<span style="color: #002200;">&#40;</span>0, 1<span style="color: #002200;">&#41;</span>, 1, shadowColor<span style="color: #002200;">&#41;</span>;<br />
&nbsp;<br />
&nbsp; &nbsp; CGContextAddArc<span style="color: #002200;">&#40;</span>context, 3, 3, 4, 0, radians<span style="color: #002200;">&#40;</span>360<span style="color: #002200;">&#41;</span>, 0<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; CGContextFillPath<span style="color: #002200;">&#40;</span>context<span style="color: #002200;">&#41;</span>;<br />
&nbsp;<br />
&nbsp; &nbsp; CGContextAddArc<span style="color: #002200;">&#40;</span>context, 16, 16, 4, 0, radians<span style="color: #002200;">&#40;</span>360<span style="color: #002200;">&#41;</span>, 0<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; CGContextFillPath<span style="color: #002200;">&#40;</span>context<span style="color: #002200;">&#41;</span>;<br />
&nbsp;<br />
<span style="color: #002200;">&#125;</span><br />
<br />
<span style="color: #002200;">-</span><span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>drawLayer<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span>CALayer <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>layer inContext<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span>CGContextRef<span style="color: #002200;">&#41;</span>context <span style="color: #002200;">&#123;</span><br />
&nbsp;<br />
&nbsp; &nbsp; CGColorRef bgColor <span style="color: #002200;">=</span><span style="color: #002200;">&#91;</span>UIColor colorWithHue<span style="color: #002200;">:</span>0.6 saturation<span style="color: #002200;">:</span>1.0 brightness<span style="color: #002200;">:</span>1.0 alpha<span style="color: #002200;">:</span>1.0<span style="color: #002200;">&#93;</span>.CGColor;<br />
&nbsp; &nbsp; CGContextSetFillColorWithColor<span style="color: #002200;">&#40;</span>context, bgColor<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; CGContextFillRect<span style="color: #002200;">&#40;</span>context, layer.bounds<span style="color: #002200;">&#41;</span>;<br />
&nbsp;<br />
&nbsp; &nbsp; staticconst CGPatternCallbacks callbacks <span style="color: #002200;">=</span><span style="color: #002200;">&#123;</span>0, <span style="color: #002200;">&amp;</span>MyDrawColoredPattern, <span style="color: #a61390;">NULL</span><span style="color: #002200;">&#125;</span>;<br />
&nbsp;<br />
&nbsp; &nbsp; CGContextSaveGState<span style="color: #002200;">&#40;</span>context<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; CGColorSpaceRef patternSpace <span style="color: #002200;">=</span> CGColorSpaceCreatePattern<span style="color: #002200;">&#40;</span><span style="color: #a61390;">NULL</span><span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; CGContextSetFillColorSpace<span style="color: #002200;">&#40;</span>context, patternSpace<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; CGColorSpaceRelease<span style="color: #002200;">&#40;</span>patternSpace<span style="color: #002200;">&#41;</span>;<br />
&nbsp;<br />
&nbsp; &nbsp; CGPatternRef pattern <span style="color: #002200;">=</span> CGPatternCreate<span style="color: #002200;">&#40;</span><span style="color: #a61390;">NULL</span>,<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;layer.bounds,<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;CGAffineTransformIdentity,<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;24,<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;24,<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;kCGPatternTilingConstantSpacing,<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span style="color: #a61390;">true</span>,<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span style="color: #002200;">&amp;</span>callbacks<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; CGFloat alpha <span style="color: #002200;">=</span><span style="color: #2400d9;">1.0</span>;<br />
&nbsp; &nbsp; CGContextSetFillPattern<span style="color: #002200;">&#40;</span>context, pattern, <span style="color: #002200;">&amp;</span>alpha<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; CGPatternRelease<span style="color: #002200;">&#40;</span>pattern<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; CGContextFillRect<span style="color: #002200;">&#40;</span>context, layer.bounds<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; CGContextRestoreGState<span style="color: #002200;">&#40;</span>context<span style="color: #002200;">&#41;</span>;<br />
<span style="color: #002200;">&#125;</span></div></td></tr></tbody></table></div>
<p>还需要注意，radians是一个自定义函数：</p>
<div class="codecolorer-container objc mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br /></div></td><td><div class="objc codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #a61390;">static</span> inline <span style="color: #a61390;">double</span> radians <span style="color: #002200;">&#40;</span><span style="color: #a61390;">double</span> degrees<span style="color: #002200;">&#41;</span> <span style="color: #002200;">&#123;</span> <span style="color: #a61390;">return</span> degrees <span style="color: #002200;">*</span> M_PI<span style="color: #002200;">/</span><span style="color: #2400d9;">180</span>; <span style="color: #002200;">&#125;</span></div></td></tr></tbody></table></div>
<p>效果如下：<br />
<a href="http://www.iphone-geek.cn/wp-content/uploads/2011/01/5.jpg"><img class="aligncenter size-full wp-image-1112" title="5" src="http://www.iphone-geek.cn/wp-content/uploads/2011/01/5.jpg" alt="5" width="320" height="480" /></a></p>
<p>本文的完整代码<a title="下载" href="http://www.raywenderlich.com/downloads/LayerFun.zip">下载</a>。</p>
<h5>原文见：<a title="Introduction to CALayers Tutorial" href="http://www.raywenderlich.com/2502/introduction-to-calayers-tutorial">Introduction to CALayers Tutorial</a></h5>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/calayer%e7%ae%80%e5%8d%95%e6%95%99%e7%a8%8b/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>怎样减小png图像文件的尺寸</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e6%80%8e%e6%a0%b7%e5%87%8f%e5%b0%8fpng%e5%9b%be%e5%83%8f%e6%96%87%e4%bb%b6%e7%9a%84%e5%b0%ba%e5%af%b8</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e6%80%8e%e6%a0%b7%e5%87%8f%e5%b0%8fpng%e5%9b%be%e5%83%8f%e6%96%87%e4%bb%b6%e7%9a%84%e5%b0%ba%e5%af%b8#comments</comments>
		<pubDate>Mon, 25 Oct 2010 08:11:42 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[图形图像]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[开源项目]]></category>
		<category><![CDATA[教程]]></category>
		<category><![CDATA[优化]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=1081</guid>
		<description><![CDATA[有两种方法:

直接使用preview打开png文件,然后选择&#8221;另存为&#8221;,覆盖原文件.
使用pngcrush开源软件,这种方法能使png文件更小,但由于pngcrush仅提供源代码需要自己编译.不过这也不是什么问题.

编译pngcrush的步骤:

下载pngcrush
解压
编译.非常简单,使用	
make
进行编译
复制到用户bin目录下
sudo mv pngcrush /usr/local/bin/

使用pngcrush也非常简单,例如:

pngcrush -reduce -brute -d output image.png image.png
就是将图像文件image.png减小尺寸,输出到output目录下.非常遗憾,pngcrsh并不支持目录操作.不过反正有源代码,自己改一下也不是什么大问题吧?
]]></description>
			<content:encoded><![CDATA[<p>有两种方法:</p>
<ol>
<li>直接使用preview打开png文件,然后选择&#8221;另存为&#8221;,覆盖原文件.</li>
<li>使用pngcrush开源软件,这种方法能使png文件更小,但由于pngcrush仅提供源代码需要自己编译.不过这也不是什么问题.</li>
</ol>
<p>编译pngcrush的步骤:</p>
<ol>
<li>下载<a href="http://pmt.sourceforge.net/pngcrush/">pngcrush</a></li>
<li>解压</li>
<li>编译.非常简单,使用	</li>
<blockquote><p>make</p></blockquote>
<li>进行编译</li>
<li>复制到用户bin目录下</li>
<blockquote><p>sudo mv pngcrush /usr/local/bin/</p></blockquote>
</ol>
<p>使用pngcrush也非常简单,例如:</p>
<blockquote><p>
pngcrush -reduce -brute -d output image.png image.png</p></blockquote>
<p>就是将图像文件image.png减小尺寸,输出到output目录下.非常遗憾,pngcrsh并不支持目录操作.不过反正有源代码,自己改一下也不是什么大问题吧?</p>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e6%80%8e%e6%a0%b7%e5%87%8f%e5%b0%8fpng%e5%9b%be%e5%83%8f%e6%96%87%e4%bb%b6%e7%9a%84%e5%b0%ba%e5%af%b8/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>iPhone上通过http传递JSON数据</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/iphone%e4%b8%8a%e9%80%9a%e8%bf%87http%e4%bc%a0%e9%80%92json%e6%95%b0%e6%8d%ae</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/iphone%e4%b8%8a%e9%80%9a%e8%bf%87http%e4%bc%a0%e9%80%92json%e6%95%b0%e6%8d%ae#comments</comments>
		<pubDate>Fri, 06 Aug 2010 09:03:49 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[编程]]></category>
		<category><![CDATA[网络]]></category>
		<category><![CDATA[JSON]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=1019</guid>
		<description><![CDATA[

本教程介绍了在iPhone程序中怎样访问JSON web service的详细步骤。


本教程创建了一个简单示例“幸运数字”，它有一个标签，其内容是通过建立于HTTP web service之上的JSON更新的。

源代码/Github

本教程的 源代码 存储于GitHub上。请运行下列命令获取代码：


打开终端程序，改变目录到你希望存储源代码的目录下
输入 git clone  git://github.com/dcgrigsby/luckynumbers.git 获取源代码




iPhone JSON 库

本教程使用了Stig Brautasetd的JSON library (version 2.2)，它提供了解析和生成JSON的功能。在本教程中没有使用生成JSON的功能。

此JSON库提供两种方法解析JSON：(1) 通过category对NSString进行扩展以实现JSON解析 (2) 较为底层一点的基于目标的解析器。本教程从简单方案开始，即方案(1)；在本教程介绍时将介绍方案(2)。


下载 磁盘映像



在稍后的步骤中我们将介绍磁盘映像的使用。

创建项目

启动Xcode 并创建一个新的 View-Based iPhone 程序，命名为  LuckyNumbers:


在Xcode菜单中使用 File &#62; New Project… 创建一个新项目
 在 iPhone OS &#62;  Application 中点取View-Based Application，按下Choose…
将此项目命名为 LuckyNumbers 并按下 Save



为此项目增添JSON支持

要使用JSON功能，我们需要将JSON库加入到项目中：


在项目的 Groups  &#38; Files 面板上展开 LuckyNumbers 项目。
使用Finder，找到你先前下载的 JSON_2.2.dmg 文件，双击加载磁盘映像。带有此DMG内容的另一个新的Finder窗口将打开
将此DMG内容中的 JSON 目录拖入到Xcode [...]]]></description>
			<content:encoded><![CDATA[<p><img src="http://www.mobileorchard.com/wp-content/uploads/2009/03/snap.png" alt="" /><br />
<br class="spacer_" /><br />
本教程介绍了在iPhone程序中怎样访问JSON web service的详细步骤。<br />
<br class="spacer_" /><br />
<span id="more-1019"></span><br />
本教程创建了一个简单示例“幸运数字”，它有一个标签，其内容是通过建立于HTTP web service之上的JSON更新的。<br />
<br class="spacer_" /></p>
<h3>源代码/Github</h3>
<p><br class="spacer_" /><br />
本教程的 <a href="http://github.com/dcgrigsby/luckynumbers">源代码</a> 存储于GitHub上。请运行下列命令获取代码：</p>
<blockquote>
<ol style="font-size: 90%;">
<li>打开终端程序，改变目录到你希望存储源代码的目录下</li>
<li>输入 <em>git clone  git://github.com/dcgrigsby/luckynumbers.git </em>获取源代码<em><br />
</em></li>
</ol>
</blockquote>
<p><br class="spacer_" /></p>
<h3>iPhone JSON 库</h3>
<p><br class="spacer_" /><br />
本教程使用了Stig Brautasetd的<a href="http://code.google.com/p/json-framework/">JSON library</a> (version 2.2)，它提供了解析和生成JSON的功能。在本教程中没有使用生成JSON的功能。<br />
<br class="spacer_" /><br />
此JSON库提供两种方法解析JSON：(1) 通过category对NSString进行扩展以实现JSON解析 (2) 较为底层一点的基于目标的解析器。本教程从简单方案开始，即方案(1)；在本教程介绍时将介绍方案(2)。</p>
<blockquote>
<ol style="font-size: 90%;">
<li><a href="http://code.google.com/p/json-framework/downloads/list">下载</a> 磁盘映像</li>
</ol>
</blockquote>
<p><br class="spacer_" /><br />
在稍后的步骤中我们将介绍磁盘映像的使用。<br />
<br class="spacer_" /></p>
<h3>创建项目</h3>
<p><br class="spacer_" /><br />
启动Xcode 并创建一个新的 View-Based iPhone 程序，命名为  LuckyNumbers:</p>
<blockquote>
<ol style="font-size: 90%;">
<li>在Xcode菜单中使用 <em>File &gt; New Project…</em> 创建一个新项目</li>
<li><em> </em>在 <em>iPhone OS &gt;  Application</em> 中点取<em>View-Based Application，</em>按下<em>Choose…</em></li>
<li>将此项目命名为 <em>LuckyNumbers</em> 并按下 <em>Save</em></li>
</ol>
</blockquote>
<p><br class="spacer_" /></p>
<h3>为此项目增添JSON支持</h3>
<p><br class="spacer_" /><br />
要使用JSON功能，我们需要将JSON库加入到项目中：</p>
<blockquote>
<ol style="font-size: 90%;">
<li>在项目的<em> Groups  &amp; Files</em> 面板上展开 <em>LuckyNumbers</em> 项目。</li>
<li>使用Finder，找到你先前下载的 <em>JSON_2.2.dmg</em> 文件，双击加载磁盘映像。带有此DMG内容的另一个新的Finder窗口将打开</li>
<li>将此DMG内容中的 <em>JSON</em> 目录拖入到Xcode <em>Groups  &amp; Files </em>面板中<em>LuckyNumbers</em> 项目图标下的<em>Classes</em> 文件夹中</li>
</ol>
</blockquote>
<p><br class="spacer_" /><br />
为测试JSON库是否正确设置，我们将解析一个JSON字典字符串，并在console中输出其结果NSDictionary。此功能是在<em>viewDidLoad</em>中完成。<br />
<br class="spacer_" /><br />
在 LuckyNumbersViewController.m 中加入如下代码：</p>
<pre>
<div class="codecolorer-container text mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">#import &quot;LuckyNumbersViewController.h&quot;<br />
#import &quot;JSON/JSON.h&quot;<br />
<br />
@implementation LuckyNumbersViewController<br />
<br />
- (void)viewDidLoad {<br />
&nbsp; &nbsp; [super viewDidLoad];<br />
&nbsp; &nbsp; NSString *jsonString = [NSString stringWithString:@&quot;{\&quot;foo\&quot;: \&quot;bar\&quot;}&quot;];<br />
&nbsp; &nbsp; NSDictionary *dictionary = [jsonString JSONValue];<br />
&nbsp; &nbsp; NSLog(@&quot;Dictionary value for \&quot;foo\&quot; is \&quot;%@\&quot;&quot;, [dictionary objectForKey:@&quot;foo&quot;]);<br />
}<br />
<br />
- (void)dealloc {<br />
&nbsp; &nbsp; [super dealloc];<br />
}<br />
<br />
@end</div></td></tr></tbody></table></div>
</pre>
<p><br class="spacer_" /><br />
运行项目。如果 JSON SDK 设置正确的话，你应该将在console中见到如下信息：“ <em>Dictionary value  for “foo” is “bar” ”</em>.<br />
<br class="spacer_" /></p>
<h3>简单UI设置</h3>
<p><br class="spacer_" /><br />
我们最终成品是使用 UILabel 来显示通过HTTP 和 JSON 获取的幸运数字。<br />
<br class="spacer_" /><br />
在 LuckyNumbersViewController.h 使用如下代码：</p>
<pre>
<div class="codecolorer-container text mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">#import &lt;UIKit/UIKit.h&gt;<br />
<br />
@interface LuckyNumbersViewController : UIViewController {<br />
&nbsp; &nbsp; IBOutlet UILabel *label;<br />
}<br />
<br />
@end</div></td></tr></tbody></table></div>
</pre>
<p><br class="spacer_" /><br />
<em>IBOutlet</em> 是一个宏，它通知编译器将变量与通过Interface Builder 的WYSIWYG添加的UILabel元件联系起来。下一步我们将添加元件并将这两部分连接起来。<br />
<br class="spacer_" /><br />
在Interface Builder中编辑 LuckyNumbersViewController.xib 文件：</p>
<blockquote>
<ol style="font-size: 90%;">
<li>在<em>Groups &amp; Files</em>中展开<em>LuckyNumbers</em>下的 <em>Resources</em> 文件夹。</li>
<li>双击 <em>LuckyNumbersViewController.xib</em> 文件。</li>
</ol>
</blockquote>
<p><br class="spacer_" /><br />
确保 Library, Inspector 和 View 窗口都打开/可见。如果没有的话：</p>
<blockquote>
<ol style="font-size: 90%;">
<li>在菜单中使用 <em>Tools &gt; Library</em> 显示 Library 窗口</li>
<li>在菜单中使用 <em>Tools &gt; Inspector</em> 显示 Inspector 窗口</li>
<li>在 <em>LuckyNumbersViewController.xib</em> 窗口中点击 View 图标</li>
</ol>
</blockquote>
<p><br class="spacer_" /><br />
添加一个label:</p>
<blockquote>
<ol style="font-size: 90%;">
<li>在<em>Library </em>窗口中找到 <em>Label</em> 元件并拖入view</li>
<li>在 <em>View </em>窗口中将 label 放大到整个view的一半</li>
<li>在 <em>Inspector </em> 窗口中的 <em>View Attributes</em>下 , 将 label 的行数设为0。</li>
</ol>
</blockquote>
<p><br class="spacer_" /><br />
将 label 行数设为0表示lable的尺寸在其指定的边界内动态可变。<br />
<br class="spacer_" /><br />
连接 Interface Builder 中的 label 到代码 <em>label</em>。   仍然在 Interface Builder 中:</p>
<blockquote>
<ol style="font-size: 90%;">
<li>Control + 点击<em>LuckyNumbersViewController.xib</em> 窗口中的 <em>File’s Owner</em> 图标</li>
<li>在弹出菜单中，点击并按住<em>Outlets</em> 部分 lable 行右方的圆圈</li>
<li>拖动鼠标到View中的 <em>Label</em> 上。 将出现一条蓝线连接这两个部分。</li>
</ol>
</blockquote>
<p><br class="spacer_" /><br />
这两个部分连接后，弹出菜单将如图所示。<br />
<br class="spacer_" /><br />
<img src="http://www.mobileorchard.com/wp-content/uploads/2009/03/connection.png" alt="" /><br />
<br class="spacer_" /><br />
如果一切正常的话，保存并关闭Interface  Builder。<br />
<br class="spacer_" /></p>
<h3>通过HTTP获取 JSON 数据</h3>
<p><br class="spacer_" /><br />
我们将使用 Cocoa 中的 NSURLConnection 来发送一个 HTTP 请求从而获取 JSON 数据。<br />
<br class="spacer_" /><br />
Cocoa 在进行HTTP请求时有两种选项：同步和异步。同步请求中程序的主runloop中运行，使得在请求的过程中，程序停止运行。而异步请求使用回调来避免程序锁住而且简单易用。因此我们使用异步请求。<br />
<br class="spacer_" /><br />
首先我们需要修改视图控制器接口，创建一个<em>NSMutableData</em>变量来保存响应数据。我们在接口（.h）中而不是在方法中定义此变量是因为响应数据是逐步的而不是一次性返回的，我们需要将所有数据组合在一起。<br />
<br class="spacer_" /><br />
修改 LuckNumbersViewController.h。改变如下黑体所示：</p>
<pre>#import &lt;UIKit/UIKit.h&gt;

@interface LuckyNumbersViewController : UIViewController {
	IBOutlet UILabel *label;
	<strong>NSMutableData *responseData;</strong>
}</pre>
<p><br class="spacer_" /><br />
简单起见，我们在 <em>viewDidLoad</em>中进行HTTP请求。<br />
<br class="spacer_" /><br />
修改 LuckyNumbersViewController.m :</p>
<pre>
<div class="codecolorer-container text mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br />19<br />20<br />21<br />22<br />23<br />24<br />25<br />26<br />27<br />28<br />29<br />30<br />31<br />32<br />33<br />34<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">#import &quot;LuckyNumbersViewController.h&quot;<br />
#import &quot;JSON/JSON.h&quot;<br />
<br />
@implementation LuckyNumbersViewController<br />
<br />
- (void)viewDidLoad {<br />
&nbsp; &nbsp; [super viewDidLoad];<br />
<br />
&nbsp; &nbsp; responseData = [[NSMutableData data] retain];<br />
&nbsp; &nbsp; NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@&quot;http://www.unpossible.com/misc/lucky_numbers.json&quot;]];<br />
&nbsp; &nbsp; [[NSURLConnection alloc] initWithRequest:request delegate:self];<br />
}<br />
<br />
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {<br />
&nbsp; &nbsp; [responseData setLength:0];<br />
}<br />
<br />
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {<br />
&nbsp; &nbsp; [responseData appendData:data];<br />
}<br />
<br />
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {<br />
&nbsp; &nbsp; label.text = [NSString stringWithFormat:@&quot;Connection failed: %@&quot;, [error description]];<br />
}<br />
<br />
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {<br />
&nbsp; &nbsp; [connection release];<br />
}<br />
<br />
- (void)dealloc {<br />
&nbsp; &nbsp; [super dealloc];<br />
}<br />
<br />
@end</div></td></tr></tbody></table></div>
</pre>
<p><br class="spacer_" /><br />
responseData 变量用来存放数据，然后在  viewDidload中进行HTTP连接;  <em>didReceiveData</em>用来收集返回的数据; 空白的 connectionDidFinishLoading 表示数据响应结束。<br />
<br class="spacer_" /></p>
<h3>使用 JSON 数据</h3>
<p><br class="spacer_" /><br />
下面我们将展示怎样在 connectionDidFinishLoading 中对获取的 JSON 数据的进行处理。<br />
<br class="spacer_" /><br />
修改LuckyNumbersViewController.m中的 connectionDidFinishLoading 方法。使用下面代码：</p>
<pre>
<div class="codecolorer-container text mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">- (void)connectionDidFinishLoading:(NSURLConnection *)connection {<br />
&nbsp; &nbsp; [connection release];<br />
<br />
&nbsp; &nbsp; NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];<br />
&nbsp; &nbsp; [responseData release];<br />
<br />
&nbsp; &nbsp; NSArray *luckyNumbers = [responseString JSONValue];<br />
<br />
&nbsp; &nbsp; NSMutableString *text = [NSMutableString stringWithString:@&quot;Lucky numbers:\n&quot;];<br />
<br />
&nbsp; &nbsp; for (int i = 0; i &amp;lt; [luckyNumbers count]; i++)<br />
&nbsp; &nbsp; &nbsp; &nbsp; [text appendFormat:@&quot;%@\n&quot;, [luckyNumbers objectAtIndex:i]];<br />
<br />
&nbsp; &nbsp; label.text = &nbsp;text;<br />
}</div></td></tr></tbody></table></div>
</pre>
<p><br class="spacer_" /><br />
本文最开始的示例代码中使用了 NSDictionary。而现在我们使用 NSArray。 解析器非常灵活，其返回对象 &#8211; 包括嵌套对象 &#8211; 将 JSON数据类型与Objective-C的数据类型很好地匹配起来。<br />
<br class="spacer_" /></p>
<h3>更好的错误处理</h3>
<p><br class="spacer_" /><br />
至此我们使用了NSString扩展的方法对JSON数据进行了解析。这样做是非常简单的。<br />
<br class="spacer_" /><br />
但不幸的是，使用此方法是错误处理变得很困难。如果JSON解析器因为某种原因失败的话，它仅仅返回一个nil值。但是如果错误发生时你观察console，你就会发现一些信息描述了引擎解析器失败的原因。<br />
<br class="spacer_" /><br />
如果我们能将详细错误信息传递给用户，那将更加用户友好。要达到这个目的，我们将切换到JSON SDK支持的第二种面向对象的方法。<br />
<br class="spacer_" /><br />
修改LuckyNumbersViewController.m中<em>connectionDidFinishLoading</em> 方法。使用下面代码：</p>
<div class="codecolorer-container text mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br />19<br />20<br />21<br />22<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">- (void)connectionDidFinishLoading:(NSURLConnection *)connection {<br />
&nbsp; &nbsp; [connection release];<br />
<br />
&nbsp; &nbsp; NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];<br />
&nbsp; &nbsp; [responseData release];<br />
<br />
&nbsp; &nbsp; NSError *error;<br />
&nbsp; &nbsp; SBJSON *json = [[SBJSON new] autorelease];<br />
&nbsp; &nbsp; NSArray *luckyNumbers = [json objectWithString:responseString error:&amp;error];<br />
&nbsp; &nbsp; [responseString release]; &nbsp; <br />
<br />
&nbsp; &nbsp; if (luckyNumbers == nil)<br />
&nbsp; &nbsp; &nbsp; &nbsp; label.text = [NSString stringWithFormat:@&quot;JSON parsing failed: %@&quot;, [error localizedDescription]];<br />
&nbsp; &nbsp; else {<br />
&nbsp; &nbsp; &nbsp; &nbsp; NSMutableString *text = [NSMutableString stringWithString:@&quot;Lucky numbers:\n&quot;];<br />
<br />
&nbsp; &nbsp; &nbsp; &nbsp; for (int i = 0; i &lt;; [luckyNumbers count]; i++)<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; [text appendFormat:@&quot;%@\n&quot;, [luckyNumbers objectAtIndex:i]];<br />
<br />
&nbsp; &nbsp; &nbsp; &nbsp; label.text = &nbsp;text;<br />
&nbsp; &nbsp; }<br />
}</div></td></tr></tbody></table></div>
</pre>
<p><br class="spacer_" /><br />
使用此方法可以帮助我们在JSON解析器发生错误时指出错误所在以便进行更好的错误处理。<br />
<br class="spacer_" /></p>
<h3>结论</h3>
<p><br class="spacer_" /><br />
JSON SDK 和 Cocoa 固有的 HTTP功能使得在iPhone应用程序中运用JSON web service变得非常容易。<br />
<br class="spacer_" /><br />
原文见：<a href="http://www.mobileorchard.com/tutorial-json-over-http-on-the-iphone/">Tutorial: JSON Over HTTP On The iPhone</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/iphone%e4%b8%8a%e9%80%9a%e8%bf%87http%e4%bc%a0%e9%80%92json%e6%95%b0%e6%8d%ae/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>IAP（程序内购买）: 完全攻略</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/iap%ef%bc%88%e7%a8%8b%e5%ba%8f%e5%86%85%e8%b4%ad%e4%b9%b0%ef%bc%89-%e5%ae%8c%e5%85%a8%e6%94%bb%e7%95%a5</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/iap%ef%bc%88%e7%a8%8b%e5%ba%8f%e5%86%85%e8%b4%ad%e4%b9%b0%ef%bc%89-%e5%ae%8c%e5%85%a8%e6%94%bb%e7%95%a5#comments</comments>
		<pubDate>Mon, 02 Aug 2010 06:20:40 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[基础]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[In-App Purchase]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=992</guid>
		<description><![CDATA[
第一印象觉得In-App Purchase（简称IAP）非常简单。Apple提供的大量文档应该让开发者很快熟悉地熟悉。那么，为什么在你的应用中集成IAP特性就如此令人生厌呢？

这是因为在开发过程中不可避免会出现一些错误。而但这些错误发生的时候，你就抓瞎了。虽然Apple提供了有关IAP的大量文档，但他们并未提及集成IAP的详细步骤。而且对StoreKit集成过程中出现的问题也没有一个核对清单。另外对于为什么诸如产品ID非法之类的问题也没有提供NSError之类的对象来告诉你原因。

在试用了各种可能的解决方案后，你只能身心疲惫，彷徨无助。

为了提高你的效率和减少你的痛苦，我觉定利用此文来介绍一下实现IAP的详细步骤。本文很详细，有点长。甚至可能太长了，但不像Apple的文档，它提供了为实现IAP的每一个步骤。

废话少说，我们直入主题吧。


概况

IAP能正常工作的秘诀：分成两个步骤：

创建及提取产品描述
购买产品


第一个步骤是你可能遇到问题的部分。一旦你在代码中成功地获取了产品描述，编写购买产品的代码不过是小菜一碟。

我们先看看步骤1。

创建及提取产品描述

下面是有关创建产品及提取其描述的非常粗略的步骤：

创建唯一的App ID
生成及安装新的provisioning profile文件
在Xcode中更新 bundle ID 及 code signing profile
如果还没做的话，请在iTunes Connect中提交有关你程序的 metadata
如果还没做的话，请在iTunes Connect中提交你程序的二进制码
为IAP添加新产品
编写提取产品描述的代码
等待几小时


提取产品描述的代码非常简单，但其他步骤则很容易错。
注意： 为提取产品描述，你并不需要在iTunes Connect中创建IAP测试用户。

 
1. 创建唯一的App ID

为支持IAP，你的App ID不能包括通配符（“*”）。为确定你的App Id是否包括通配符，请登录http://developer.apple.com/iphone,在 iPhone Developer Program Portal中选择左边菜单中的 “App IDs”检查你的 App ID。

下面是一个唯一的App ID:
7DW89RZKLY.com.runmonster.runmonsterfree

下面不是一个唯一的 App ID:
7DW89RZKLY.com.runmonster.*

如果你还没有一个唯一的App ID，按如下步骤创建一个：

在developer portal中的 App IDs 部分，选择“New App ID”
填写下列信息：

Display name（显示名）: 选取一个不同的App ID的名称。你不能编辑或删除旧的App ID，所以你必须为你的App ID提供一个新名称以避免混淆。
Prefix（前缀）: 生成一个新的前缀，或者如果你的程序是通过Keychain Services API分享数据的系列程序中之一的话，则选用已存在的前缀。
Suffix（后缀）: com.companyname.appname (这是通用格式 &#8211; 注意没有使用通配符）。


按 “Save”
按 App ID旁的“Configure” 链接
选取 “Enable [...]]]></description>
			<content:encoded><![CDATA[<div>
<p>第一印象觉得In-App Purchase（简称IAP）非常简单。Apple提供的<a href="http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Introduction/Introduction.html">大量</a><a href="https://itunesconnect.apple.com/docs/iTunesConnect_DeveloperGuide.pdf">文档</a>应该让开发者很快熟悉地熟悉。那么，为什么在你的应用中集成IAP特性就如此令人生厌呢？</p>
<p><br class="spacer_" /></p>
<p>这是因为在开发过程中不可避免会出现一些错误。而但这些错误发生的时候，你就抓瞎了。虽然Apple提供了有关IAP的大量文档，但他们并未提及集成IAP的详细步骤。而且对StoreKit集成过程中出现的问题也没有一个核对清单。另外对于为什么诸如产品ID非法之类的问题也没有提供NSError之类的对象来告诉你原因。</p>
<p><br class="spacer_" /></p>
<p>在试用了各种可能的解决方案后，你只能身心疲惫，彷徨无助。</p>
<p><br class="spacer_" /></p>
<p>为了提高你的效率和减少你的痛苦，我觉定利用此文来介绍一下实现IAP的详细步骤。本文很详细，有点长。甚至可能太长了，但不像Apple的文档，它提供了为实现IAP的每一个步骤。</p>
<p><br class="spacer_" /></p>
<p>废话少说，我们直入主题吧。</p>
<p><br class="spacer_" /></p>
<p><span id="more-992"></span></p>
<h2><strong>概况</strong></h2>
<p><br class="spacer_" /></p>
<p>IAP能正常工作的秘诀：分成两个步骤：</p>
<ol>
<li>创建及提取产品描述</li>
<li>购买产品</li>
</ol>
<p><br class="spacer_" /></p>
<p>第一个步骤是你可能遇到问题的部分。一旦你在代码中成功地获取了产品描述，编写购买产品的代码不过是小菜一碟。</p>
<p><br class="spacer_" /></p>
<p>我们先看看步骤1。</p>
<p><br class="spacer_" /></p>
<h2>创建及提取产品描述</h2>
<p><br class="spacer_" /></p>
<p>下面是有关创建产品及提取其描述的非常粗略的步骤：</p>
<ol>
<li>创建唯一的App ID</li>
<li>生成及安装新的provisioning profile文件</li>
<li>在Xcode中更新 bundle ID 及 code signing profile</li>
<li>如果还没做的话，请在iTunes Connect中提交有关你程序的 metadata</li>
<li>如果还没做的话，请在iTunes Connect中提交你程序的二进制码</li>
<li>为IAP添加新产品</li>
<li>编写提取产品描述的代码</li>
<li>等待几小时</li>
</ol>
<p><br class="spacer_" /></p>
<p>提取产品描述的代码非常简单，但其他步骤则很容易错。</p>
<p><em><strong>注意：</strong> 为提取产品描述，你并<strong>不</strong>需要</em><em>在iTunes Connect中</em><em>创建IAP测试用户。</em></p>
<p><em><br />
 </em></p>
<h3>1. 创建唯一的App ID</h3>
<p><br class="spacer_" /></p>
<p>为支持IAP，你的App ID不能包括通配符（“*”）。为确定你的App Id是否包括通配符，请登录<a href="http://developer.apple.com/iphone">http://developer.apple.com/iphone</a>,在 iPhone Developer Program Portal中选择左边菜单中的 “App IDs”检查你的 App ID。</p>
<p><br class="spacer_" /></p>
<p>下面是一个唯一的App ID:</p>
<p>7DW89RZKLY.com.runmonster.runmonsterfree</p>
<p><br class="spacer_" /></p>
<p>下面<strong>不</strong>是一个唯一的 App ID:</p>
<p>7DW89RZKLY.com.runmonster.*</p>
<p><br class="spacer_" /></p>
<p>如果你还没有一个唯一的App ID，按如下步骤创建一个：</p>
<ol>
<li>在developer portal中的 App IDs 部分，选择“New App ID”</li>
<li>填写下列信息：
<ul>
<li><strong>Display name（显示名）</strong>: 选取一个不同的App ID的名称。你不能编辑或删除旧的App ID，所以你必须为你的App ID提供一个新名称以避免混淆。</li>
<li><strong>Prefix（前缀）</strong>: 生成一个新的前缀，或者如果你的程序是<a href="http://developer.apple.com/iphone/library/samplecode/GenericKeychain/index.html">通过Keychain Services API分享数据</a>的系列程序中之一的话，则选用已存在的前缀。<a href="http://developer.apple.com/iphone/library/samplecode/GenericKeychain/index.html"></a></li>
<li><strong>Suffix（后缀）</strong>: <em>com.companyname.appname</em> (这是通用格式 &#8211; 注意没有使用通配符）。</li>
</ul>
</li>
<li>按 “Save”</li>
<li>按 App ID旁的“Configure” 链接</li>
<li>选取 “Enable In App Purchase”选择框</li>
<li>按“Done”</li>
</ol>
</div>
<div>
<p><br class="spacer_" /></p>
<h3>2. 创建一个新的Provisioning Profile文件</h3>
<p><br class="spacer_" /></p>
<p>在创建了新的App ID后,你需要生成一个指向这个App ID的新provisioning profile。</p>
<p><br class="spacer_" /></p>
<p>下面就是令人痛苦的生成和安装新provisioning profile的详细步骤:</p>
<ol>
<li>在 iPhone Developer Portal中, 选择左边的Provisioning部分</li>
<li>确保你处于Development 标签下, 按下右上角的 “New Profile”</li>
<li>填入所需信息并指向你刚创建的唯一的App ID</li>
<li>如果你在Actions条目下看到 “Pending”，那么请按下“Development”标签标题进行刷新</li>
<li>点击 “Download” 下载新的profile文件</li>
<li>将profile文件拖入到Dock中Xcode图标上进行安装</li>
<li>如果你想在硬盘上保存provisioning  profile，那么你可以按如下步骤手工安装profile：
<ol style="list-style-type: decimal;">
<li>在Xcode中, 选择 Window > Organizer </li>
<li>选择左边 “Provisioning Profiles” 分类</li>
<li>Ctrl-按下profile > Reveal in Finder </li>
<li>将新profile拖入到 profile Finder 窗口</li>
</ol>
</li>
</ol>
</div>
<div>
<p><br class="spacer_" /></p>
<h3></h3>
<h3>3. 更新Xcode 设置</h3>
<p><br class="spacer_" /></p>
<p>在Xcode中安装了 profile 文件后，你需要对使用此provisiong profile的项目进行一些编辑工作：</p>
<ol>
<li>编辑项目 .plist 文件使其 Bundle ID 与  App ID 匹配。忽略ID开始部分的字母数字序列。例如，在Developer Portal中你的App ID为“7DW89RZKLY.com.runmonster.runmonsterfree”，那么在Bundle ID中你只需输入“com.runmonster.runmonsterfree” 。</li>
<li>编辑项目的 target 信息以使用新的provisioning profile:
<ol style="list-style-type: decimal;">
<li>选取 Project > Edit Active Target </li>
<li>选取顶部“Build” 标签</li>
<li>选取需要的 configuration (通常为 Debug) </li>
<li>在<strong>Code  Signing Identity</strong>中选择新的provisioning profile </li>
<li>在<strong>Code Signing Identity</strong>之下的行中(可能名为 <strong>Any iPhone OS Device</strong>)选择新的provisioning profile</li>
</ol>
</li>
</ol>
</div>
<div>
<p><br class="spacer_" /></p>
<h3>4. 添加你的应用程序</h3>
<p><br class="spacer_" /></p>
<p>如果你的程序已经发表到App Store了，那么可以略过此步骤。</p>
<p><br class="spacer_" /></p>
<p>在你将产品添加到 iTunes Connect之前，你必须添加此产品所需的程序。如果你的程序还没有100%完成也无需担心，你可以先提交具有部分数据的程序，最后再提交真实的程序。</p>
<p><br class="spacer_" /></p>
<p><em><strong>注意</strong>: 只有 SKU 和 version（版本）部分是以后不可修改的</em></p>
<ol>
<li>登录到 <a href="http://developer.apple.com/iphone">http://developer.apple.com/iphone</a></li>
<li>点击右边链接进入 iTunes Connect
<ul>
<li><em>注意：你<strong>必须</strong>先登录到developer.apple.com，否则会有不测发生（译者注：具体是什么不测我也不太清楚，胆大的请自己试一下）</em></li>
</ul>
</li>
<li>在 iTunes Connect主页点击 “Manage Your  Applications” </li>
<li>在右上角点击“Create New Application”</li>
<li>填写程序所需的一切信息。当要求程序二进制码时，请选择稍后上传选项。</li>
</ol>
</div>
<div>
<p><br class="spacer_" /></p>
<h3>5. 提交程序二进制码</h3>
<p><br class="spacer_" /></p>
<p>Apple的文档中没有任何地方提及详情，但它却是必须的步骤。要成功测IAP功能，你<strong>必须</strong>提交程序的二进制码。即使你的程序还没有100%完成，你仍然需要提交二进制码。然而，你也可以立即摈弃你的二进制码，使其不会进入审核阶段。</p>
<p><br class="spacer_" /></p>
<p>下面这些步骤非常关键，我可是因为少做了某些步骤而度过了一段非常痛苦的时间：</p>
<ol>
<li>生成App Store发布版程序
<ul>
<li>如果你不知怎么做，请在 iPhone Developer Portal 中点击左方的 Distribution标签，并选择  “Prepare App” 标签。然后，根据蓝色链接的指示：
<ul>
<li>获取iPhone发行许可证</li>
<li>创建并下载在App Store发行所需的iPhone Distribution Provisioning Profile</li>
<li>在Xcode中生成程序的发行版</li>
</ul>
</li>
</ul>
</li>
<li>在iTunes Connect中进入程序页</li>
<li>选择 “Upload Binary”</li>
<li>上传.zip压缩程序</li>
<li>如果你的程序还没有100%完成以进行审核，那么请点击iTunes Connect中你程序首页中的  “Reject Binary”链接。程序的状态应该更新为 “Developer Rejected”.</li>
</ol>
<p><br class="spacer_" /></p>
<p>不用担心，由于程序的状态是“Developer Rejected”，Apple是不会对其进行审核的。你可以在任何时候提交程序的新版本并使其状态为“Developer Rejected”，这不会对以后程序正式提交的等待时间有任何影响。</p>
<p><br class="spacer_" /></p>
<h3>6. 添加产品</h3>
<p><br class="spacer_" /></p>
<p>完成了以上所有步骤后，我们最终可以向iTunes Connect中添加产品了。</p>
<ol>
<li>确保登录到 <a href="http://developer.apple.com/iphone">http://developer.apple.com/iphone</a></li>
<li>进入 iTunes Connect 主页</li>
<li>点击 “Manage Your in App Purchases” 链接</li>
<li>点击 “Create New”</li>
<li>选择你的程序</li>
<li>填写下列产品信息：
<ul>
<li><strong>Reference Name（参考名称）</strong>: 产品的通用名称。比如，我使用的是  “Pro Upgrade”。此名称是不允许进行编辑的，它不会显示于App Store中。</li>
<li><strong>Product ID（产品ID）</strong>: 你产品的唯一id。通常格式是 <em>com.company.appname.product</em>，但它可以说任何形式。它并不要求以程序的App ID作为前缀。</li>
<li><strong>Type（类型）</strong>: 有三种选择
<ul>
<li><strong>Non-consumable（非消耗品）</strong>: 仅需付费一次 (例如你希望将出现从免费版升级为专业版）</li>
<li><strong>Consumable（消耗品）</strong>: 每次下载都需要付费</li>
<li><strong>Subscription（预订）</strong>: 循环反复</li>
</ul>
</li>
<li><strong>Price Tier（价格等级）</strong>: 产品价格。参见不同等级的价格列表。</li>
<li><strong>Cleared for Sale（等待销售）</strong>: 一定要选取此项，否则的话，测试时会发生非法产品ID的错误。</li>
<li><strong>Language to Add（增加的语言）</strong>:  选一项。下列两项将出现：
<ul>
<li><strong>Displayed Name（显示名称）</strong>: 用户看到的产品名称。比如我选择 “Upgrade to Pro”。</li>
<li><strong>Description（描述）</strong>: 对产品进行描述。此处输入的文本将与Displayed Name 及 Price 一起在你代码中提取 SKProduct时出现。</li>
</ul>
</li>
<li><strong>Screenshot（截屏）</strong>: 展示你产品的截屏。尽管屏幕上会显示“提交截屏会触发产品审核过程”之类的文字（个人拙见，这是非常糟糕的设计），你还是可以安全地提交截屏而不会使产品进入审核过程。存储后，选择“Submit  with app binary” （随程序二进制码一起提交）选项。是产品与程序二进制绑定在一起，所以在你最后正式提交100%完成的程序二进制码时，产品也会随之提交。</li>
</ul>
</li>
<li>点击 “Save”</li>
</ol>
</div>
<div>
<p><br class="spacer_" /></p>
<h3>7. 编写代码</h3>
<p><br class="spacer_" /></p>
<p>下面我们开始编写代码对刚加入到iTunes Connect中的产品信息进行提取。我访问产品数据，我们需要使用 StoreKit framework。</p>
<p><br class="spacer_" /></p>
<p><em>注意： StoreKit 无法在模拟器上工作。你必须在真机上进行测试。</em></p>
<ol>
<li>1.添加 StoreKit framework 到你的项目中。</li>
<li>2.添加SKProduct引用到你的 .h 文件中：</li>
</ol>
<p><br class="spacer_" /></p>
<div class="codecolorer-container objc mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br /></div></td><td><div class="objc codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #11740a; font-style: italic;">// InAppPurchaseManager.h</span><br />
<br />
<span style="color: #6e371a;">#import &lt;StoreKit/StoreKit.h&gt;</span><br />
<br />
<span style="color: #6e371a;">#define kInAppPurchaseManagerProductsFetchedNotification @&quot;kInAppPurchaseManagerProductsFetchedNotification&quot;</span><br />
<br />
<span style="color: #a61390;">@interface</span> InAppPurchaseManager <span style="color: #002200;">:</span> <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/"><span style="color: #400080;">NSObject</span></a> &lt;SKProductsRequestDelegate&gt;<br />
<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; SKProduct <span style="color: #002200;">*</span>proUpgradeProduct;<br />
&nbsp; &nbsp; SKProductsRequest <span style="color: #002200;">*</span>productsRequest;<br />
<span style="color: #002200;">&#125;</span></div></td></tr></tbody></table></div>
<p><br class="spacer_" /></p>
<p><em>注意: InAppPurchaseManager 是一个单例类，它处理程序中所有IAP任务。它是本文中的示例程序。</em></p>
<ol>
<li>3.产品请求，并在相应.m文件中实现代理协议：</li>
</ol>
<p><br class="spacer_" /></p>
<div class="codecolorer-container objc mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br />19<br />20<br />21<br />22<br />23<br />24<br />25<br />26<br />27<br />28<br />29<br />30<br />31<br />32<br />33<br />34<br />35<br />36<br />37<br /></div></td><td><div class="objc codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #11740a; font-style: italic;">// InAppPurchaseManager.m</span><br />
<br />
<span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>requestProUpgradeProductData<br />
<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/"><span style="color: #400080;">NSSet</span></a> <span style="color: #002200;">*</span>productIdentifiers <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/"><span style="color: #400080;">NSSet</span></a> setWithObject<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;com.runmonster.runmonsterfree.upgradetopro&quot;</span> <span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; productsRequest <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #002200;">&#91;</span>SKProductsRequest alloc<span style="color: #002200;">&#93;</span> initWithProductIdentifiers<span style="color: #002200;">:</span>productIdentifiers<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; productsRequest.delegate <span style="color: #002200;">=</span> self;<br />
&nbsp; &nbsp; <span style="color: #002200;">&#91;</span>productsRequest start<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <br />
&nbsp; &nbsp; <span style="color: #11740a; font-style: italic;">// we will release the request object in the delegate callback</span><br />
<span style="color: #002200;">&#125;</span><br />
<br />
<span style="color: #6e371a;">#pragma mark -</span><br />
<span style="color: #6e371a;">#pragma mark SKProductsRequestDelegate methods</span><br />
<br />
<span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>productsRequest<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span>SKProductsRequest <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>request didReceiveResponse<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span>SKProductsResponse <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>response<br />
<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color: #400080;">NSArray</span></a> <span style="color: #002200;">*</span>products <span style="color: #002200;">=</span> response.products;<br />
&nbsp; &nbsp; proUpgradeProduct <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>products count<span style="color: #002200;">&#93;</span> <span style="color: #002200;">==</span> 1 ? <span style="color: #002200;">&#91;</span><span style="color: #002200;">&#91;</span>products firstObject<span style="color: #002200;">&#93;</span> retain<span style="color: #002200;">&#93;</span> <span style="color: #002200;">:</span> <span style="color: #a61390;">nil</span>;<br />
&nbsp; &nbsp; <span style="color: #a61390;">if</span> <span style="color: #002200;">&#40;</span>proUpgradeProduct<span style="color: #002200;">&#41;</span><br />
&nbsp; &nbsp; <span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; NSLog<span style="color: #002200;">&#40;</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;Product title: %@&quot;</span> , proUpgradeProduct.localizedTitle<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; NSLog<span style="color: #002200;">&#40;</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;Product description: %@&quot;</span> , proUpgradeProduct.localizedDescription<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; NSLog<span style="color: #002200;">&#40;</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;Product price: %@&quot;</span> , proUpgradeProduct.price<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; NSLog<span style="color: #002200;">&#40;</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;Product id: %@&quot;</span> , proUpgradeProduct.productIdentifier<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; <span style="color: #002200;">&#125;</span><br />
&nbsp; &nbsp; <br />
&nbsp; &nbsp; <span style="color: #a61390;">for</span> <span style="color: #002200;">&#40;</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: #400080;">NSString</span></a> <span style="color: #002200;">*</span>invalidProductId <span style="color: #a61390;">in</span> response.invalidProductIdentifiers<span style="color: #002200;">&#41;</span><br />
&nbsp; &nbsp; <span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; NSLog<span style="color: #002200;">&#40;</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;Invalid product id: %@&quot;</span> , invalidProductId<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; <span style="color: #002200;">&#125;</span><br />
&nbsp; &nbsp; <br />
&nbsp; &nbsp; <span style="color: #11740a; font-style: italic;">// finally release the reqest we alloc/init’ed in requestProUpgradeProductData</span><br />
&nbsp; &nbsp; <span style="color: #002200;">&#91;</span>productsRequest release<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <br />
&nbsp; &nbsp; <span style="color: #002200;">&#91;</span><span style="color: #002200;">&#91;</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/"><span style="color: #400080;">NSNotificationCenter</span></a> defaultCenter<span style="color: #002200;">&#93;</span> postNotificationName<span style="color: #002200;">:</span>kInAppPurchaseManagerProductsFetchedNotification object<span style="color: #002200;">:</span>self userInfo<span style="color: #002200;">:</span><span style="color: #a61390;">nil</span><span style="color: #002200;">&#93;</span>;<br />
<span style="color: #002200;">&#125;</span></div></td></tr></tbody></table></div>
<p><br class="spacer_" /></p>
<p>上面代码有几点需要注意：</p>
<ul>
<li>指定产品id时，你必须使用完整产品id。例如，上例中使用 “com.runmonster.runmonsterfree.upgradetopro”。仅使用 “upgradetopro” 将不会正常工作。</li>
<li>如果在productsRequest:didReceiveResponse:中response.products 为 nil，而你的产品id出现于 response.invalidProductIdentifers 数组中时，那么请做好心理准备开始一场徒劳的搜索战吧。 StoreKit API没有提供任何帮助，也没有任何指示关于为什么你的id是无效的。很可爱，不是吗？</li>
<li>SKProduct类提供了有关程序标题和描述的本地化版本，但是价格则没有本地化版本。下面是针对此疏忽提供的代码：</li>
</ul>
<p><br class="spacer_" /></p>
<div class="codecolorer-container objc mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br /></div></td><td><div class="objc codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #11740a; font-style: italic;">// SKProduct+LocalizedPrice.h</span><br />
<br />
<span style="color: #6e371a;">#import &lt;Foundation/Foundation.h&gt;</span><br />
<span style="color: #6e371a;">#import &lt;StoreKit/StoreKit.h&gt;</span><br />
<br />
<span style="color: #a61390;">@interface</span> SKProduct <span style="color: #002200;">&#40;</span>LocalizedPrice<span style="color: #002200;">&#41;</span><br />
<br />
<span style="color: #a61390;">@property</span> <span style="color: #002200;">&#40;</span>nonatomic, readonly<span style="color: #002200;">&#41;</span> <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: #400080;">NSString</span></a> <span style="color: #002200;">*</span>localizedPrice;<br />
<br />
<span style="color: #a61390;">@end</span></div></td></tr></tbody></table></div>
<p><br class="spacer_" /></p>
<div class="codecolorer-container objc mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br /></div></td><td><div class="objc codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #11740a; font-style: italic;">// SKProduct+LocalizedPrice.m</span><br />
<br />
<span style="color: #6e371a;">#import &quot;SKProduct+LocalizedPrice.h&quot;</span><br />
<br />
<span style="color: #a61390;">@implementation</span> SKProduct <span style="color: #002200;">&#40;</span>LocalizedPrice<span style="color: #002200;">&#41;</span><br />
<br />
<span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: #400080;">NSString</span></a> <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>localizedPrice<br />
<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumberFormatter_Class/"><span style="color: #400080;">NSNumberFormatter</span></a> <span style="color: #002200;">*</span>numberFormatter <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #002200;">&#91;</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumberFormatter_Class/"><span style="color: #400080;">NSNumberFormatter</span></a> alloc<span style="color: #002200;">&#93;</span> init<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <span style="color: #002200;">&#91;</span>numberFormatter setFormatterBehavior<span style="color: #002200;">:</span>NSNumberFormatterBehavior10_4<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <span style="color: #002200;">&#91;</span>numberFormatter setNumberStyle<span style="color: #002200;">:</span>NSNumberFormatterCurrencyStyle<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <span style="color: #002200;">&#91;</span>numberFormatter setLocale<span style="color: #002200;">:</span>self.priceLocale<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: #400080;">NSString</span></a> <span style="color: #002200;">*</span>formattedString <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>numberFormatter stringFromNumber<span style="color: #002200;">:</span>self.price<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <span style="color: #002200;">&#91;</span>numberFormatter release<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <span style="color: #a61390;">return</span> formattedString;<br />
<span style="color: #002200;">&#125;</span><br />
<br />
<span style="color: #a61390;">@end</span></div></td></tr></tbody></table></div>
<p><br class="spacer_" /></p>
<p>加入上述代码，测试一下。你应该在控制台窗口中看见产品信息了。然而更大的可能是，你得到了一个无效的产品id。我下一篇<a href="无效产品id的处理方法">文章</a>将介绍怎样对这个问题进行调试。但是，下面的步骤8有可能是阻碍你前进的障碍。</p>
<p><br class="spacer_" /></p>
<h3>8. 等待几小时</h3>
<p><br class="spacer_" /></p>
<p>遵循了上述所有步骤，但是你的产品仍然是无效的？你是否两次，三次，四次不懈努力地确认你是否遵循了上面提到的每个步骤？你是否已经对网上IAP信息少得可怜而感到绝望？</p>
<p><br class="spacer_" /></p>
<p>那么，你应该等待。</p>
<p><br class="spacer_" /></p>
<p>你的产品要进入iTunes Connect使得Apple准备好沙箱环境需要一些时间。对于我而言，我是经过了无数次产品无效错误的绝望。而在24小时后，我没有修改任何一行代码，但产品id变为有效。我认为要使产品发布到Apple的网络系统需要几个小时的时间，但如果你有时间的话，你可以像我一样等上24个小时。</p>
<p><br class="spacer_" /></p>
<h2>购买产品</h2>
<p><br class="spacer_" /></p>
<p>至此你应该已经成功地获取了 SKProduct 描述。比较而言，支持购买产品相对简单些。仅需下面三个步骤：</p>
<ol>
<li>编写代码支持事务（transaction）</li>
<li>在iTunes Connect中添加程序测试用户</li>
<li>在设备中登录你的 iTunes Store 帐号</li>
<li>购买测试</li>
</ol>
<p><br class="spacer_" /></p>
<p>我们从编写支持事务所需代码开始。</p>
<p><br class="spacer_" /></p>
<h3>1. 编写代码支持事务</h3>
<p><br class="spacer_" /></p>
<p>首先注意：你将负责开发产品购买的用户界面。StoreKit 未提供任何与用户界面相关的元素。如果你希望你的购买用户界面与App Store一样，那么你要自己完成。</p>
<p><br class="spacer_" /></p>
<p>下面所有代码都是有关事务处理的后台部分。这是一个单独的类只有一条简单的API以供外部类（比如view controller）调用进行购买。如果你找到将其集成到你程序的购买部分的方法，那么我推荐你使用类似方案。</p>
<p><br class="spacer_" /></p>
<p>首先，需要遵循 SKPaymentTransactionObserver 协议:</p>
<p><br class="spacer_" /></p>
<div class="codecolorer-container objc mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br />19<br /></div></td><td><div class="objc codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #11740a; font-style: italic;">// InAppPurchaseManager.h</span><br />
<br />
<span style="color: #11740a; font-style: italic;">// add a couple notifications sent out when the transaction completes</span><br />
<span style="color: #6e371a;">#define kInAppPurchaseManagerTransactionFailedNotification @&quot;kInAppPurchaseManagerTransactionFailedNotification&quot;</span><br />
<span style="color: #6e371a;">#define kInAppPurchaseManagerTransactionSucceededNotification @&quot;kInAppPurchaseManagerTransactionSucceededNotification&quot;</span><br />
<br />
…<br />
<br />
<span style="color: #a61390;">@interface</span> InAppPurchaseManager <span style="color: #002200;">:</span> <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/"><span style="color: #400080;">NSObject</span></a> &lt;SKProductsRequestDelegate, SKPaymentTransactionObserver&gt;<br />
<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; …<br />
<span style="color: #002200;">&#125;</span><br />
<br />
<span style="color: #11740a; font-style: italic;">// public methods</span><br />
<span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>loadStore;<br />
<span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">BOOL</span><span style="color: #002200;">&#41;</span>canMakePurchases;<br />
<span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>purchaseProUpgrade;<br />
<br />
<span style="color: #a61390;">@end</span></div></td></tr></tbody></table></div>
<p><br class="spacer_" /></p>
<p>上面我们定义了两个新的notification，它们将作为购买事务的结果被发送。在上例中我们仍然使用与获取产品描述同一个InAppPurchaseManager类。</p>
<p><br class="spacer_" /></p>
<div class="codecolorer-container objc mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br />19<br />20<br />21<br />22<br />23<br />24<br />25<br />26<br />27<br />28<br />29<br />30<br />31<br />32<br />33<br />34<br />35<br />36<br />37<br />38<br />39<br />40<br />41<br />42<br />43<br />44<br />45<br />46<br />47<br />48<br />49<br />50<br />51<br />52<br />53<br />54<br />55<br />56<br />57<br />58<br />59<br />60<br />61<br />62<br />63<br />64<br />65<br />66<br />67<br />68<br />69<br />70<br />71<br />72<br />73<br />74<br />75<br />76<br />77<br />78<br />79<br />80<br />81<br />82<br />83<br />84<br />85<br />86<br />87<br />88<br />89<br />90<br />91<br />92<br />93<br />94<br />95<br />96<br />97<br />98<br />99<br />100<br />101<br />102<br />103<br />104<br />105<br />106<br />107<br />108<br />109<br />110<br />111<br />112<br />113<br />114<br />115<br />116<br />117<br />118<br />119<br />120<br />121<br />122<br />123<br />124<br />125<br />126<br />127<br />128<br />129<br />130<br />131<br />132<br />133<br />134<br />135<br />136<br />137<br />138<br />139<br />140<br />141<br />142<br />143<br />144<br />145<br />146<br />147<br />148<br />149<br />150<br />151<br /></div></td><td><div class="objc codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #11740a; font-style: italic;">// InAppPurchaseManager.m</span><br />
<br />
<span style="color: #6e371a;">#define kInAppPurchaseProUpgradeProductId @&quot;com.runmonster.runmonsterfree.upgradetopro&quot;</span><br />
<br />
…<br />
<br />
<span style="color: #6e371a;">#pragma -</span><br />
<span style="color: #6e371a;">#pragma Public methods</span><br />
<br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #11740a; font-style: italic;">// call this method once on startup</span><br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>loadStore<br />
<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; <span style="color: #11740a; font-style: italic;">// restarts any purchases if they were interrupted last time the app was open</span><br />
&nbsp; &nbsp; <span style="color: #002200;">&#91;</span><span style="color: #002200;">&#91;</span>SKPaymentQueue defaultQueue<span style="color: #002200;">&#93;</span> addTransactionObserver<span style="color: #002200;">:</span>self<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <br />
&nbsp; &nbsp; <span style="color: #11740a; font-style: italic;">// get the product description (defined in early sections)</span><br />
&nbsp; &nbsp; <span style="color: #002200;">&#91;</span>self requestProUpgradeProductData<span style="color: #002200;">&#93;</span>;<br />
<span style="color: #002200;">&#125;</span><br />
<br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #11740a; font-style: italic;">// call this before making a purchase</span><br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">BOOL</span><span style="color: #002200;">&#41;</span>canMakePurchases<br />
<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; <span style="color: #a61390;">return</span> <span style="color: #002200;">&#91;</span>SKPaymentQueue canMakePayments<span style="color: #002200;">&#93;</span>;<br />
<span style="color: #002200;">&#125;</span><br />
<br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #11740a; font-style: italic;">// kick off the upgrade transaction</span><br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>purchaseProUpgrade<br />
<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; SKPayment <span style="color: #002200;">*</span>payment <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>SKPayment paymentWithProductIdentifier<span style="color: #002200;">:</span>kInAppPurchaseProUpgradeProductId<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <span style="color: #002200;">&#91;</span><span style="color: #002200;">&#91;</span>SKPaymentQueue defaultQueue<span style="color: #002200;">&#93;</span> addPayment<span style="color: #002200;">:</span>payment<span style="color: #002200;">&#93;</span>;<br />
<span style="color: #002200;">&#125;</span><br />
<br />
<span style="color: #6e371a;">#pragma -</span><br />
<span style="color: #6e371a;">#pragma Purchase helpers</span><br />
<br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #11740a; font-style: italic;">// saves a record of the transaction by storing the receipt to disk</span><br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>recordTransaction<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span>SKPaymentTransaction <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>transaction<br />
<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; <span style="color: #a61390;">if</span> <span style="color: #002200;">&#40;</span><span style="color: #002200;">&#91;</span>transaction.payment.productIdentifier isEqualToString<span style="color: #002200;">:</span>kInAppPurchaseProUpgradeProductId<span style="color: #002200;">&#93;</span><span style="color: #002200;">&#41;</span><br />
&nbsp; &nbsp; <span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #11740a; font-style: italic;">// save the transaction receipt to disk</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span><span style="color: #002200;">&#91;</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/"><span style="color: #400080;">NSUserDefaults</span></a> standardUserDefaults<span style="color: #002200;">&#93;</span> setValue<span style="color: #002200;">:</span>transaction.transactionReceipt forKey<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;proUpgradeTransactionReceipt&quot;</span> <span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span><span style="color: #002200;">&#91;</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/"><span style="color: #400080;">NSUserDefaults</span></a> standardUserDefaults<span style="color: #002200;">&#93;</span> synchronize<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <span style="color: #002200;">&#125;</span><br />
<span style="color: #002200;">&#125;</span><br />
<br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #11740a; font-style: italic;">// enable pro features</span><br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>provideContent<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: #400080;">NSString</span></a> <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>productId<br />
<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; <span style="color: #a61390;">if</span> <span style="color: #002200;">&#40;</span><span style="color: #002200;">&#91;</span>productId isEqualToString<span style="color: #002200;">:</span>kInAppPurchaseProUpgradeProductId<span style="color: #002200;">&#93;</span><span style="color: #002200;">&#41;</span><br />
&nbsp; &nbsp; <span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #11740a; font-style: italic;">// enable the pro features</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span><span style="color: #002200;">&#91;</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/"><span style="color: #400080;">NSUserDefaults</span></a> standardUserDefaults<span style="color: #002200;">&#93;</span> setBool<span style="color: #002200;">:</span><span style="color: #a61390;">YES</span> forKey<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;isProUpgradePurchased&quot;</span> <span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span><span style="color: #002200;">&#91;</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/"><span style="color: #400080;">NSUserDefaults</span></a> standardUserDefaults<span style="color: #002200;">&#93;</span> synchronize<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <span style="color: #002200;">&#125;</span><br />
<span style="color: #002200;">&#125;</span><br />
<br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #11740a; font-style: italic;">// removes the transaction from the queue and posts a notification with the transaction result</span><br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>finishTransaction<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span>SKPaymentTransaction <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>transaction wasSuccessful<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span><span style="color: #a61390;">BOOL</span><span style="color: #002200;">&#41;</span>wasSuccessful<br />
<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; <span style="color: #11740a; font-style: italic;">// remove the transaction from the payment queue.</span><br />
&nbsp; &nbsp; <span style="color: #002200;">&#91;</span><span style="color: #002200;">&#91;</span>SKPaymentQueue defaultQueue<span style="color: #002200;">&#93;</span> finishTransaction<span style="color: #002200;">:</span>transaction<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <br />
&nbsp; &nbsp; <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color: #400080;">NSDictionary</span></a> <span style="color: #002200;">*</span>userInfo <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color: #400080;">NSDictionary</span></a> dictionaryWithObjectsAndKeys<span style="color: #002200;">:</span>transaction, <span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;transaction&quot;</span> , <span style="color: #a61390;">nil</span><span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <span style="color: #a61390;">if</span> <span style="color: #002200;">&#40;</span>wasSuccessful<span style="color: #002200;">&#41;</span><br />
&nbsp; &nbsp; <span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #11740a; font-style: italic;">// send out a notification that we’ve finished the transaction</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span><span style="color: #002200;">&#91;</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/"><span style="color: #400080;">NSNotificationCenter</span></a> defaultCenter<span style="color: #002200;">&#93;</span> postNotificationName<span style="color: #002200;">:</span>kInAppPurchaseManagerTransactionSucceededNotification object<span style="color: #002200;">:</span>self userInfo<span style="color: #002200;">:</span>userInfo<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <span style="color: #002200;">&#125;</span><br />
&nbsp; &nbsp; <span style="color: #a61390;">else</span><br />
&nbsp; &nbsp; <span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #11740a; font-style: italic;">// send out a notification for the failed transaction</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span><span style="color: #002200;">&#91;</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/"><span style="color: #400080;">NSNotificationCenter</span></a> defaultCenter<span style="color: #002200;">&#93;</span> postNotificationName<span style="color: #002200;">:</span>kInAppPurchaseManagerTransactionFailedNotification object<span style="color: #002200;">:</span>self userInfo<span style="color: #002200;">:</span>userInfo<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <span style="color: #002200;">&#125;</span><br />
<span style="color: #002200;">&#125;</span><br />
<br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #11740a; font-style: italic;">// called when the transaction was successful</span><br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>completeTransaction<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span>SKPaymentTransaction <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>transaction<br />
<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; <span style="color: #002200;">&#91;</span>self recordTransaction<span style="color: #002200;">:</span>transaction<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <span style="color: #002200;">&#91;</span>self provideContent<span style="color: #002200;">:</span>transaction.payment.productIdentifier<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <span style="color: #002200;">&#91;</span>self finishTransaction<span style="color: #002200;">:</span>transaction wasSuccessful<span style="color: #002200;">:</span><span style="color: #a61390;">YES</span><span style="color: #002200;">&#93;</span>;<br />
<span style="color: #002200;">&#125;</span><br />
<br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #11740a; font-style: italic;">// called when a transaction has been restored and and successfully completed</span><br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>restoreTransaction<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span>SKPaymentTransaction <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>transaction<br />
<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; <span style="color: #002200;">&#91;</span>self recordTransaction<span style="color: #002200;">:</span>transaction.originalTransaction<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <span style="color: #002200;">&#91;</span>self provideContent<span style="color: #002200;">:</span>transaction.originalTransaction.payment.productIdentifier<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <span style="color: #002200;">&#91;</span>self finishTransaction<span style="color: #002200;">:</span>transaction wasSuccessful<span style="color: #002200;">:</span><span style="color: #a61390;">YES</span><span style="color: #002200;">&#93;</span>;<br />
<span style="color: #002200;">&#125;</span><br />
<br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #11740a; font-style: italic;">// called when a transaction has failed</span><br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>failedTransaction<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span>SKPaymentTransaction <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>transaction<br />
<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; <span style="color: #a61390;">if</span> <span style="color: #002200;">&#40;</span>transaction.error.code <span style="color: #002200;">!=</span> SKErrorPaymentCancelled<span style="color: #002200;">&#41;</span><br />
&nbsp; &nbsp; <span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #11740a; font-style: italic;">// error!</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span>self finishTransaction<span style="color: #002200;">:</span>transaction wasSuccessful<span style="color: #002200;">:</span><span style="color: #a61390;">NO</span><span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <span style="color: #002200;">&#125;</span><br />
&nbsp; &nbsp; <span style="color: #a61390;">else</span><br />
&nbsp; &nbsp; <span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #11740a; font-style: italic;">// this is fine, the user just cancelled, so don’t notify</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span><span style="color: #002200;">&#91;</span>SKPaymentQueue defaultQueue<span style="color: #002200;">&#93;</span> finishTransaction<span style="color: #002200;">:</span>transaction<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; <span style="color: #002200;">&#125;</span><br />
<span style="color: #002200;">&#125;</span><br />
<br />
<span style="color: #6e371a;">#pragma mark -</span><br />
<span style="color: #6e371a;">#pragma mark SKPaymentTransactionObserver methods</span><br />
<br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #11740a; font-style: italic;">// called when the transaction status is updated</span><br />
<span style="color: #11740a; font-style: italic;">//</span><br />
<span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>paymentQueue<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span>SKPaymentQueue <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>queue updatedTransactions<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color: #400080;">NSArray</span></a> <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>transactions<br />
<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; <span style="color: #a61390;">for</span> <span style="color: #002200;">&#40;</span>SKPaymentTransaction <span style="color: #002200;">*</span>transaction <span style="color: #a61390;">in</span> transactions<span style="color: #002200;">&#41;</span><br />
&nbsp; &nbsp; <span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #a61390;">switch</span> <span style="color: #002200;">&#40;</span>transaction.transactionState<span style="color: #002200;">&#41;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #a61390;">case</span> SKPaymentTransactionStatePurchased<span style="color: #002200;">:</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span>self completeTransaction<span style="color: #002200;">:</span>transaction<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #a61390;">break</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #a61390;">case</span> SKPaymentTransactionStateFailed<span style="color: #002200;">:</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span>self failedTransaction<span style="color: #002200;">:</span>transaction<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #a61390;">break</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #a61390;">case</span> SKPaymentTransactionStateRestored<span style="color: #002200;">:</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span>self restoreTransaction<span style="color: #002200;">:</span>transaction<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #a61390;">break</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #a61390;">default</span><span style="color: #002200;">:</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #a61390;">break</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#125;</span><br />
&nbsp; &nbsp; <span style="color: #002200;">&#125;</span><br />
<span style="color: #002200;">&#125;</span></div></td></tr></tbody></table></div>
<p><br class="spacer_" /></p>
<p>要测试上面的新代码，你还需要编写调用 loadStore, canMakePurchases 以及 purchaseProUpgrade 方法的代码。</p>
<p><br class="spacer_" /></p>
<p>有关上述代码的详细解释，请参考官方 <a href="http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/StoreKitGuide/AddingaStoretoYourApplication/AddingaStoretoYourApplication.html#//apple_ref/doc/uid/TP40008267-CH101-SW1">In  App Purchase Programming Guide （IAP编程指南）</a></p>
<p><br class="spacer_" /></p>
<p>上述代码有几个部分是针对我的程序的。例如，在 provideContent:中，NSUserDefaults 中的@&#8221;isProUpgradePurchased&#8221; BOOL 字段被设定为 YES。程序的其他部分将检查此BOOL值以确定是否需要启动专业版功能。如果你正好也要实现免费升级专业版的功能，那么你可以使用同样的方法。</p>
<p><br class="spacer_" /></p>
<h3>2. 添加测试用户</h3>
<p><br class="spacer_" /></p>
<p>为测试上述代码，你需要在 iTunes Connect 中创建测试用户以对IAP功能进行测试。你可以使用测试帐号购买产品而不被Apple收取费用。</p>
<p>按以下步骤创建测试用户：</p>
<ol>
<li>登录到 <a href="http://developer.apple.com/iphone">http://developer.apple.com/iphone</a></li>
<li>进入 iTunes Connect</li>
<li>选择iTunes Connect首页中的 “Manage Users”</li>
<li>选择 “In App Purchase Test User”</li>
<li>选择 “Add New User”</li>
<li>填入用户信息. 所有信息都不必是合法的。建议使用虚假简短的email地址及简短的密码。</li>
<li>选择 “Save”</li>
</ol>
<p><br class="spacer_" /></p>
<p>测试时你需要输入这些email地址和密码。</p>
<p><br class="spacer_" /></p>
<h3>3. 在你的设备中退出登录</h3>
<p><br class="spacer_" /></p>
<p>在进行程序购买功能测试前，你必须在你的设备中退出iTunes Store。遵循以下步骤：</p>
<ol>
<li>打开Settings App</li>
<li>点击 “Store” 行</li>
<li>点击 “Sign Out”</li>
</ol>
</div>
<div>
<p><br class="spacer_" /></p>
<h3>4. 购买测试</h3>
<p><br class="spacer_" /></p>
<p>现在，终于可以开始进行IAP功能的测试了。测试很简单：</p>
<ol>
<li>运行你设备中的程序</li>
<li>进行购买</li>
<li>当程序提示输入用户名和密码时，输入参数用户的信息</li>
</ol>
<p><br class="spacer_" /></p>
<p>如果你使用同一账户进行购买时，系统将提示你已经购买了此产品。按“Yes”就可以再次下载此产品。</p>
<p><br class="spacer_" /></p>
<h2>总结</h2>
<p><br class="spacer_" /></p>
<p>实现IAP功能比想象的要复杂许多。我可是经过无数痛苦的经历才完成我的程序。希望能够帮助其他开发者减轻他们的痛苦。</p>
<p><br class="spacer_" /></p>
<p>原文见：<a title="In App Purchases: A Full Walkthrough" rel="bookmark" href="http://troybrant.net/blog/2010/01/in-app-purchases-a-full-walkthrough/">In  App  Purchases: A Full Walkthrough</a></p>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/iap%ef%bc%88%e7%a8%8b%e5%ba%8f%e5%86%85%e8%b4%ad%e4%b9%b0%ef%bc%89-%e5%ae%8c%e5%85%a8%e6%94%bb%e7%95%a5/feed</wfw:commentRss>
		<slash:comments>23</slash:comments>
		</item>
		<item>
		<title>调试教程 – 使用UIRecorder Instrument将测试自动化</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e8%b0%83%e8%af%95%e6%95%99%e7%a8%8b-%e2%80%93-%e4%bd%bf%e7%94%a8uirecorder-instrument%e5%b0%86%e6%b5%8b%e8%af%95%e8%87%aa%e5%8a%a8%e5%8c%96</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e8%b0%83%e8%af%95%e6%95%99%e7%a8%8b-%e2%80%93-%e4%bd%bf%e7%94%a8uirecorder-instrument%e5%b0%86%e6%b5%8b%e8%af%95%e8%87%aa%e5%8a%a8%e5%8c%96#comments</comments>
		<pubDate>Mon, 19 Jul 2010 01:52:31 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[基础]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[调试]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=970</guid>
		<description><![CDATA[（注：这是我以前收集的一篇文章，找不到出处了，稍作修改在此发布）

如果你发现程序的bug，但每次进行测试都需要许多步骤才能重现问题，那么本教程适合你。通常，测试和调试是非常繁琐的事情，iPhone程序的开发尤其如此。

比如说，你有个程序需要经过5个步骤才能到达另一个view。如果你有一个bug发生在这个需要5步之多的view上，通常情况下调试的步骤如下：

运行程序
触击 view 1
触击view 2
触击 view 3
触击 view 4
触击 view 5
(程序崩溃)
修改代码
重复以上步骤


如你所见，这是多么痛苦的事情啊。 Kendall Gelner 在 360iDev 上有关使用Instruments 和 XCode进行调试的若干提示的演讲中，对我而言最为有用的就是将测试自动化。我演示一下应该怎么做:
1. 打开希望测试/调试的程序
2. 在模拟器中运行
3. 打开Instrument – 位于 /Developer/Applications/Instruments
 
 



 
4. 选择 UIRecorder 并按Choose - 你将看到下面窗口


 
5.  然后需要将此工具与iPhone模拟器进程链接。按下下拉菜单选择 Default Target -&#62; Attach to Process -&#62; iPhone Simulator.


6. 再按下 Drive &#38; Record 并在模拟器中完成测试所需的所有步骤。至此，UI Recorder记录下了你的一举一动。完成时，按下Stop按钮。 注意： 在完成记录后请不要移动模拟器，因为这有可能会使弄乱整个处理过程。
7. 修改你的代码…
8. 按下 Drive &#38; Record [...]]]></description>
			<content:encoded><![CDATA[<p>（注：这是我以前收集的一篇文章，找不到出处了，稍作修改在此发布）</p>
<p><br class="spacer_" /></p>
<p>如果你发现程序的bug，但每次进行测试都需要许多步骤才能重现问题，那么本教程适合你。通常，测试和调试是非常繁琐的事情，iPhone程序的开发尤其如此。</p>
<p><span id="more-970"></span><br class="spacer_" /></p>
<p>比如说，你有个程序需要经过5个步骤才能到达另一个view。如果你有一个bug发生在这个需要5步之多的view上，通常情况下调试的步骤如下：</p>
<ul>
<li>运行程序</li>
<li>触击 view 1</li>
<li>触击view 2</li>
<li>触击 view 3</li>
<li>触击 view 4</li>
<li>触击 view 5</li>
<li>(程序崩溃)</li>
<li>修改代码</li>
<li>重复以上步骤</li>
</ul>
<p><br class="spacer_" /></p>
<p>如你所见，这是多么痛苦的事情啊。 <a href="http://twitter.com/kendalldevdiary">Kendall Gelner</a> 在 <a href="http://www.360idev.com/">360iDev</a> 上有关使用Instruments 和 XCode进行调试的若干提示的演讲中，对我而言最为有用的就是将测试自动化。我演示一下应该怎么做:</p>
<p style="padding-left: 30px;">1. 打开希望测试/调试的程序</p>
<p style="padding-left: 30px;">2. 在模拟器中运行</p>
<p style="padding-left: 30px;">3. 打开<strong>Instrument</strong> – 位于 /Developer/<a id="KonaLink2" style="position: static; text-decoration: underline !important;" onclick="adlinkMouseClick(event,this,2);" onmouseover="adlinkMouseOver(event,this,2);" onmouseout="adlinkMouseOut(event,this,2);" href="http://www.iphone-geek.cn/wp-admin/#" target="_new"><span style="position: static; color: #346200 !important; font-weight: 400;"><span style="position: static; font-family: Helvetica, Arial, sans-serif; color: #346200 !important; font-weight: 400;">Applications</span></span></a>/Instruments</p>
<ol> </ol>
<ol> </ol>
<p><br class="spacer_" /></p>
<p style="text-align: center;"><strong><a href="http://icodeblog.com/wp-content/uploads/2009/10/screenshot_01.png"><img class="aligncenter" style="zoom: 1; display: inline;" title="screenshot_01" src="http://icodeblog.com/wp-content/uploads/2009/10/screenshot_01-300x229.png" alt="screenshot_01" width="300" height="229" /></a></strong></p>
<p style="text-align: center;"><strong><br />
 </strong></p>
<p style="padding-left: 30px;">4. 选择 <strong>UIRecorder</strong> 并按<strong>Choose </strong>- 你将看到下面窗口</p>
<p style="text-align: center;"><strong><a href="http://icodeblog.com/wp-content/uploads/2009/10/screenshot_02.png"><img class="aligncenter" style="zoom: 1; display: inline;" title="screenshot_02" src="http://icodeblog.com/wp-content/uploads/2009/10/screenshot_02-300x211.png" alt="screenshot_02" width="300" height="211" /></a></strong></p>
<p style="text-align: center;"><strong><br />
 </strong></p>
<p style="padding-left: 30px;">5.  然后需要将此工具与iPhone模拟器进程链接。按下下拉菜单选择 <strong>Default Target</strong> -&gt; <strong>Attach to Process </strong>-&gt; <strong>iPhone Simulator</strong>.</p>
<p style="text-align: center;"><a href="http://icodeblog.com/wp-content/uploads/2009/10/ss_03.png"><img class="aligncenter" style="zoom: 1; display: inline;" title="ss_03" src="http://icodeblog.com/wp-content/uploads/2009/10/ss_03-300x136.png" alt="ss_03" width="300" height="136" /></a></p>
<p><br class="spacer_" /></p>
<p style="padding-left: 30px;">6. 再按下 <strong>Drive &amp; Record</strong> 并在模拟器中完成测试所需的所有步骤。至此，UI Recorder记录下了你的一举一动。完成时，按下<strong>Stop</strong>按钮。 <span style="color: #ff0000;"><strong>注意：</strong></span> 在完成记录后请不要移动模拟器，因为这有可能会使弄乱整个处理过程。</p>
<p style="padding-left: 30px;">7. 修改你的代码…</p>
<p style="padding-left: 30px;">8. 按下 <strong>Drive &amp; Record </strong>按钮亲眼目睹一下有什么神奇的事情发生吧。测试自动进行了！你应该看到它自动模拟了你刚才进行的所有动作。</p>
<p><br class="spacer_" /></p>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e8%b0%83%e8%af%95%e6%95%99%e7%a8%8b-%e2%80%93-%e4%bd%bf%e7%94%a8uirecorder-instrument%e5%b0%86%e6%b5%8b%e8%af%95%e8%87%aa%e5%8a%a8%e5%8c%96/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>创建离线iPhone Web App</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e5%88%9b%e5%bb%ba%e7%a6%bb%e7%ba%bfiphone-web-app</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e5%88%9b%e5%bb%ba%e7%a6%bb%e7%ba%bfiphone-web-app#comments</comments>
		<pubDate>Tue, 18 May 2010 05:09:21 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[编程]]></category>
		<category><![CDATA[网络]]></category>
		<category><![CDATA[Web App]]></category>
		<category><![CDATA[教程]]></category>
		<category><![CDATA[浏览器]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=952</guid>
		<description><![CDATA[创建Web App的好处很多，比如：


可以运行在任何平台上 （当然要考虑浏览器的兼容性和屏幕大小问题） 
不需要学习iPhone编程语言 
最大的好处是不需要经过App Store批准 


随着Internet技术的迅速发展，使得Web App越来越强大，从简单的应用甚至到游戏，都可能通过Web App实现。但是，它要求随时连接到Internet，大大降低了Web App的实用性。随着Safari对HTML 5的支持，使得Web App的离线运行成为可能。这意味着你可以在没有Internet连接的情况下运行你的应用程序。

下面的例子改编自How to Make an HTML5 iPhone App，它介绍了创建一个离线“积木”游戏的全过程，我加上了创建用户自定义的桌面图标和启动画面的方法，使它看上去完全像一个真正的iPhone应用程序，而这是完全不需要通过Apple的App Store批准的。


基本要求

网页编程技术：HTML(5)， CSS 以及 JavaScript。

准备工作

由于我们要利用HTML5的离线缓存技术，我们需要访问网页服务器以便对文件的HTTP Headers进行修改（稍后我们会详细讨论）。

Apache服务器很容易通过修改 .htaccess 文件完成你需要的工作。这里是一篇教程关于 m使用htaccess修改HTTP header。

然后你需要打开Safari浏览器的调试窗以帮助你进行调试。在Settings > Safari > Developer中打开调试终端，它能够帮助你发现一些潜在的Javascript错误。记住在完成调试后，关闭调试窗。



应用程序缓存

HTML5 有关离线的标准参考这里。

应用程序缓存允许浏览器预先确定网页所需的所有文件，然后它将保存这些文件。格式是绝对地址如http://yourwebserver.com/picture.png 或者相对地址(/picture.png)，浏览器将离线保存这些文件。

你还可以列出不需要缓存的URL。最主要的部分是清单文件（需要离线缓存文件的清单）在Http Header中的filetype必须设置为text/manifest。


屏幕尺寸

在设计iPhone Web App时必须注意，当处于app模式时，屏幕尺寸为 320px X 460px，而在web模式时为 320px X 356px。这也许会影响你的用户界面设计。




代码

12345678910111213141516&#60;!DOCTYPE html&#62;
&#60;html manifest=&#34;tetris.manifest&#34;&#62;
&#60;head&#62;
&#160; &#160; &#60;meta name=&#34;viewport&#34; content=&#34;user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0&#34;/&#62;
&#160; &#160; &#60;meta name=&#34;apple-mobile-web-app-capable&#34; content=&#34;yes&#34; [...]]]></description>
			<content:encoded><![CDATA[<p>创建Web App的好处很多，比如：</p>
<p><br class="spacer_" /></p>
<ul>
<li>可以运行在任何平台上 （当然要考虑浏览器的兼容性和屏幕大小问题） </li>
<li>不需要学习iPhone编程语言 </li>
<li>最大的好处是不需要经过App Store批准 </li>
</ul>
<p><br class="spacer_" /></p>
<p>随着Internet技术的迅速发展，使得Web App越来越强大，从简单的应用甚至到游戏，都可能通过Web App实现。但是，它要求随时连接到Internet，大大降低了Web App的实用性。随着Safari对HTML 5的支持，使得Web App的离线运行成为可能。这意味着你可以在没有Internet连接的情况下运行你的应用程序。</p>
<p><br class="spacer_" /></p>
<p>下面的例子改编自<a href="http://sixrevisions.com/web-development/html5-iphone-app/">How to Make an HTML5 iPhone App</a>，它介绍了创建一个离线“积木”游戏的全过程，我加上了创建用户自定义的桌面图标和启动画面的方法，使它看上去完全像一个真正的iPhone应用程序，而这是完全不需要通过Apple的App Store批准的。</p>
<p><span id="more-952"></span></p>
<p><br class="spacer_" /></p>
<h3>基本要求</h3>
<p><br class="spacer_" /></p>
<p>网页编程技术：HTML(5)， CSS 以及 JavaScript。</p>
<p><br class="spacer_" /></p>
<h3>准备工作</h3>
<p><br class="spacer_" /></p>
<p>由于我们要利用HTML5的离线缓存技术，我们需要访问网页服务器以便对文件的<a href="http://en.wikipedia.org/wiki/List_of_HTTP_headers">HTTP Headers</a>进行修改（稍后我们会详细讨论）。</p>
<p><br class="spacer_" /></p>
<p>Apache服务器很容易通过修改 .htaccess 文件完成你需要的工作。这里是一篇教程关于 <a href="http://www.askapache.com/htaccess/using-http-headers-with-htaccess.html">m使用htaccess修改HTTP header</a>。</p>
<p><br class="spacer_" /></p>
<p>然后你需要打开Safari浏览器的调试窗以帮助你进行调试。在Settings > Safari > Developer中打开调试终端，它能够帮助你发现一些潜在的Javascript错误。记住在完成调试后，关闭调试窗。</p>
<p><br class="spacer_" /></p>
<p><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" src="http://www.iphone-geek.cn/wp-content/uploads/2010/05/image3.png" border="0" alt="image" width="324" height="484" /></p>
<p><br class="spacer_" /></p>
<h3>应用程序缓存</h3>
<p><br class="spacer_" /></p>
<p>HTML5 有关离线的标准参考这里。</p>
<p><br class="spacer_" /></p>
<p>应用程序缓存允许浏览器预先确定网页所需的所有文件，然后它将保存这些文件。格式是绝对地址如<a href="http://yourwebserver.com/picture.png">http://yourwebserver.com/picture.png</a> 或者相对地址(/picture.png)，浏览器将离线保存这些文件。</p>
<p><br class="spacer_" /></p>
<p>你还可以列出不需要缓存的URL。最主要的部分是清单文件（需要离线缓存文件的清单）在Http Header中的filetype必须设置为text/manifest。</p>
<p><br class="spacer_" /></p>
<p><br class="spacer_" /></p>
<h3>屏幕尺寸</h3>
<p><br class="spacer_" /></p>
<p>在设计iPhone Web App时必须注意，当处于app模式时，屏幕尺寸为 320px X 460px，而在web模式时为 320px X 356px。这也许会影响你的用户界面设计。</p>
<p><br class="spacer_" /></p>
<p><a href="http://www.iphone-geek.cn/wp-content/uploads/2010/05/image4.png"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" src="http://www.iphone-geek.cn/wp-content/uploads/2010/05/image_thumb.png" border="0" alt="image" width="554" height="417" /></a></p>
<p><br class="spacer_" /></p>
<h3></h3>
<h3>代码</h3>
<p><br class="spacer_" /></p>
<div class="codecolorer-container html4strict mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br /></div></td><td><div class="html4strict codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #00bbdd;">&lt;!DOCTYPE html&gt;</span><br />
<span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/html.html"><span style="color: #000000; font-weight: bold;">html</span></a> manifest<span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;tetris.manifest&quot;</span>&gt;</span><br />
<span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/head.html"><span style="color: #000000; font-weight: bold;">head</span></a>&gt;</span><br />
&nbsp; &nbsp; <span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/meta.html"><span style="color: #000000; font-weight: bold;">meta</span></a> <span style="color: #000066;">name</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;viewport&quot;</span> <span style="color: #000066;">content</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0&quot;</span><span style="color: #66cc66;">/</span>&gt;</span><br />
&nbsp; &nbsp; <span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/meta.html"><span style="color: #000000; font-weight: bold;">meta</span></a> <span style="color: #000066;">name</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;apple-mobile-web-app-capable&quot;</span> <span style="color: #000066;">content</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;yes&quot;</span> <span style="color: #66cc66;">/</span>&gt;</span><br />
&nbsp; &nbsp; <span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/meta.html"><span style="color: #000000; font-weight: bold;">meta</span></a> <span style="color: #000066;">name</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;apple-mobile-web-app-status-bar-style&quot;</span> <span style="color: #000066;">content</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;black&quot;</span> <span style="color: #66cc66;">/</span>&gt;</span><br />
&nbsp; &nbsp; <span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/link.html"><span style="color: #000000; font-weight: bold;">link</span></a> <span style="color: #000066;">rel</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;apple-touch-icon&quot;</span> <span style="color: #000066;">href</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;iphon_tetris_icon.png&quot;</span><span style="color: #66cc66;">/</span>&gt;</span><br />
&nbsp; &nbsp; <span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/link.html"><span style="color: #000000; font-weight: bold;">link</span></a> <span style="color: #000066;">rel</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;apple-touch-startup-image&quot;</span> <span style="color: #000066;">href</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;startup.png&quot;</span> <span style="color: #66cc66;">/</span>&gt;</span><br />
&nbsp; &nbsp; <span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/link.html"><span style="color: #000000; font-weight: bold;">link</span></a> <span style="color: #000066;">rel</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;stylesheet&quot;</span> <span style="color: #000066;">href</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;tetris.css&quot;</span> <span style="color: #000066;">type</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;text/css&quot;</span> <span style="color: #000066;">media</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;screen, mobile&quot;</span> <span style="color: #000066;">title</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;main&quot;</span> <span style="color: #000066;">charset</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;utf-8&quot;</span>&gt;</span><br />
&nbsp; &nbsp; <span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/title.html"><span style="color: #000000; font-weight: bold;">title</span></a>&gt;</span>offline Tetris<span style="color: #009900;">&lt;<span style="color: #66cc66;">/</span><a href="http://december.com/html/4/element/title.html"><span style="color: #000000; font-weight: bold;">title</span></a>&gt;</span><br />
<span style="color: #009900;">&lt;<span style="color: #66cc66;">/</span><a href="http://december.com/html/4/element/head.html"><span style="color: #000000; font-weight: bold;">head</span></a>&gt;</span><br />
<span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/body.html"><span style="color: #000000; font-weight: bold;">body</span></a>&gt;</span><br />
&nbsp; &nbsp;<span style="color: #808080; font-style: italic;">&lt;!-- Put your Markup Here --&gt;</span><br />
&nbsp; &nbsp;<span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/script.html"><span style="color: #000000; font-weight: bold;">script</span></a> <span style="color: #000066;">type</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;text/javascript&quot;</span> <span style="color: #000066;">src</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;tetris.js&quot;</span>&gt;&lt;<span style="color: #66cc66;">/</span><a href="http://december.com/html/4/element/script.html"><span style="color: #000000; font-weight: bold;">script</span></a>&gt;</span><br />
<span style="color: #009900;">&lt;<span style="color: #66cc66;">/</span><a href="http://december.com/html/4/element/body.html"><span style="color: #000000; font-weight: bold;">body</span></a>&gt;</span><br />
<span style="color: #009900;">&lt;<span style="color: #66cc66;">/</span><a href="http://december.com/html/4/element/html.html"><span style="color: #000000; font-weight: bold;">html</span></a>&gt;</span></div></td></tr></tbody></table></div>
<p><html>中的 manifest=&#8221;cache.manifest&#8221; 属性将告诉浏览器我们可能需要对文件进行离线缓存。</p>
<p><br class="spacer_" /></p>
<p>下面是Apple专用的HTML 5属性：</p>
<ul>
<li>
<div class="codecolorer-container text mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">apple-mobile-web-app-capable</div></td></tr></tbody></table></div>
<p>: 设置为离线模式 </li>
<li>
<div class="codecolorer-container text mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">apple-mobile-web-app-status-bar-style</div></td></tr></tbody></table></div>
<p>: 当处于离线时隐藏状态条，导航条 </li>
<li>
<div class="codecolorer-container text mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">apple-touch-icon</div></td></tr></tbody></table></div>
<p>:指向作为图标的图像 </li>
<li>
<div class="codecolorer-container text mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">apple-touch-startup-image</div></td></tr></tbody></table></div>
<p>: 指向作为启动图像的url</li>
</ul>
<p><br class="spacer_" /></p>
<h3>CSS</h3>
<p><br class="spacer_" /></p>
<p>这几乎与普通网页完全一样。还有一些webkit专用的CSS能实现一些特殊效果如动画等。</p>
<p><br class="spacer_" /></p>
<p>我们使用的CSS如下：</p>
<pre>body {
    overflow:hidden;
    background: #d7d7d7;
    margin:0;
    padding:0;
}
#tetris {
    width: 320px;
    height: 460px;
    background:#000;
}</pre>
<h3>JavaScript</h3>
<p><br class="spacer_" /></p>
<p>我使用的是 <a href="http://www.daltonridenhour.com">Dalton Ridenhour</a> 的Javascript，源代码在 <a href="http://github.com/daltonridenhour/DOM-Tetris">Github</a>。原JS是为普通网页设计的，我们仅需要修改一下拿掉对键盘的支持。</p>
<p><br class="spacer_" /></p>
<p>通常JS在iPhone上完全能正常工作，当然有一些例外。请参阅 <a href="http://www.quirksmode.org/blog/archives/2008/08/iphone_events.html">events on the iPhone</a> 。</p>
<p><br class="spacer_" /></p>
<p>下面是完成的网页，你可以进行测试，看看它是怎样离线运行的：</p>
<p><br class="spacer_" /></p>
<p><a href="http://tetris.alexkessinger.net">http://tetris.alexkessinger.net</a></p>
<p><br class="spacer_" /></p>
<h3>设定图标和启动画面</h3>
<p><br class="spacer_" /></p>
<p>使用iPhone Safari浏览器，很容易将应用程序的图标添加到你iPhone上的Home Screen。</p>
<p><br class="spacer_" /></p>
<ul>
<li>首先，创建一个57&#215;57的png图标。将其放在网站的文档根目录下（注意不是服务器的文档根目录）</li>
</ul>
<p><br class="spacer_" /></p>
<p><a href="http://www.iphone-geek.cn/wp-content/uploads/2010/05/image5.png"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" src="http://www.iphone-geek.cn/wp-content/uploads/2010/05/image_thumb1.png" border="0" alt="image" width="61" height="61" /></a></p>
<p><br class="spacer_" /></p>
<ul>
<li>在html中加入（见上面HTML代码部分） ，指向图标文件。</li>
<li>在html中加入（见上面HTML代码部分） ，指向启动画面图像文件。</li>
<li>打开浏览器，在地址栏输入你Web App的URL，按下浏览器下方的加号，选择“Add To Home Screen”,然后输入Web App的名称，比如Tetris。</li>
</ul>
<p><br class="spacer_" /></p>
<p>这样一个积木游戏的图标就添加到了你的iPhone屏幕上，启动后即使你的iPhone没有在线，也可以正常运行。</p>
<p><br class="spacer_" /></p>
<h3>我的设想</h3>
<p><br class="spacer_" /></p>
<p>这样的Web App看上去与普通程序没什么大区别，不过却需要用户自己添加图标，这虽然不是什么大问题，但还不是很完美。我想要是在你的网页上添加一项“安装到iPhone/iPod Touch/iPad”，在按下后可以自动添加图标，那么一切就完美了。我现在还没有解决方法，大家有什么建议吗？</p>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e5%88%9b%e5%bb%ba%e7%a6%bb%e7%ba%bfiphone-web-app/feed</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>iPad 编程教程 &#8211; Hello World++</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/ipad-%e7%bc%96%e7%a8%8b%e6%95%99%e7%a8%8b-hello-world</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/ipad-%e7%bc%96%e7%a8%8b%e6%95%99%e7%a8%8b-hello-world#comments</comments>
		<pubDate>Wed, 07 Apr 2010 05:07:44 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[用户界面]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[iPad]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=923</guid>
		<description><![CDATA[概述

随着iPad的发布，我想很多人开始绞尽脑汁试图在新一轮的“淘金热”中抢占一块市场。iCodeBlog将推出一系列iPad教程帮助大家。

由于iPad使用iPhone同样的SDK，所有代码看上去完全一样。实际上，看看最新和修改了的API类，你就会发现大部分是有关用户界面的。这是一个好消息，因为我们都对iPhone编程有了一定的经验。

此教程被称为“Hello World”，实际上它远远不止这些。首先，我假定你们已经具有iPhone/Objective-C的编程经验。


我们要做什么

今天的教程，我将展示怎样创建一个iPad项目，使用UISplitViewController显示两个单独的窗格。我还将涉及一些新的设计/UI模式并介绍iPad编程。

此项目将基于使用UITableView显示水果列表教程 。我们将扩展其中一个范例，如下所示：



本文将使用UISplitViewController在左窗格显示一个UITableView，而在右窗格显示一个具有UIImageView子视窗的UIView。实际上创建这样一个项目非常简单，因为项目模板提供了大部分初始代码。
开始


从http://developer.apple.com/iphone/下载SDK 3.2。iPad模拟器包括在了SDK中。 
下载本项目所需的资源文件 iPadHelloWorldResources.zip 。 

创建项目

创建iPad项目与iPhone项目没有分别。打开XCode并选择File-&#62;New Project，你将发现有一个新的Split View-Based Application选项。选择它并命名为iPadHelloWorld。

这将创建一个UITableView位于左窗格，UIView位于右窗格的应用程序。它还甚至提供了一些示例数据。下列文件将被加入到项目中。



各文件的简单介绍如下：


iPadHelloWorldAppDelegate – 类似于 app delegate。在 application:didFinishLaunchingWithOptions 方法中，创建了具有MasterViewController和 DetailViewController的UISplitViewController。 
MasterViewController – UITableViewController。它将处理左方视窗。 
DetailViewController – 它将处理右方视窗。当用户选择左方列表中不同行时，我们将在此视窗中做相应更新。 


按“Build and Run”测试一下。

注意：当你运行此程序时，你只能看到主视窗，这是因为模拟器运行于垂直模式。选取 “Hardware -&#62; Rotate Left/Right”将使模拟器旋转。你还可以通过按下键盘上的CMD-&#62;Arrow Left/Right 键获得同样效果。

 

导入项目图像

下一步你需要导入项目需要的图像。将下载的资源文件解压，并拖入称为“Resources-iPad”的文件夹下。
XCode 将提示你复制这些文件，选择“Yes”并按下OK。



请包括四个图像文件以及fruits.plist。
显示水果列表

显示水果列表与显示其他UITableView中的数据并无区别。打开MasterViewController.h 加入水果数组定义。
#import   

@class DetailViewController;   

@interface MasterViewController : UITableViewController {
    DetailViewController *detailViewController;
    NSArray * fruits;
} 

@property [...]]]></description>
			<content:encoded><![CDATA[<h3>概述</h3>
<p><br class="spacer_" /></p>
<p>随着iPad的发布，我想很多人开始绞尽脑汁试图在新一轮的“淘金热”中抢占一块市场。iCodeBlog将推出一系列iPad教程帮助大家。</p>
<p><br class="spacer_" /></p>
<p>由于iPad使用iPhone同样的SDK，所有代码看上去完全一样。实际上，看看<a href="http://developer.apple.com/iphone/prerelease/library/releasenotes/General/WhatsNewIniPhoneOS/Articles/iPhoneOS3_2.html#//apple_ref/doc/uid/TP40009337-SW1">最新和修改了的API类</a>，你就会发现大部分是有关用户界面的。这是一个好消息，因为我们都对iPhone编程有了一定的经验。</p>
<p><br class="spacer_" /></p>
<p>此教程被称为“Hello World”，实际上它远远不止这些。首先，我假定你们已经具有iPhone/Objective-C的编程经验。</p>
<p><br class="spacer_" /></p>
<p><span id="more-923"></span></p>
<h3>我们要做什么</h3>
<p><br class="spacer_" /></p>
<p>今天的教程，我将展示怎样创建一个iPad项目，使用UISplitViewController显示两个单独的窗格。我还将涉及一些新的设计/UI模式并介绍iPad编程。</p>
<p><br class="spacer_" /></p>
<p>此项目将基于<a href="http://icodeblog.com/2008/08/08/iphone-programming-tutorial-populating-uitableview-with-an-nsarray/">使用UITableView显示水果列表教程</a> 。我们将扩展其中一个范例，如下所示：</p>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2010/02/watermelon-ss.png"><img title="watermelon-ss" src="http://icodeblog.com/wp-content/uploads/2010/02/watermelon-ss.png" alt="" width="500" height="387" /></a></p>
<p><br class="spacer_" /></p>
<p>本文将使用UISplitViewController在左窗格显示一个UITableView，而在右窗格显示一个具有UIImageView子视窗的UIView。实际上创建这样一个项目非常简单，因为项目模板提供了大部分初始代码。</p>
<h3>开始</h3>
<p><br class="spacer_" /></p>
<ol>
<li>从<a href="http://developer.apple.com/iphone/">http://developer.apple.com/iphone/</a>下载SDK 3.2。iPad模拟器包括在了SDK中。 </li>
<li>下载本项目所需的资源文件 <a href="http://icodeblog.com/wp-content/uploads/2010/02/iPadHelloWorldResources.zip">iPadHelloWorldResources.zip</a> 。 </li>
</ol>
<h3>创建项目</h3>
<p><br class="spacer_" /></p>
<p>创建iPad项目与iPhone项目没有分别。打开XCode并选择File-&gt;New Project，你将发现有一个新的Split View-Based Application选项。选择它并命名为iPadHelloWorld。</p>
<p><a href="http://icodeblog.com/wp-content/uploads/2010/02/Screen-shot-2010-02-01-at-12.37.12-PM.png"><img title="Screen shot 2010-02-01  at 12.37.12 PM" src="http://icodeblog.com/wp-content/uploads/2010/02/Screen-shot-2010-02-01-at-12.37.12-PM.png" alt="" width="544" height="418" /></a></p>
<p>这将创建一个UITableView位于左窗格，UIView位于右窗格的应用程序。它还甚至提供了一些示例数据。下列文件将被加入到项目中。</p>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2010/02/Screen-shot-2010-02-02-at-11.02.17-AM.png"><img title="Screen shot 2010-02-02  at 11.02.17 AM" src="http://icodeblog.com/wp-content/uploads/2010/02/Screen-shot-2010-02-02-at-11.02.17-AM.png" alt="" width="247" height="349" /></a></p>
<p><br class="spacer_" /></p>
<p>各文件的简单介绍如下：</p>
<p><br class="spacer_" /></p>
<ul>
<li>iPadHelloWorldAppDelegate – 类似于 app delegate。在 application:didFinishLaunchingWithOptions 方法中，创建了具有MasterViewController和 DetailViewController的UISplitViewController。 </li>
<li>MasterViewController – UITableViewController。它将处理左方视窗。 </li>
<li>DetailViewController – 它将处理右方视窗。当用户选择左方列表中不同行时，我们将在此视窗中做相应更新。 </li>
</ul>
<p><br class="spacer_" /></p>
<p>按“Build and Run”测试一下。</p>
<p><br class="spacer_" /></p>
<blockquote><p><strong>注意：当你运行此程序时，你只能看到主视窗，这是因为模拟器运行于垂直模式。选取 “Hardware -&gt; Rotate Left/Right”将使模拟器旋转。你还可以通过按下键盘上的CMD-&gt;Arrow Left/Right 键获得同样效果。</strong></p>
<p><strong><br />
 </strong></p>
</blockquote>
<h3>导入项目图像</h3>
<p><br class="spacer_" /></p>
<p>下一步你需要导入项目需要的图像。将下载的资源文件解压，并拖入称为“Resources-iPad”的文件夹下。</p>
<p>XCode 将提示你复制这些文件，选择“Yes”并按下OK。</p>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2010/02/Screen-shot-2010-02-02-at-11.30.31-AM.png"><img style="display: block; float: none; margin-left: auto; margin-right: auto" title="Screen shot 2010-02-02  at 11.30.31 AM" src="http://icodeblog.com/wp-content/uploads/2010/02/Screen-shot-2010-02-02-at-11.30.31-AM.png" alt="" width="450" height="413" /></a></p>
<p><br class="spacer_" /></p>
<p>请包括四个图像文件以及fruits.plist。</p>
<h3>显示水果列表</h3>
<p><br class="spacer_" /></p>
<p>显示水果列表与显示其他UITableView中的数据并无区别。打开MasterViewController.h 加入水果数组定义。</p>
<pre>#import   

@class DetailViewController;   

@interface MasterViewController : UITableViewController {
    DetailViewController *detailViewController;
    NSArray * fruits;
} 

@property (nonatomic, retain) IBOutlet DetailViewController *detailViewController;
@property (nonatomic, retain) NSMutableArray *fruits;  

@end </pre>
<p>如你所见，这里没有任何新的东西。我们只是简单的定义了一个水果数组，并为其创建属性。</p>
<p><br class="spacer_" /></p>
<p>我们将从plist中加载水果数据。当你不想使用数据库时，从plist文件加载数据是一种方便而快捷的方法。</p>
<p><br class="spacer_" /></p>
<p>打开MasterViewController.m 在 viewDidLoad 方法中加入下列代码：</p>
<pre>- (void)viewDidLoad {
    [super viewDidLoad];
    self.fruits = [[NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]
        pathForResource:@"fruits" ofType:@"plist"]] retain];
}</pre>
<p>fruits.plist文件实际上是一个写入文件的数组。如果你打开它，你就会发现它十分类似于XML。在为数组赋值后，我们要实现UITableView delegate 和 datasource 方法以显示列表。</p>
<p><strong> </strong></p>
<p><br class="spacer_" /></p>
<p><strong>UITableView datasource 方法</strong></p>
<pre>- (NSInteger)tableView:(UITableView *)aTableView
    numberOfRowsInSection:(NSInteger)section {
    // Return the number of rows in the section.
    return [fruits count];
}   </pre>
<pre>- (UITableViewCell *)tableView:(UITableView *)tableView
    cellForRowAtIndexPath:(NSIndexPath *)indexPath {  
    // Dequeue or create a cell of the appropriate type.
    static NSString *CellIdentifier = @"CellIdentifier";  
    UITableViewCell *cell =
        [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
           reuseIdentifier:CellIdentifier] autorelease];
        cell.accessoryType = UITableViewCellAccessoryNone;
    }   // Get the object to display and set the value in the cell.
    cell.textLabel.text = [self.fruits objectAtIndex:indexPath.row];
    return cell;
}</pre>
<p>没有什么特殊的东西… 我们首先告诉tableview 我们需要 fruits.count (等于4) 行。然后在各tableview单元中显示水果的名称。</p>
<p><br class="spacer_" /></p>
<p><strong>UITableView delegate 方法</strong></p>
<p><strong><br />
 </strong></p>
<pre>- (void)tableView:(UITableView *)aTableView
    didSelectRowAtIndexPath:(NSIndexPath *)indexPath {  
    /* When a row is selected, set the detail view controller's detail item
       to the item associated with the selected row.
     */
    detailViewController.detailItem = [self.fruits objectAtIndex: indexPath.row];
}
</pre>
<p>这里我们简单地将detailViewController 的detailItem 属性设置为所需水果。我们将在随后介绍一下属性，现在我们需要知道的是它的类型是 <strong>id。</strong></p>
<p><strong> </strong></p>
<p>现在，按下“Build and Run”运行一下。你应该看到如下画面：</p>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2010/02/tableview.png"><img title="tableview" src="http://icodeblog.com/wp-content/uploads/2010/02/tableview.png" alt="" width="500" height="387" /></a></p>
<p><br class="spacer_" /></p>
<p>它列出了水果的列表，但当你进行选择时，没有任何变化（当然detailView的标题会改变）。</p>
<p><br class="spacer_" /></p>
<p>下面，我们需要实现显示相应水果图像的代码。</p>
<p><br class="spacer_" /></p>
<h3>显示水果</h3>
<p><br class="spacer_" /></p>
<p>显示所选水果实际上非常简单。首先我们要在detailView中加入一个UIImageView。打开DetailViewController.h 加入下列代码：</p>
<pre>@interface DetailViewController : UIViewController  {  
    UIPopoverController *popoverController;
    UINavigationBar *navigationBar;  
    id detailItem;  
    IBOutlet UIImageView * fruitImageView;
}   

@property (nonatomic, retain) UIPopoverController *popoverController;
@property (nonatomic, retain) IBOutlet UINavigationBar *navigationBar; 
@property (nonatomic, retain) id detailItem;  
@property (nonatomic, retain) IBOutlet UIImageView * fruitImageView;   

@end</pre>
<p>除了添加IBOutlet UIImageView外的所有代码都是由项目模板生成的。添加此行代码后，在Interface Builder中打开DetailView.xib。</p>
<p><br class="spacer_" /></p>
<p>在视窗中加入一个尺寸为500&#215;500的UIImageView。</p>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2010/02/notitle.png"><img title="notitle" src="http://icodeblog.com/wp-content/uploads/2010/02/notitle.png" alt="" width="500" height="672" /></a></p>
<p><br class="spacer_" /></p>
<p>现在，选择File’s Owner 打开连接观察器 (Tools -&gt; connection inspector)。将imageView IBOutlet拖动到UIImageView 然后释放，将UIImageView 连接到你的outlet。</p>
<p><br class="spacer_" /></p>
<blockquote><p><strong>注意：如果你不希望图像变形，请将图像视窗的内容模式（content mode）设置为(在属性观察器attributes inspector中) Aspect Fit。</strong></p>
</blockquote>
<p><br class="spacer_" /></p>
<p>连接了imageview后，需要编写代码进行图像更新。关闭Interface Builder打开 DetailViewController.m 在setDetailItem 方法中加入下列代码：</p>
<pre>- (void)setDetailItem:(id)newDetailItem {
    if (detailItem != newDetailItem) {
        [detailItem release];
        detailItem = [newDetailItem retain];   // Update the view.
        navigationBar.topItem.title = detailItem;
	NSString * imageName = [NSString stringWithFormat:@"%@.png",detailItem];
	[self.fruitImageView setImage:[UIImage imageNamed:imageName]];
    }   if (popoverController != nil) {
        [popoverController dismissPopoverAnimated:YES];
    }
}</pre>
<p>大部分代码是由项目模板生成的，所以我不过多讨论。我添加的代码是根据水果的名称加载图像并随后设置其image属性。运行！下面是最终产品：</p>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2010/02/cherry.png"><img title="cherry" src="http://icodeblog.com/wp-content/uploads/2010/02/cherry.png" alt="" width="500" height="387" /></a></p>
<h3>SplitViewController的另一个很酷的特性</h3>
<p><br class="spacer_" /></p>
<p>当出于垂直模式时，SplitViewController提供另一个称为UIPopOverView的新的UI元素。我们以后将就此提供教程。下面我仅给出示范图：</p>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2010/02/popover.png"><img title="popover" src="http://icodeblog.com/wp-content/uploads/2010/02/popover.png" alt="" width="500" height="645" /></a></p>
<p><br class="spacer_" /></p>
<p>但设备处于垂直位置时，它将自动旋转视图并在“Master List”按钮按下时提供UIPopoverView。（另外，此按钮本身也是可以定制的）。</p>
<p><br class="spacer_" /></p>
<p>源码下载 <a href="http://icodeblog.com/wp-content/uploads/2010/02/iPadHelloWorld.zip">iPadHelloWorld.zip</a>.</p>
<p><br class="spacer_" /></p>
<h5>原文见：<a href="http://icodeblog.com/2010/04/05/ipad-programming-tutorial-hello-world/">iPad Programming Tutorial – Hello World++</a></h5>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/ipad-%e7%bc%96%e7%a8%8b%e6%95%99%e7%a8%8b-hello-world/feed</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>从零开始学习OpenGL ES之七 &#8211; 变换和矩阵</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%b8%83-%e5%8f%98%e6%8d%a2%e5%92%8c%e7%9f%a9%e9%98%b5</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%b8%83-%e5%8f%98%e6%8d%a2%e5%92%8c%e7%9f%a9%e9%98%b5#comments</comments>
		<pubDate>Thu, 25 Mar 2010 04:35:35 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[图形图像]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[OpenGL ES]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=628</guid>
		<description><![CDATA[今天的主题是我一度谈之色变的。概念上讲，它是3D编程中最为困难的部分。

首先，你应该理解 3D  几何和笛卡尔坐标系他。你还应该理解由顶点构成的三角形组成的OpenGL虚拟世界的物体，各顶点定义了三维空间的特定点，你还应理解怎样使用这些信息在  iPhone上使用OpenGL ES进行绘制。如果你不理解这些概念，我建议你回头再看看我的前六篇文章。

为在交互式程序如游戏中使用这些虚拟世界中的物体，必须要有一种方法来改变物体间的相对位置以及物体与观察者之间的相对位置。要有一种方法不但可以移动，而且可以旋转和改变物体的大小。
还必须要有一种方法将虚拟的三维坐标转换成电脑屏幕的二维坐标。所有这些都是通过所谓变换来实现的。实现变换的内部机制是就是矩阵。

尽管你不需要懂得太多有关矩阵和矩阵的数学知识就可以实现许多OpenGL的功能，但对这些观念的基本理解有很大的帮助。

内建变换以及单元矩阵

你已经见识许多OpenGL的常用变换。其中有一个在每个程序中都可以见到，它就是 glLoadIdentity()，我们在 drawView:  方法的开始处调用它来进行状态复位。还见识过 glRotatef()，用来使二十面体旋转，另外还有 glTranslatef() 使物体在虚拟世界中移动。

我们首先看看glLoadIdentity()。此函数加载单元矩阵。我们将稍后讨论此特殊矩阵，但是加载单元矩阵本质上就是将虚拟世界进行复位。它清除了先前应用的任何变换。在绘制开始前调用  glLoadIdentity() 是一个很正常的习惯，因为你可以知道起点在什么地方 &#8211; 原点，从而可以预料变换的结果。
想知道如果你不调用glLoadIdentity()会产生什么结果，下载 第四部分的Xcode项目，在drawView:中为glLoadIdentity()调用加上注释，运行。发生了什么？

应该慢速旋转的二十面体急速地离开，对吗？像小飞鼠一样飞向天空离开视线。

发生这种现象的原因是在项目中我们使用了两个变换。二十面体的顶点是围绕原点定义的，所以我们使用转移变换将其移动三个单位远离观察者使整个物体可见。我们使用的第二个变换是旋转变换，它使多面体旋转。当使用了glLoadIdentity()时，每一帧开始时都是回到原点将多面体移离观察者三个单位，也就是说它每次都终止于同一位置z  = -3.0。类似地，旋转值是随时间增长而增加的，使二十面体以均匀的步调旋转。由于前一个旋转值在开始旋转前被  glLoadIdentity()调用清除掉所以旋转是以匀速进行的。
不调用glLoadIdentity()，尽管第一次二十面体还是移离观察者三个单位并旋转一个小的角度。但从第二帧开始（几十毫秒后），二十面体将继续后移三个单位而且其旋转角度rot叠加已经旋转的角度。这将发生在每一帧上，这意味着二十面体每帧都会移离三个单位而且旋转的速度都会增加。

尽管我们可以不调用  glLoadIdentity()而对其结果进行补偿，但是由于我们许多时候无法预料转换的结果，所以最好的方法还是让转换开始于已知的位置（通常是原点）并且未经过旋转或尺寸变化，这就是我们总是先调用  glLoadIdentity()的原因了。

常见变换

除了glTranslatef()和 glRotatef()外，还有 glScalef()，它使绘制物体的尺寸增大或减小。在OpenGL  ES中还有其他一些变换功能，但这三个（与glLoadIdentity()一起使用）是最常见的。其他功能主要用于将三维虚拟世界转换为二维表示的过程，这个过程称为透视 。我们稍后涉及一下透视，但大部分情况下，除了设置视口外我们不需要直接与透视打交道。

这些常用的变换非常实用。你可以仅使用这四个调用就完成整个游戏。但是有时你可能需要自己控制转换。你想要自己控制转换的一个原因是这些常用变换必须按顺序分别调用，每次调用都是一次具有一定开销的矩阵乘法（稍后讨论）。如果你自己进行转换，你可以将多个转换组合成一个矩阵，从而减少每帧都要进行的矩阵乘法操作。

由于你可以向量化你的矩阵乘法调用，所以你可以通过定义自己的矩阵来获取最大性能。据我所知，关于此点iPhone  并无文档记录，但作为基本规则OpenGL  ES将对向量间或顶点和矩阵间的乘法进行硬件加速，但两个转换矩阵间的乘法并无加速。通过矩阵乘法向量化，你可以获得比让OpenGL进行矩阵乘法更好的性能。由于通常矩阵与矩阵的乘法调用的数量远小于向量/顶点间的矩阵乘法调用的数量，所以这并不会带来巨大的性能提升，但在一个复杂的3D程序中，每个方面小的额外性能提升都很有好处。

矩阵

这里我明显不是指电影“黑客帝国”（&#8221;The Matrix&#8221;），我们将要在随后的篇幅中介绍矩阵。

不幸的是没人可以告诉我矩阵是什么。


实际上，矩阵不过是一个二维数组。就这么简单。没什么神秘的。下面是一个矩阵示例：



这是一个 3&#215;3 矩阵，它有三行和三列。顶点和向量实际由一个1&#215;3的矩阵表示：



一个顶点还可以由一个 3&#215;1 数组而不是一个 1&#215;3 数组表示，但是我们这里使用1&#215;3 格式（后面解释原因）。甚至一个数据元素技术上也可以用一个 1&#215;1  矩阵表示，尽管这并不是一个十分有用的矩阵。
还有什么可以用数组来表示？坐标系统。还记得向量？向量是想象的从原点指向空间一点的直线。现在，记住笛卡尔坐标系统具有三个轴：



那么，指向X轴反向的归一向量是什么样的？记住：归一向量是长度为一的向量，所以一个沿X轴正向的归一向量是这样的：



注意我们是使用3&#215;1  矩阵而不是像处理顶点一样使用1&#215;3矩阵来表示向量。实际上这并不重要，只要按处理顶点相反的方式处理向量就行。向量中的三个数值适用于同一个坐标轴。我知道，这可能还不太好理解，但我很快就会解释到。Y轴向上的向量看上去像这样：



Z轴上的向量像这样:



现在我们将这三个向量矩阵按照它们在顶点（x然后是y再后是z）中的顺序放入矩阵中，像这样：



有一个特殊矩阵称为单元矩阵。听起来很熟悉？当你调用glLoadIdentity()时， 你就加载了一个单元矩阵1。 这里我解释一下为什么它是一个特殊矩阵。矩阵可以通过相乘而组合在一起。如果你将矩阵乘以一个单元矩阵，其结果就是原始矩阵，就像数字乘以一。你可以通过将所有除行号和列号相等的元素外的值设置为0来设定一个任意尺寸的单元矩阵，行号和列号相等处的值为1.0.

矩阵相乘

矩阵相乘是矩阵组合的关键。如果你有一个定义了转移到矩阵和一个定义了旋转的矩阵，如果将它们相乘，你将得到一个既定义了旋转又定义了转移的矩阵。让我们看看一个简单的矩阵相乘的简单例子。看看下面两个矩阵的图像：




矩阵相乘的结果是另一个与等式左边矩阵一样尺寸的矩阵。矩阵乘法是不可置换的。顺序是很重要的。将矩阵a乘以矩阵b的结果不一定与矩阵b乘以矩阵a的结果相同（尽管在某些情况下有可能相同）。

有关矩阵相乘还有一件事需要注意：并不是所有的矩阵都可以相乘的。它们并不需要具有同样的尺寸，但等式中右边的矩阵的行数必须与等式中左边矩阵的列数相同。所以，你可以将一个3&#215;3的矩阵与另一个3&#215;3的矩阵相乘，或者你可以将一个1&#215;3的矩阵与一个3&#215;6的矩阵相乘，但你不能将一个2&#215;4的矩阵与另一个2&#215;4的矩阵相乘，因为2&#215;4矩阵的列数与另一个2&#215;4矩阵的行数并不相同。

要得出矩阵相乘的结果，我们首先建立一个与等式中左边矩阵同样尺寸的空矩阵：



对矩阵中的各点，我们从左边矩阵中取出相应行，再从右边矩阵中取出相应列。所以，对于结果矩阵中的左上方的点，我们取出等式中左边矩阵的第一行以及等式中右边矩阵的第一列，像这样：



然后，将左边矩阵行中第一个值乘以右边矩阵列的第一个值，左边矩阵行第二个值乘以右边矩阵列的第二个值，左边矩阵行第三个值乘以右边矩阵列的第三个值，然后再将它们相加。像这样：



对结果矩阵中的各点重复以上步骤，得到以下结果：



将矩阵（蓝色）乘以单元矩阵（红色），其结果就是原始矩阵。由于单元矩阵代表没有经过任何变换的坐标系统，所以结果完全合理。这也同样适用于顶点。我们将一个顶点乘以一个矩阵：


现在，我们假定我们希望旋转一个物体。我们要做的就是定义一个描述了被旋转的坐标系统的矩阵。在场景中，我们实际上是旋转了世界坐标，然后将物体绘制其上。如果说我们希望绕Z轴旋转一个物体，那么Z轴将保持不变，而X和Y轴将变化。尽管有些不那么直观，要定义一个绕z轴旋转的坐标系统，我们需要调整3&#215;3矩阵中的x和y向量，换句话说，我们必须修改第一和第二列。



所以需要调整通过修改X轴向量的X值和Y向量的Y值为旋转角度的余弦值。余弦是三角形角度对应的相邻两边之比。我们还需要修改X轴向量的Y值为相同角度的正弦值的负值，以及Y轴向量的X值为相同角度的正弦值。用矩阵来表示可能更容易理解：



现在如果将世界坐标中所有物体的各顶点都乘以这个矩阵，那么物体将被旋转。一旦对物体的所有顶点都进行此操作，那么该物体将沿Z轴旋转n度。

如果你还不太理解也不要紧。其实你并不需要真正理解使用矩阵的数学原理。它们都是已经解决了的难题，你可以通过Google找到任何变换所需的矩阵。实际上，你可以在OpenGL的文档中找到大部分。所以如果你不能完全理解为什么这个矩阵导致绕Z轴的旋转，也完全不必气恼。

一个3&#215;3的矩阵可以描述绕任意轴旋转任何角度的情况。然而，为表示可能遇到的任何变换，我们仍然需要第四行/列。第四列用来保存变换信息，第四行用来表示透视变换。因为需要理解同原坐标以及透视空间，而这些对于成为一个出色的OpenGL程序员并不重要，所以我不打算在这里介绍透视变换的数学原理了。要乘以一个4&#215;4矩阵，我们需要填充一个附加值，通常我们称其为W。W应该为1。在进行乘法运算后，忽略W值。我不打算介绍向量的矩阵乘法，因为OpenGL已经对此进行了硬件加速，所以通常不需要进行手工处理，但理解其基本步骤是很好的建议。

OpenGL ES矩阵

OpenGL ES [...]]]></description>
			<content:encoded><![CDATA[<p>今天的主题是我一度谈之色变的。概念上讲，它是3D编程中最为困难的部分。</p>
<p><br class="spacer_" /></p>
<p>首先，你应该理解 3D  几何和笛卡尔坐标系他。你还应该理解由顶点构成的三角形组成的OpenGL虚拟世界的物体，各顶点定义了三维空间的特定点，你还应理解怎样使用这些信息在  iPhone上使用OpenGL ES进行绘制。如果你不理解这些概念，我建议你回头再看看我的前六篇文章。</p>
<p><br class="spacer_" /></p>
<p>为在交互式程序如游戏中使用这些虚拟世界中的物体，必须要有一种方法来改变物体间的相对位置以及物体与观察者之间的相对位置。要有一种方法不但可以移动，而且可以旋转和改变物体的大小。</p>
<p>还必须要有一种方法将虚拟的三维坐标转换成电脑屏幕的二维坐标。所有这些都是通过所谓<em>变换</em>来实现的。实现变换的内部机制是就是<em>矩阵</em>。</p>
<p><br class="spacer_" /></p>
<p>尽管你不需要懂得太多有关矩阵和矩阵的数学知识就可以实现许多OpenGL的功能，但对这些观念的基本理解有很大的帮助。</p>
<h3><span id="more-628"></span></h3>
<h3>内建变换以及单元矩阵</h3>
<p><br class="spacer_" /></p>
<p>你已经见识许多OpenGL的常用变换。其中有一个在每个程序中都可以见到，它就是 glLoadIdentity()，我们在 drawView:  方法的开始处调用它来进行状态复位。还见识过 glRotatef()，用来使二十面体旋转，另外还有 glTranslatef() 使物体在虚拟世界中移动。</p>
<p><br class="spacer_" /></p>
<p>我们首先看看glLoadIdentity()。此函数加载<em>单元矩阵。</em>我们将稍后讨论此特殊矩阵，但是加载单元矩阵本质上就是将虚拟世界进行复位。它清除了先前应用的任何变换。在绘制开始前调用  glLoadIdentity() 是一个很正常的习惯，因为你可以知道起点在什么地方 &#8211; 原点，从而可以预料变换的结果。</p>
<p>想知道如果你不调用glLoadIdentity()会产生什么结果，下载 <a href="../wp-content/uploads/2009/12/Part4ProjectFinal.zip">第四部分的Xcode项目</a>，在drawView:中为glLoadIdentity()调用加上注释，运行。发生了什么？</p>
<p><br class="spacer_" /></p>
<p>应该慢速旋转的二十面体急速地离开，对吗？像小飞鼠一样飞向天空离开视线。</p>
<p><br class="spacer_" /></p>
<p>发生这种现象的原因是在项目中我们使用了两个变换。二十面体的顶点是围绕原点定义的，所以我们使用<em>转移变换</em>将其移动三个单位远离观察者使整个物体可见。我们使用的第二个变换是<em>旋转变换</em>，它使多面体旋转。当使用了glLoadIdentity()时，每一帧开始时都是回到原点将多面体移离观察者三个单位，也就是说它每次都终止于同一位置z  = -3.0。类似地，旋转值是随时间增长而增加的，使二十面体以均匀的步调旋转。由于前一个旋转值在开始旋转前被  glLoadIdentity()调用清除掉所以旋转是以匀速进行的。</p>
<p>不调用glLoadIdentity()，尽管第一次二十面体还是移离观察者三个单位并旋转一个小的角度。但从第二帧开始（几十毫秒后），二十面体将继续后移三个单位而且其旋转角度rot叠加已经旋转的角度。这将发生在每一帧上，这意味着二十面体每帧都会移离三个单位而且旋转的速度都会增加。</p>
<p><br class="spacer_" /></p>
<p>尽管我们可以不调用  glLoadIdentity()而对其结果进行补偿，但是由于我们许多时候无法预料转换的结果，所以最好的方法还是让转换开始于已知的位置（通常是原点）并且未经过旋转或尺寸变化，这就是我们总是先调用  glLoadIdentity()的原因了。</p>
<h3></h3>
<h3>常见变换</h3>
<p><br class="spacer_" /></p>
<p>除了glTranslatef()和 glRotatef()外，还有 glScalef()，它使绘制物体的尺寸增大或减小。在OpenGL  ES中还有其他一些变换功能，但这三个（与glLoadIdentity()一起使用）是最常见的。其他功能主要用于将三维虚拟世界转换为二维表示的过程，这个过程称为<em>透视</em> 。我们稍后涉及一下透视，但大部分情况下，除了设置视口外我们不需要直接与透视打交道。</p>
<p><br class="spacer_" /></p>
<p>这些常用的变换非常实用。你可以仅使用这四个调用就完成整个游戏。但是有时你可能需要自己控制转换。你想要自己控制转换的一个原因是这些常用变换必须按顺序分别调用，每次调用都是一次具有一定开销的<em>矩阵乘法</em>（稍后讨论）。如果你自己进行转换，你可以将多个转换组合成一个矩阵，从而减少每帧都要进行的矩阵乘法操作。</p>
<p><br class="spacer_" /></p>
<p>由于你可以<em>向量化</em>你的矩阵乘法调用，所以你可以通过定义自己的矩阵来获取最大性能。据我所知，关于此点iPhone  并无文档记录，但作为基本规则OpenGL  ES将对向量间或顶点和矩阵间的乘法进行硬件加速，但两个转换矩阵间的乘法并无加速。通过矩阵乘法向量化，你可以获得比让OpenGL进行矩阵乘法更好的性能。由于通常矩阵与矩阵的乘法调用的数量远小于向量/顶点间的矩阵乘法调用的数量，所以这并不会带来巨大的性能提升，但在一个复杂的3D程序中，每个方面小的额外性能提升都很有好处。</p>
<h3></h3>
<h3>矩阵</h3>
<p><br class="spacer_" /></p>
<p>这里我明显不是指电影“黑客帝国”（&#8221;The Matrix&#8221;），我们将要在随后的篇幅中介绍矩阵。</p>
<p><br class="spacer_" /></p>
<blockquote><p><em>不幸的是没人可以告诉我矩阵是什么。</em></p>
</blockquote>
<p><br class="spacer_" /></p>
<p>实际上，矩阵不过是一个二维数组。就这么简单。没什么神秘的。下面是一个矩阵示例：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/simplematrix.png"><img title="simplematrix" src="../wp-content/uploads/2009/12/simplematrix.png" alt="simplematrix" width="385" height="279" /></a></p>
<p><br class="spacer_" /></p>
<p>这是一个 3&#215;3 矩阵，它有三行和三列。顶点和向量实际由一个1&#215;3的矩阵表示：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/vertexmatrix.png"><img title="vertexmatrix" src="../wp-content/uploads/2009/12/vertexmatrix.png" alt="vertexmatrix" width="283" height="72" /></a></p>
<p><br class="spacer_" /></p>
<p>一个顶点还可以由一个 3&#215;1 数组而不是一个 1&#215;3 数组表示，但是我们这里使用1&#215;3 格式（后面解释原因）。甚至一个数据元素技术上也可以用一个 1&#215;1  矩阵表示，尽管这并不是一个十分有用的矩阵。</p>
<p>还有什么可以用数组来表示？坐标系统。还记得向量？向量是想象的从原点指向空间一点的直线。现在，记住笛卡尔坐标系统具有三个轴：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/cartesian1.png"><img title="cartesian" src="../wp-content/uploads/2009/12/cartesian1.png" alt="cartesian" width="654" height="538" /></a></p>
<p><br class="spacer_" /></p>
<p>那么，指向X轴反向的归一向量是什么样的？记住：归一向量是长度为一的向量，所以一个沿X轴正向的归一向量是这样的：</p>
<p><br class="spacer_" /></p>
<p><img title="xaxisvector" src="../wp-content/uploads/2009/12/xaxisvector.png" alt="xaxisvector" width="129" height="192" /></p>
<p><br class="spacer_" /></p>
<p>注意我们是使用3&#215;1  矩阵而不是像处理顶点一样使用1&#215;3矩阵来表示向量。实际上这并不重要，只要按处理顶点相反的方式处理向量就行。向量中的三个数值适用于同一个坐标轴。我知道，这可能还不太好理解，但我很快就会解释到。Y轴向上的向量看上去像这样：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/yaxisvector.png"><img title="yaxisvector" src="../wp-content/uploads/2009/12/yaxisvector.png" alt="yaxisvector" width="129" height="196" /></a></p>
<p><br class="spacer_" /></p>
<p>Z轴上的向量像这样:</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/zaxisvector.png"><img title="zaxisvector" src="../wp-content/uploads/2009/12/zaxisvector.png" alt="zaxisvector" width="129" height="195" /></a></p>
<p><br class="spacer_" /></p>
<p>现在我们将这三个向量矩阵按照它们在顶点（x然后是y再后是z）中的顺序放入矩阵中，像这样：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/3x3identity.png"><img title="3x3identity" src="../wp-content/uploads/2009/12/3x3identity.png" alt="3x3identity" width="243" height="195" /></a></p>
<p><br class="spacer_" /></p>
<p>有一个特殊矩阵称为<em>单元矩阵。</em>听起来很熟悉？当你调用glLoadIdentity()时， 你就加载了一个单元矩阵<sup>1。</sup> 这里我解释一下为什么它是一个特殊矩阵。矩阵可以通过相乘而组合在一起。如果你将矩阵乘以一个单元矩阵，其结果就是原始矩阵，就像数字乘以一。你可以通过将所有除行号和列号相等的元素外的值设置为0来设定一个任意尺寸的单元矩阵，行号和列号相等处的值为1.0.</p>
<p><br class="spacer_" /></p>
<h3>矩阵相乘</h3>
<p><br class="spacer_" /></p>
<p>矩阵相乘是矩阵组合的关键。如果你有一个定义了转移到矩阵和一个定义了旋转的矩阵，如果将它们相乘，你将得到一个既定义了旋转又定义了转移的矩阵。让我们看看一个简单的矩阵相乘的简单例子。看看下面两个矩阵的图像：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/simplemultiply.png"><img title="simplemultiply" src="../wp-content/uploads/2009/12/simplemultiply.png" alt="simplemultiply" width="637" height="192" /></a></p>
<p><br class="spacer_" /></p>
<p><br class="spacer_" /></p>
<p>矩阵相乘的结果是另一个与等式左边矩阵一样尺寸的矩阵。矩阵乘法是不可置换的。顺序是很重要的。将矩阵a乘以矩阵b的结果不一定与矩阵b乘以矩阵a的结果相同（尽管在某些情况下有可能相同）。</p>
<p><br class="spacer_" /></p>
<p>有关矩阵相乘还有一件事需要注意：并不是所有的矩阵都可以相乘的。它们并不需要具有同样的尺寸，但等式中右边的矩阵的行数必须与等式中左边矩阵的列数相同。所以，你可以将一个3&#215;3的矩阵与另一个3&#215;3的矩阵相乘，或者你可以将一个1&#215;3的矩阵与一个3&#215;6的矩阵相乘，但你不能将一个2&#215;4的矩阵与另一个2&#215;4的矩阵相乘，因为2&#215;4矩阵的列数与另一个2&#215;4矩阵的行数并不相同。</p>
<p><br class="spacer_" /></p>
<p>要得出矩阵相乘的结果，我们首先建立一个与等式中左边矩阵同样尺寸的空矩阵：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/empty3x3matrix.png"><img title="empty3x3matrix" src="../wp-content/uploads/2009/12/empty3x3matrix.png" alt="empty3x3matrix" width="243" height="196" /></a></p>
<p><br class="spacer_" /></p>
<p>对矩阵中的各点，我们从左边矩阵中取出相应行，再从右边矩阵中取出相应列。所以，对于结果矩阵中的左上方的点，我们取出等式中左边矩阵的第一行以及等式中右边矩阵的第一列，像这样：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/multbreakdown.png"><img title="multbreakdown" src="../wp-content/uploads/2009/12/multbreakdown.png" alt="multbreakdown" width="651" height="140" /></a></p>
<p><br class="spacer_" /></p>
<p>然后，将左边矩阵行中第一个值乘以右边矩阵列的第一个值，左边矩阵行第二个值乘以右边矩阵列的第二个值，左边矩阵行第三个值乘以右边矩阵列的第三个值，然后再将它们相加。像这样：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/firstspotcalc.png"><img title="firstspotcalc" src="../wp-content/uploads/2009/12/firstspotcalc.png" alt="firstspotcalc" width="549" height="72" /></a></p>
<p><br class="spacer_" /></p>
<p>对结果矩阵中的各点重复以上步骤，得到以下结果：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/simplematrixfinal.png"><img title="simplematrixfinal" src="../wp-content/uploads/2009/12/simplematrixfinal.png" alt="simplematrixfinal" width="656" height="149" /></a></p>
<p><br class="spacer_" /></p>
<p>将矩阵（蓝色）乘以单元矩阵（红色），其结果就是原始矩阵。由于单元矩阵代表没有经过任何变换的坐标系统，所以结果完全合理。这也同样适用于顶点。我们将一个顶点乘以一个矩阵：</p>
<p><a href="../wp-content/uploads/2009/12/vertexmult.png"><img title="vertexmult" src="../wp-content/uploads/2009/12/vertexmult.png" alt="vertexmult" width="654" height="171" /></a></p>
<p><br class="spacer_" /></p>
<p>现在，我们假定我们希望旋转一个物体。我们要做的就是定义一个描述了被旋转的坐标系统的矩阵。在场景中，我们实际上是旋转了世界坐标，然后将物体绘制其上。如果说我们希望绕Z轴旋转一个物体，那么Z轴将保持不变，而X和Y轴将变化。尽管有些不那么直观，要定义一个绕z轴旋转的坐标系统，我们需要调整3&#215;3矩阵中的x和y向量，换句话说，我们必须修改第一和第二列。</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/vectoraxes.png"><img title="vectoraxes" src="../wp-content/uploads/2009/12/vectoraxes.png" alt="vectoraxes" width="243" height="235" /></a></p>
<p><br class="spacer_" /></p>
<p>所以需要调整通过修改X轴向量的X值和Y向量的Y值为旋转角度的余弦值。余弦是三角形角度对应的相邻两边之比。我们还需要修改X轴向量的Y值为相同角度的正弦值的负值，以及Y轴向量的X值为相同角度的正弦值。用矩阵来表示可能更容易理解：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/zrotation.png"><img title="zrotation" src="../wp-content/uploads/2009/12/zrotation.png" alt="zrotation" width="396" height="195" /></a></p>
<p><br class="spacer_" /></p>
<p>现在如果将世界坐标中所有物体的各顶点都乘以这个矩阵，那么物体将被旋转。一旦对物体的所有顶点都进行此操作，那么该物体将沿Z轴旋转n度。</p>
<p><br class="spacer_" /></p>
<p>如果你还不太理解也不要紧。其实你并不需要真正理解使用矩阵的数学原理。它们都是已经解决了的难题，你可以通过Google找到任何变换所需的矩阵。实际上，你可以在OpenGL的文档中找到大部分。所以如果你不能完全理解为什么这个矩阵导致绕Z轴的旋转，也完全不必气恼。</p>
<p><br class="spacer_" /></p>
<p>一个3&#215;3的矩阵可以描述绕任意轴旋转任何角度的情况。然而，为表示可能遇到的任何变换，我们仍然需要第四行/列。第四列用来保存变换信息，第四行用来表示透视变换。因为需要理解同原坐标以及透视空间，而这些对于成为一个出色的OpenGL程序员并不重要，所以我不打算在这里介绍透视变换的数学原理了。要乘以一个4&#215;4矩阵，我们需要填充一个附加值，通常我们称其为W。W应该为1。在进行乘法运算后，忽略W值。我不打算介绍向量的矩阵乘法，因为OpenGL已经对此进行了硬件加速，所以通常不需要进行手工处理，但理解其基本步骤是很好的建议。</p>
<p><br class="spacer_" /></p>
<h3>OpenGL ES矩阵</h3>
<p><br class="spacer_" /></p>
<p>OpenGL ES 中有两套矩阵，都是4&#215;4的GLfloat矩阵。一个叫 <em>modelview matrix</em> ，你大部分时间都会与之打交道。它是你用来对虚拟世界进行变换的矩阵。要对虚拟世界中的物体进行旋转，转移或尺寸变化，你都需要对此矩阵进行修改。</p>
<p><br class="spacer_" /></p>
<p>另一个矩阵用来创建根据设定的视口对世界坐标进行描述的二维表示。此矩阵称为 <em>projection matrix</em> 。在绝大部分时间内，你都不需要接触该矩阵。</p>
<p><br class="spacer_" /></p>
<p>任何时刻这两个矩阵中只能有一个是活动的，任何与矩阵相关的调用，包括 glLoadIdentity(), glRotatef(),  glTranslatef(), 和 glScalef() 只影响活动矩阵。当你调用  glLoadIdentity时，活动矩阵设置为单元矩阵。其他三个调用则创建一个转移/尺寸变换/旋转矩阵，并将该矩阵乘以活动矩阵，将活动矩阵的结果替换为矩阵乘法得到的结果。</p>
<p><br class="spacer_" /></p>
<p>在大部分情况下，你仅需在程序开始时将 modelview 矩阵设置为活动。实际上，如果你看过我的OpenGL ES 模板，你将在setupView:  方法中看到下面代码：</p>
<p><br class="spacer_" /></p>
<p>glMatrixMode(GL_MODELVIEW);</p>
<p><br class="spacer_" /></p>
<p>OpenGL ES的矩阵定义为一个由16 个GLfloat组成的数组，像这样：</p>
<pre>        GLfloat     matrix[16];</pre>
<p>它们也可以表示为一个二维C数组，像这样：</p>
<pre>        GLfloat     matrix[4][4];</pre>
<p>两种方法导致同样的内存被分配，所以完全是个人习惯问题，尽管前者更为普遍。</p>
<h4>主题</h4>
<p><br class="spacer_" /></p>
<p>好，我相信现在你已经有了足够的理论基础，想开始进行实践了。首先使用我的模板 <a href="http://www.topsyproxy.com/browse.php/be203aea/0b376beO/i8vd3d3L/mlubmVyb/G9vcC5ia/XovY29kZ/S9FbXB0e/SBPcGVuR/0wgRVMgQ/XBwbGljY/XRpb24ue/mlw/b0/">Open</a><a href="../wp-content/uploads/2009/12/Empty%20OpenGL%20ES%20Application.zip">Empty%20OpenGL%20ES%20Application</a> 创建一个项目，替换 drawView: 和 setupView:</p>
<pre>- (void)drawView:(GLView*)view;
{

    static GLfloat  rot = 0.0;
    static GLfloat  scale = 1.0;
    static GLfloat  yPos = 0.0;
    static BOOL     scaleIncreasing = YES;

    // This is the same result as using Vertex3D, just faster to type and
    // can be made const this way
    static const Vertex3D vertices[]= {
        {0, -0.525731, 0.850651},             // vertices[0]
        {0.850651, 0, 0.525731},              // vertices[1]
        {0.850651, 0, -0.525731},             // vertices[2]
        {-0.850651, 0, -0.525731},            // vertices[3]
        {-0.850651, 0, 0.525731},             // vertices[4]
        {-0.525731, 0.850651, 0},             // vertices[5]
        {0.525731, 0.850651, 0},              // vertices[6]
        {0.525731, -0.850651, 0},             // vertices[7]
        {-0.525731, -0.850651, 0},            // vertices[8]
        {0, -0.525731, -0.850651},            // vertices[9]
        {0, 0.525731, -0.850651},             // vertices[10]
        {0, 0.525731, 0.850651}               // vertices[11]
    };

    static const Color3D colors[] = {
        {1.0, 0.0, 0.0, 1.0},
        {1.0, 0.5, 0.0, 1.0},
        {1.0, 1.0, 0.0, 1.0},
        {0.5, 1.0, 0.0, 1.0},
        {0.0, 1.0, 0.0, 1.0},
        {0.0, 1.0, 0.5, 1.0},
        {0.0, 1.0, 1.0, 1.0},
        {0.0, 0.5, 1.0, 1.0},
        {0.0, 0.0, 1.0, 1.0},
        {0.5, 0.0, 1.0, 1.0},
        {1.0, 0.0, 1.0, 1.0},
        {1.0, 0.0, 0.5, 1.0}
    };

    static const GLubyte icosahedronFaces[] = {
        1, 2, 6,
        1, 7, 2,
        3, 4, 5,
        4, 3, 8,
        6, 5, 11,
        5, 6, 10,
        9, 10, 2,
        10, 9, 3,
        7, 8, 9,
        8, 7, 0,
        11, 0, 1,
        0, 11, 4,
        6, 2, 10,
        1, 6, 11,
        3, 5, 10,
        5, 4, 11,
        2, 7, 9,
        7, 1, 0,
        3, 9, 8,
        4, 8, 0,
    };

    static const Vector3D normals[] = {
        {0.000000, -0.417775, 0.675974},
        {0.675973, 0.000000, 0.417775},
        {0.675973, -0.000000, -0.417775},
        {-0.675973, 0.000000, -0.417775},
        {-0.675973, -0.000000, 0.417775},
        {-0.417775, 0.675974, 0.000000},
        {0.417775, 0.675973, -0.000000},
        {0.417775, -0.675974, 0.000000},
        {-0.417775, -0.675974, 0.000000},
        {0.000000, -0.417775, -0.675973},
        {0.000000, 0.417775, -0.675974},
        {0.000000, 0.417775, 0.675973},
    };

    glLoadIdentity();
    glTranslatef(0.0f,yPos,-3);
    glRotatef(rot,1.0f,1.0f,1.0f);
    glScalef(scale, scale, scale);

    glClearColor(0.0, 0.0, 0.05, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
    glEnable(GL_COLOR_MATERIAL);
    glEnableClientState(GL_NORMAL_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, vertices);
    glColorPointer(4, GL_FLOAT, 0, colors);
    glNormalPointer(GL_FLOAT, 0, normals);
    glDrawElements(GL_TRIANGLES, 60, GL_UNSIGNED_BYTE, icosahedronFaces);

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisable(GL_COLOR_MATERIAL);
    static NSTimeInterval lastDrawTime;
    if (lastDrawTime)
    {
        NSTimeInterval timeSinceLastDraw =
           [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
        rot+=50 * timeSinceLastDraw;  

        if (scaleIncreasing)
        {
            scale += timeSinceLastDraw;
            yPos += timeSinceLastDraw;
            if (scale &gt; 2.0)
                scaleIncreasing = NO;
        }
        else
        {
            scale -= timeSinceLastDraw;
            yPos -= timeSinceLastDraw;
            if (scale &lt; 1.0)
                scaleIncreasing = YES;

        }
    }
    lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}
-(void)setupView:(GLView*)view
{
    const GLfloat zNear = 0.01, zFar = 1000.0, fieldOfView = 45.0;
    GLfloat size;
    glEnable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    size = zNear * tanf(DEGREES_TO_RADIANS(fieldOfView) / 2.0);
    CGRect rect = view.bounds;
    glFrustumf(-size, size, -size / (rect.size.width / rect.size.height), size /
               (rect.size.width / rect.size.height), zNear, zFar);
    glViewport(0, 0, rect.size.width, rect.size.height);
    glMatrixMode(GL_MODELVIEW);

    // Enable lighting
    glEnable(GL_LIGHTING);

    // Turn the first light on
    glEnable(GL_LIGHT0);

    // Define the ambient component of the first light
    static const Color3D light0Ambient[] = {{0.3, 0.3, 0.3, 1.0}};
    glLightfv(GL_LIGHT0, GL_AMBIENT, (const GLfloat *)light0Ambient);

    // Define the diffuse component of the first light
    static const Color3D light0Diffuse[] = {{0.4, 0.4, 0.4, 1.0}};
    glLightfv(GL_LIGHT0, GL_DIFFUSE, (const GLfloat *)light0Diffuse);

    // Define the specular component of the first light
    static const Color3D light0Specular[] = {{0.7, 0.7, 0.7, 1.0}};
    glLightfv(GL_LIGHT0, GL_SPECULAR, (const GLfloat *)light0Specular);

    // Define the position of the first light
    // const GLfloat light0Position[] = {10.0, 10.0, 10.0};
    static const Vertex3D light0Position[] = {{10.0, 10.0, 10.0}};
    glLightfv(GL_LIGHT0, GL_POSITION, (const GLfloat *)light0Position); 

    // Calculate light vector so it points at the object
    static const Vertex3D objectPoint[] = {{0.0, 0.0, -3.0}};
    const Vertex3D lightVector =
        Vector3DMakeWithStartAndEndPoints(light0Position[0], objectPoint[0]);
    glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, (GLfloat *)&amp;lightVector);

    // Define a cutoff angle. This defines a 90° field of vision, since the cutoff
    // is number of degrees to each side of an imaginary line drawn from the light's
    // position along the vector supplied in GL_SPOT_DIRECTION above
    glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 25.0);

    glLoadIdentity();
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}</pre>
<p>以上代码使用我们的老朋友 二十面体。与以前一样它旋转，但它同时还使用转移变换沿着Y轴上下移动，并且使用尺寸变换矩阵增加和减小尺寸。它使用了所有三个  modelview 变换；我们加载单元矩阵，使用OpenGL ES内部变换函数改变尺寸，旋转并且移动 。</p>
<p><br class="spacer_" /></p>
<p>让我们用自定义的矩阵替换内部函数。在进行修改前，先运行一下看看我们的程序到底应该是怎样工作的。</p>
<p><br class="spacer_" /></p>
<h4>定义矩阵</h4>
<p><br class="spacer_" /></p>
<p>我们自定义一个矩阵。</p>
<pre>typedef GLfloat Matrix3D[16];</pre>
<h3>自定义单元矩阵</h3>
<p><br class="spacer_" /></p>
<p>首先，我们自定义一个单元矩阵。像这样：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/4x4identity.png"><img title="4x4identity" src="../wp-content/uploads/2009/12/4x4identity.png" alt="4x4identity" width="291" height="222" /></a></p>
<p><br class="spacer_" /></p>
<p>下面是代码：</p>
<pre>static inline void Matrix3DSetIdentity(Matrix3D matrix)
{
    matrix[0] = matrix[5] =  matrix[10] = matrix[15] = 1.0;
    matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0;
    matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0;
    matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
}</pre>
<p>第一印象这好像不太对。看上去我们传递的是Matrix3D的值。然而我们使用的是 typedef<sup>2</sup> 数组，由于C99数组与指针等价，数组是通过参考而不是值传递的，所以我们只是赋值给个数组中的值而不需要直接传递指针。</p>
<p><br class="spacer_" /></p>
<p>我使用了内嵌函数以消除函数调用的开销。但是这会有一定的副作用（主要是增加了代码的尺寸），本文中的代码与普通C函数一样。注意在C和Objective-C程序中，  static 关键字是正确的（而且是很好的方法），但是如果你使用 C++ 或者 Objective-C++，那么你应该移除它。 <a href="http://gcc.gnu.org/onlinedocs/gcc-4.0.4/gcc/Inline.html">GCC 手册</a> 推荐在C内嵌函数中使用static，这是因为它允许编译器移除未被使用的内嵌函数所生成的汇编码。然而如果你使用 C++ 或者 Objective-C++，关键字  static 可能会影响链接器的行为而不会提供真正的好处。</p>
<p><br class="spacer_" /></p>
<p>好，现在我们使用新函数替代 glLoadIdentity()。删除 glLoadIdentity()然后使用下列代码进行替换：</p>
<pre>    static Matrix3D    identityMatrix;
    Matrix3DSetIdentity(identityMatrix);
    glLoadMatrixf(identityMatrix);</pre>
<p><br class="spacer_" /></p>
<p>我们定义了一个 Matrix3D，赋予其单元矩阵值，然后使用 glLoadMatrixf()加载此矩阵，这使用单元矩阵替代了活动矩阵（即本文情况下的  modelview 矩阵)。这与glLoadIdentity()调用完全一样。运行，你将看到与以前一样的结果。</p>
<p><br class="spacer_" /></p>
<p>现在，你明白了 glLoadIdentity() 是怎样工作的。继续。</p>
<h3>矩阵乘法</h3>
<p><br class="spacer_" /></p>
<p>在开始任何更多的变换前，我们需要编写一个两个矩阵相乘的函数。记住矩阵相乘是将两个矩阵合成一个矩阵的方法。我们需要编写一个通用矩阵相乘的方法，它允许任何尺寸的数组并且使用循环进行计算，但是我们最好不要使用循环。循环通常会带来一些开销。由于OpenGL  ES中的矩阵总是为 4&#215;4，最快的方法是分别进行计算。下面是矩阵乘法的代码：</p>
<pre>static inline void Matrix3DMultiply(Matrix3D m1, Matrix3D m2, Matrix3D result)
{
    result[0] = m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2] + m1[12] * m2[3];
    result[1] = m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2] + m1[13] * m2[3];
    result[2] = m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2] + m1[14] * m2[3];
    result[3] = m1[3] * m2[0] + m1[7] * m2[1] + m1[11] * m2[2] + m1[15] * m2[3];

    result[4] = m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6] + m1[12] * m2[7];
    result[5] = m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6] + m1[13] * m2[7];
    result[6] = m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6] + m1[14] * m2[7];
    result[7] = m1[3] * m2[4] + m1[7] * m2[5] + m1[11] * m2[6] + m1[15] * m2[7];

    result[8] = m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10] + m1[12] * m2[11];
    result[9] = m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10] + m1[13] * m2[11];
    result[10] = m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10] + m1[14] * m2[11];
    result[11] = m1[3] * m2[8] + m1[7] * m2[9] + m1[11] * m2[10] + m1[15] * m2[11];

    result[12] = m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12] * m2[15];
    result[13] = m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13] * m2[15];
    result[14] = m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14] * m2[15];
    result[15] = m1[3] * m2[12] + m1[7] * m2[13] + m1[11] * m2[14] + m1[15] * m2[15];
}
</pre>
<p>此函数不分配任何内存，它只是将两个数组相乘的结果赋予结果数组。结果数组并非两个相乘数组中的任何一个，这是由于如果将结果赋予两个数组中的任意一个，在再次使用时会产生不正确的结果。</p>
<p><br class="spacer_" /></p>
<p>但是，等一下… 这实际不是更快一些吗，至少在iPhone上如此。iPhone具有4个向量处理器，它们使得进行浮点运算比使用iPhone  CPU更快。然而使用向量处理器要求编写 ARM6 汇编码因为没有C函数库可以访问向量处理器。幸运的是已经有人找到通过使用向量处理器进行矩阵相乘的方法了。<a href="http://code.google.com/p/vfpmathlibrary/">VFP 数学库</a> 包含了许多向量运算功能而且它的许可证要求很宽容。所以我将在我们的代码中使用 VFP  数学库进行矩阵相乘，当运行在设备上向量版将被使用而在模拟器上则使用普通版（注意我已经包括了VFP数学库的许可证）：</p>
<pre>/*
   These define the vectorized version of the
   matrix multiply function and are based on the Matrix4Mul method from
   the vfp-math-library. This code has been modified, but is still subject to
   the original license terms and ownership as follow:

 VFP math library for the iPhone / iPod touch

 Copyright (c) 2007-2008 Wolfgang Engel and Matthias Grundmann
 http://code.google.com/p/vfpmathlibrary/

 This software is provided 'as-is', without any express or implied warranty.
 In no event will the authors be held liable for any damages arising
 from the use of this software.
 Permission is granted to anyone to use this software for any purpose,
 including commercial applications, and to alter it and redistribute it freely,
 subject to the following restrictions:

 1. The origin of this software must not be misrepresented; you must
 not claim that you wrote the original software. If you use this
 software in a product, an acknowledgment in the product documentation
 would be appreciated but is not required.

 2. Altered source versions must be plainly marked as such, and must
 not be misrepresented as being the original software.

 3. This notice may not be removed or altered from any source distribution.
 */
#if TARGET_OS_IPHONE &amp;&amp; !TARGET_IPHONE_SIMULATOR
#define VFP_CLOBBER_S0_S31 "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8",  \
"s9", "s10", "s11", "s12", "s13", "s14", "s15", "s16",  \
"s17", "s18", "s19", "s20", "s21", "s22", "s23", "s24",  \
"s25", "s26", "s27", "s28", "s29", "s30", "s31"
#define VFP_VECTOR_LENGTH(VEC_LENGTH) "fmrx    r0, fpscr                    \n\t" \
"bic     r0, r0, #0x00370000               \n\t" \
"orr     r0, r0, #0x000" #VEC_LENGTH "0000 \n\t" \
"fmxr    fpscr, r0                         \n\t"
#define VFP_VECTOR_LENGTH_ZERO "fmrx    r0, fpscr            \n\t" \
"bic     r0, r0, #0x00370000  \n\t" \
"fmxr    fpscr, r0            \n\t"
#endif
static inline void Matrix3DMultiply(Matrix3D m1, Matrix3D m2, Matrix3D result)
{
#if TARGET_OS_IPHONE &amp;&amp; !TARGET_IPHONE_SIMULATOR
    __asm__ __volatile__ ( VFP_VECTOR_LENGTH(3)

                  // Interleaving loads and adds/muls for faster calculation.
                  // Let A:=src_ptr_1, B:=src_ptr_2, then
                  // function computes A*B as (B^T * A^T)^T.

                  // Load the whole matrix into memory.
                  "fldmias  %2, {s8-s23}    \n\t"
                  // Load first column to scalar bank.
                  "fldmias  %1!, {s0-s3}    \n\t"
                  // First column times matrix.
                  "fmuls s24, s8, s0        \n\t"
                  "fmacs s24, s12, s1       \n\t"

                  // Load second column to scalar bank.
                  "fldmias %1!,  {s4-s7}    \n\t"

                  "fmacs s24, s16, s2       \n\t"
                  "fmacs s24, s20, s3       \n\t"
                  // Save first column.
                  "fstmias  %0!, {s24-s27}  \n\t" 

                  // Second column times matrix.
                  "fmuls s28, s8, s4        \n\t"
                  "fmacs s28, s12, s5       \n\t"

                  // Load third column to scalar bank.
                  "fldmias  %1!, {s0-s3}    \n\t"

                  "fmacs s28, s16, s6       \n\t"
                  "fmacs s28, s20, s7       \n\t"
                  // Save second column.
                  "fstmias  %0!, {s28-s31}  \n\t" 

                  // Third column times matrix.
                  "fmuls s24, s8, s0        \n\t"
                  "fmacs s24, s12, s1       \n\t"

                  // Load fourth column to scalar bank.
                  "fldmias %1,  {s4-s7}    \n\t"

                  "fmacs s24, s16, s2       \n\t"
                  "fmacs s24, s20, s3       \n\t"
                  // Save third column.
                  "fstmias  %0!, {s24-s27}  \n\t" 

                  // Fourth column times matrix.
                  "fmuls s28, s8, s4        \n\t"
                  "fmacs s28, s12, s5       \n\t"
                  "fmacs s28, s16, s6       \n\t"
                  "fmacs s28, s20, s7       \n\t"
                  // Save fourth column.
                  "fstmias  %0!, {s28-s31}  \n\t" 

                  VFP_VECTOR_LENGTH_ZERO
                  : "=r" (result), "=r" (m2)
                  : "r" (m1), "0" (result), "1" (m2)
                  : "r0", "cc", "memory", VFP_CLOBBER_S0_S31
                  );
#else
    result[0] = m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2] + m1[12] * m2[3];
    result[1] = m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2] + m1[13] * m2[3];
    result[2] = m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2] + m1[14] * m2[3];
    result[3] = m1[3] * m2[0] + m1[7] * m2[1] + m1[11] * m2[2] + m1[15] * m2[3];

    result[4] = m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6] + m1[12] * m2[7];
    result[5] = m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6] + m1[13] * m2[7];
    result[6] = m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6] + m1[14] * m2[7];
    result[7] = m1[3] * m2[4] + m1[7] * m2[5] + m1[11] * m2[6] + m1[15] * m2[7];

    result[8] = m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10] + m1[12] * m2[11];
    result[9] = m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10] + m1[13] * m2[11];
    result[10] = m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10] + m1[14] * m2[11];
    result[11] = m1[3] * m2[8] + m1[7] * m2[9] + m1[11] * m2[10] + m1[15] * m2[11];

    result[12] = m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12] * m2[15];
    result[13] = m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13] * m2[15];
    result[14] = m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14] * m2[15];
    result[15] = m1[3] * m2[12] + m1[7] * m2[13] + m1[11] * m2[14] + m1[15] * m2[15];
#endif
}</pre>
<p>既然我们有将矩阵相乘的能力，我们就可以合并多个矩阵。由于我们的矩阵相乘的方法是硬件加速的，而OpenGL  ES不支持矩阵与矩阵相乘的硬件加速，所以我们的方法应该比内嵌的变换方法要快一些<sup>3</sup> 。我们现在开始进行转移变换。</p>
<p><br class="spacer_" /></p>
<h4>自定义转移</h4>
<p><br class="spacer_" /></p>
<p>如果你还记得，我们使用4&#215;4矩阵而不是使用3&#215;3矩阵的一个原因是我们需要额外的一列用来保存转移信息。转移矩阵看上去像这样：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/translatematrix.png"><img title="translatematrix" src="../wp-content/uploads/2009/12/translatematrix.png" alt="translatematrix" width="291" height="222" /></a></p>
<p><br class="spacer_" /></p>
<p>我们可以将其转换为函数：</p>
<pre>static inline void Matrix3DSetTranslation(Matrix3D matrix, GLfloat xTranslate,
    GLfloat yTranslate, GLfloat zTranslate)
{
    matrix[0] = matrix[5] =  matrix[10] = matrix[15] = 1.0;
    matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0;
    matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0;
    matrix[11] = 0.0;
    matrix[12] = xTranslate;
    matrix[13] = yTranslate;
    matrix[14] = zTranslate;
}</pre>
<p>现在，我们怎样将其引入到 drawView: 方法中? 我们可以删除  glTranslatef()，替换为定义另一个矩阵并赋予适当的转移值，然后将其与当前矩阵相乘最后将结果加载到OpenGL的代码，对吗？</p>
<pre>    static Matrix3D    identityMatrix;
    Matrix3DSetIdentity(identityMatrix);
    static Matrix3D    translateMatrix;
    Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
    static Matrix3D    resultMatrix;
    Matrix3DMultiply(identityMatrix, translateMatrix, resultMatrix);
    glLoadMatrixf(resultMatrix);</pre>
<p>是的，这可以工作，但它做了一些不必要的工作。记住，如果你将矩阵与单元矩阵相乘，其结果一定是矩阵本身。所以当使用自定义矩阵时，如果要进行任何变换，我们不再需要首先加载单元矩阵。我们只需要创建变换矩阵并加载它：</p>
<pre>    static Matrix3D    translateMatrix;
    Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
    glLoadMatrixf(translateMatrix);</pre>
<p>由于不需要加载单元矩阵，此方法可以省去一些工作。另外，注意我将Matrix3D定义为static。我们不希望经常分配和解除内存分配。我们知道当程序运行时每一秒钟都需要用到此矩阵许多次，所以将其定义为static可以省去分配和解除内存分配的开销。</p>
<h3>自定义尺寸变换</h3>
<p><br class="spacer_" /></p>
<p>一个用于物体尺寸变换的矩阵像这样：</p>
<p><a href="../wp-content/uploads/2009/12/scalematrix.png"><img title="scalematrix" src="../wp-content/uploads/2009/12/scalematrix.png" alt="scalematrix" width="291" height="226" /></a></p>
<p><br class="spacer_" /></p>
<p>x, y, 或 z  为1.0表示在相应方向上尺寸无变化。3个值都为1.0代表单元矩阵。如果你赋值为2.0，则物在相应轴上尺寸加倍。我们可以将尺寸变换矩阵转化为OpenGL  ES矩阵，像这样：</p>
<pre>static inline void Matrix3DSetScaling(Matrix3D matrix, GLfloat xScale,
    GLfloat yScale, GLfloat zScale)
{
    matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0;
    matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0;
    matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
    matrix[0] = xScale;
    matrix[5] = yScale;
    matrix[10] = zScale;
    matrix[15] = 1.0;
}</pre>
<p>现在，因为我们需要使用超过一种变换，我们将使用矩阵乘法。要进行尺寸变换和旋转，我们需要将这两个矩阵相乘。删除先前代码中的 glScalef()  替换为下列代码：</p>
<pre>    static Matrix3D    translateMatrix;
    Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
    static Matrix3D    scaleMatrix;
    Matrix3DSetScaling(scaleMatrix, scale, scale, scale);
    static Matrix3D     resultMatrix;
    Matrix3DMultiply(translateMatrix, scaleMatrix, resultMatrix);
    glLoadMatrixf(resultMatrix);</pre>
<p>我们创建一个矩阵并赋予其适当的转移值。然后创建尺寸变换矩阵并赋值。然后将这两个矩阵相乘将结果加载到模型视口矩阵中。</p>
<h3>自定义旋转</h3>
<p><br class="spacer_" /></p>
<p>旋转稍微有点难度。我们可以创建绕各轴旋转的矩阵。我们已经知道绕Z轴旋转的矩阵像这样：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/zrotation1.png"><img title="zrotation" src="../wp-content/uploads/2009/12/zrotation1.png" alt="zrotation" width="396" height="195" /></a></p>
<p><br class="spacer_" /></p>
<p>绕X轴像这样：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/xaxisrotation.png"><img title="xaxisrotation" src="../wp-content/uploads/2009/12/xaxisrotation.png" alt="xaxisrotation" width="393" height="192" /></a></p>
<p><br class="spacer_" /></p>
<p>绕 Y轴旋转：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/yaxisrotation.png"><img title="yaxisrotation" src="../wp-content/uploads/2009/12/yaxisrotation.png" alt="yaxisrotation" width="393" height="193" /></a></p>
<p><br class="spacer_" /></p>
<p>这三种旋转写成函数：</p>
<pre>static inline void Matrix3DSetXRotationUsingRadians(Matrix3D matrix, GLfloat degrees)
{
    matrix[0] = matrix[15] = 1.0;
    matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0;
    matrix[7] = matrix[8] = 0.0;
    matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;

    matrix[5] = cosf(degrees);
    matrix[6] = -fastSinf(degrees);
    matrix[9] = -matrix[6];
    matrix[10] = matrix[5];
}
static inline void Matrix3DSetXRotationUsingDegrees(Matrix3D matrix, GLfloat degrees)
{
    Matrix3DSetXRotationUsingRadians(matrix, degrees * M_PI / 180.0);
}
static inline void Matrix3DSetYRotationUsingRadians(Matrix3D matrix, GLfloat degrees)
{
    matrix[0] = cosf(degrees);
    matrix[2] = fastSinf(degrees);
    matrix[8] = -matrix[2];
    matrix[10] = matrix[0];
    matrix[1] = matrix[3] = matrix[4] = matrix[6] = matrix[7] = 0.0;
    matrix[9] = matrix[11] = matrix[13] = matrix[12] = matrix[14] = 0.0;
    matrix[5] = matrix[15] = 1.0;
}
static inline void Matrix3DSetYRotationUsingDegrees(Matrix3D matrix, GLfloat degrees)
{
    Matrix3DSetYRotationUsingRadians(matrix, degrees * M_PI / 180.0);
}
static inline void Matrix3DSetZRotationUsingRadians(Matrix3D matrix, GLfloat degrees)
{
    matrix[0] = cosf(degrees);
    matrix[1] = fastSinf(degrees);
    matrix[4] = -matrix[1];
    matrix[5] = matrix[0];
    matrix[2] = matrix[3] = matrix[6] = matrix[7] = matrix[8] = 0.0;
    matrix[9] = matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
    matrix[10] = matrix[15] = 1.0;
}
static inline void Matrix3DSetZRotationUsingDegrees(Matrix3D matrix, GLfloat degrees)
{
    Matrix3DSetZRotationUsingRadians(matrix, degrees * M_PI / 180.0);
}</pre>
<p>对于各轴的旋转有两种方法，一种是使用弧度，另一种是使用角度。这三个矩阵被称为<em>Eular angle</em> 。有一个问题是我们必须按顺序对多个轴进行旋转，当我们对这三个角度进行旋转时，我们可能会遇到一种被称作<a href="http://www.topsyproxy.com/browse.php/be203aea/0b376beO/i8vZW4ud/2lraXBlZ/GlhLm9yZ/y93aWtpL/0dpbWJhb/F9sb2Nr/b0/">gimbal  lock</a>（框架自锁）的现像，它导致其中一个轴的旋转被锁定。为防止这种现象的发生，我们必须创建一个可以处理多轴旋转的矩阵。除了可以消除自锁的问题，它还能在物体需要绕多轴旋转时节省处理器的开销。</p>
<p><br class="spacer_" /></p>
<p>说实在的，我并不打算假装理解了此方法后面的数学机制。我读过一篇有关此机制的博士论文（<a href="http://www.topsyproxy.com/browse.php/be203aea/0b376beO/i8vZW4ud/2lraXBlZ/GlhLm9yZ/y93aWtpL/1F1YXRlc/m5pb24_3/D/b0/">四元数</a>的矩阵表示），但我无法完全理解所有的数学原理，所以我们就基于信任此多旋转矩阵可以正常工作（它确实如此）。此矩阵假定了一个指定的角度N以及一个由3个浮点数值表示的向量。此向量的各元素将被N乘，从而导致绕对应轴的旋转：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/multirotatematrix.png"><img title="multirotatematrix" src="../wp-content/uploads/2009/12/multirotatematrix.png" alt="multirotatematrix" width="664" height="122" /></a></p>
<p><br class="spacer_" /></p>
<p>此矩阵要求向量是以单元向量（即法线）方式被传递的，所以我们在为矩阵赋值前必须保证这点。表示为OpenGL矩阵，像这样：</p>
<pre>static inline void Matrix3DSetRotationByRadians(Matrix3D matrix, GLfloat angle,
    GLfloat x, GLfloat y, GLfloat z)
{
    GLfloat mag = sqrtf((x*x) + (y*y) + (z*z));
    if (mag == 0.0)
    {
        x = 1.0;
        y = 0.0;
        z = 0.0;
    }
    else if (mag != 1.0)
    {
        x /= mag;
        y /= mag;
        z /= mag;
    }

    GLfloat c = cosf(angle);
    GLfloat s = fastSinf(angle);
    matrix[3] = matrix[7] = matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
    matrix[15] = 1.0;

    matrix[0] = (x*x)*(1-c) + c;
    matrix[1] = (y*x)*(1-c) + (z*s);
    matrix[2] = (x*z)*(1-c) - (y*s);
    matrix[4] = (x*y)*(1-c)-(z*s);
    matrix[5] = (y*y)*(1-c)+c;
    matrix[6] = (y*z)*(1-c)+(x*s);
    matrix[8] = (x*z)*(1-c)+(y*s);
    matrix[9] = (y*z)*(1-c)-(x*s);
    matrix[10] = (z*z)*(1-c)+c;

}
static inline void Matrix3DSetRotationByDegrees(Matrix3D matrix, GLfloat angle,
    GLfloat x, GLfloat y, GLfloat z)
{
    Matrix3DSetRotationByRadians(matrix, angle * M_PI / 180.0, x, y, z);
}</pre>
<p><br class="spacer_" /></p>
<p>此多轴旋转的版本以glRotatef()一样地工作。</p>
<p><br class="spacer_" /></p>
<p>现在我们替换了3个内嵌函数，下面是使用自定义矩阵和变换函数的 drawView: 方法。新矩阵代码以<strong>粗体</strong>表示：</p>
<pre>- (void)drawView:(GLView*)view;
{

    static GLfloat  rot = 0.0;
    static GLfloat  scale = 1.0;
    static GLfloat  yPos = 0.0;
    static BOOL     scaleIncreasing = YES;

    // This is the same result as using Vertex3D, just faster to type and
    // can be made const this way
    static const Vertex3D vertices[]= {
        {0, -0.525731, 0.850651},             // vertices[0]
        {0.850651, 0, 0.525731},              // vertices[1]
        {0.850651, 0, -0.525731},             // vertices[2]
        {-0.850651, 0, -0.525731},            // vertices[3]
        {-0.850651, 0, 0.525731},             // vertices[4]
        {-0.525731, 0.850651, 0},             // vertices[5]
        {0.525731, 0.850651, 0},              // vertices[6]
        {0.525731, -0.850651, 0},             // vertices[7]
        {-0.525731, -0.850651, 0},            // vertices[8]
        {0, -0.525731, -0.850651},            // vertices[9]
        {0, 0.525731, -0.850651},             // vertices[10]
        {0, 0.525731, 0.850651}               // vertices[11]
    };

    static const Color3D colors[] = {
        {1.0, 0.0, 0.0, 1.0},
        {1.0, 0.5, 0.0, 1.0},
        {1.0, 1.0, 0.0, 1.0},
        {0.5, 1.0, 0.0, 1.0},
        {0.0, 1.0, 0.0, 1.0},
        {0.0, 1.0, 0.5, 1.0},
        {0.0, 1.0, 1.0, 1.0},
        {0.0, 0.5, 1.0, 1.0},
        {0.0, 0.0, 1.0, 1.0},
        {0.5, 0.0, 1.0, 1.0},
        {1.0, 0.0, 1.0, 1.0},
        {1.0, 0.0, 0.5, 1.0}
    };

    static const GLubyte icosahedronFaces[] = {
        1, 2, 6,
        1, 7, 2,
        3, 4, 5,
        4, 3, 8,
        6, 5, 11,
        5, 6, 10,
        9, 10, 2,
        10, 9, 3,
        7, 8, 9,
        8, 7, 0,
        11, 0, 1,
        0, 11, 4,
        6, 2, 10,
        1, 6, 11,
        3, 5, 10,
        5, 4, 11,
        2, 7, 9,
        7, 1, 0,
        3, 9, 8,
        4, 8, 0,
    };

    static const Vector3D normals[] = {
        {0.000000, -0.417775, 0.675974},
        {0.675973, 0.000000, 0.417775},
        {0.675973, -0.000000, -0.417775},
        {-0.675973, 0.000000, -0.417775},
        {-0.675973, -0.000000, 0.417775},
        {-0.417775, 0.675974, 0.000000},
        {0.417775, 0.675973, -0.000000},
        {0.417775, -0.675974, 0.000000},
        {-0.417775, -0.675974, 0.000000},
        {0.000000, -0.417775, -0.675973},
        {0.000000, 0.417775, -0.675974},
        {0.000000, 0.417775, 0.675973},
    };

    <strong>static Matrix3D    translateMatrix;
    Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
    static Matrix3D    scaleMatrix;
    Matrix3DSetScaling(scaleMatrix, scale, scale, scale);
    static Matrix3D     tempMatrix;
    Matrix3DMultiply(translateMatrix, scaleMatrix, tempMatrix);
    static Matrix3D    rotationMatrix;
    Matrix3DSetRotationByDegrees(rotationMatrix, rot, 1.0f, 1.0f, 1.0f);
    static Matrix3D    finalMatrix;
    Matrix3DMultiply(tempMatrix, rotationMatrix, finalMatrix);
    glLoadMatrixf(finalMatrix);</strong>

    glClearColor(0.0, 0.0, 0.05, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
    glEnable(GL_COLOR_MATERIAL);
    glEnableClientState(GL_NORMAL_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, vertices);
    glColorPointer(4, GL_FLOAT, 0, colors);
    glNormalPointer(GL_FLOAT, 0, normals);
    glDrawElements(GL_TRIANGLES, 60, GL_UNSIGNED_BYTE, icosahedronFaces);

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisable(GL_COLOR_MATERIAL);
    static NSTimeInterval lastDrawTime;
    if (lastDrawTime)
    {
        NSTimeInterval timeSinceLastDraw =
            [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
        rot+=50 * timeSinceLastDraw;  

        if (scaleIncreasing)
        {
            scale += timeSinceLastDraw;
            yPos += timeSinceLastDraw;
            if (scale &gt; 2.0)
                scaleIncreasing = NO;
        }
        else
        {
            scale -= timeSinceLastDraw;
            yPos -= timeSinceLastDraw;
            if (scale &lt; 1.0)
                scaleIncreasing = YES;

        }
    }
    lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}</pre>
<h4>诡异但很奇妙的自定义矩阵</h4>
<p><br class="spacer_" /></p>
<p>到目前为止，我仅仅展示了怎样重建OpenGL存在的功能，我们所作的只是基于矩阵相乘是硬件加速的这一原理对性能进行了一番小小的提升，但在99%的情况下，还不足以让我们如此大费周章。</p>
<p><br class="spacer_" /></p>
<p>但是，手工处理矩阵变换还有其他好处。例如，你可以创建OpenGL ES本身不支持的变换。比如，你可以进行<em>剪切变换。 </em>剪切本质上就是将物体沿两轴扭转。如果你对一个塔的轴进行扭转，那么它就会成为比萨斜塔。下面是剪切矩阵：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/shearmatrix.png"><img title="shearmatrix" src="../wp-content/uploads/2009/12/shearmatrix.png" alt="shearmatrix" width="427" height="196" /></a></p>
<p><br class="spacer_" /></p>
<p>下面是代码：</p>
<pre>static inline void Matrix3DSetShear(Matrix3D matrix, GLfloat xShear, GLfloat yShear)
{
    matrix[0] = matrix[5] =  matrix[10] = matrix[15] = 1.0;
    matrix[1] = matrix[2] = matrix[3] = 0.0;
    matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0;
    matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
    matrix[1] = xShear;
    matrix[4] = yShear;
}
</pre>
<p>如果我们应用剪切矩阵，我们将得到：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz0012.jpg"><img title="iPhone SimulatorScreenSnapz001" src="../wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz0012.jpg" alt="iPhone SimulatorScreenSnapz001" width="312" height="600" /></a></p>
<p><br class="spacer_" /></p>
<p>尝试一下用内嵌函数实现。你可以组合矩阵调用。例如，创建一个函数不需要任何矩阵乘法生成转移和尺寸变换。</p>
<p><br class="spacer_" /></p>
<h3>结论</h3>
<p><br class="spacer_" /></p>
<p>矩阵是一个很大而且经常让人误解的课题，许多人（包括我自己）都在殚精竭虑去理解它。希望本文能给你足够的帮助，而且还提供了一个矩阵相关的库，你可以用在自己的项目中。如果你希望下载自己尝试一下，请下载<a href="http://www.topsyproxy.com/browse.php/be203aea/0b376beO/i8vd3d3L/mlubmVyb/G9vcC5ia/XovY29kZ/S9QYXJ0N/lByb2plY/3Quemlw/b0/">fee</a><a href="../wp-content/uploads/2009/12/Part6Project.zip">Part6Project</a>l。我定义了两个常量，一个用来打开/关闭自定义变换，另一个用来打开/关闭剪切变换。它们在  <em>GLViewController.h </em>中:</p>
<pre>#define USE_CUSTOM_MATRICES 1
#define USE_SHEAR_TRANSFORM 1</pre>
<p>1为打开，0为关闭。另外我还更新了 <a href="http://www.topsyproxy.com/browse.php/be203aea/0b376beO/i8vd3d3L/mlubmVyb/G9vcC5ia/XovY29kZ/S9FbXB0e/SBPcGVuR/0wgRVMgQ/XBwbGljY/XRpb24ue/mlw/b0/">OpenGL  ES Xcode Tem</a><a href="../wp-content/uploads/2009/12/Empty%20OpenGL%20ES%20Application.zip">Empty%20OpenGL%20ES%20Application</a> 项目使其包括了新的矩阵函数，包括向量化矩阵乘法函数。如果你无法完全消化，你也无需担心。对于开发OpenGL  ES程序的99%的人来说，你不需要完全理解透视空间，同质坐标或线性变换，只需大体上的了解即可。</p>
<p><br class="spacer_" /></p>
<p><em>感谢 <a href="http://www.snappytouch.com/">Snappy Touch</a> 的 <a>Noel  Llopis</a>。</em></p>
<hr />
<p><strong>注脚</strong></p>
<ol>
<li>实际上，当你调用glLoadIdentity()时, 你加载了一个 4&#215;4 的单元矩阵。 </li>
<li>如果你希望使用 struct 而不是  typedef 时，你必须记住要以引用方式传递参数。struct并不会像数组一样自动以引用方式被传递。 </li>
<li>使用Shark测试，drawView: 方法从.7% 的处理时间降低为 .1%  的处理时间。对于此方法而言，速度有本质的提高，但对程序总体而言，影响不大。</li>
</ol>
<p><br class="spacer_" /></p>
<h6>原文见：<a href="http://www.topsyproxy.com/browse.php/be203aea/0b376beO/i8vaXBob/25lZGV2Z/WxvcG1lb/nQuYmxvZ/3Nwb3QuY/29tLzIwM/DkvMDYvb/3BlbmdsL/WVzLWZyb/20tZ3Jvd/W5kLXVwL/XBhcnQtN/18wNC5od/G1s/b0/">OpenGL  ES From the Ground Up, Part 7: Transformations and Matrices</a></h6>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%b8%83-%e5%8f%98%e6%8d%a2%e5%92%8c%e7%9f%a9%e9%98%b5/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>怎样编写Apple Push Notification服务器</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e6%80%8e%e6%a0%b7%e7%bc%96%e5%86%99apple-push-notification%e6%9c%8d%e5%8a%a1%e5%99%a8</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e6%80%8e%e6%a0%b7%e7%bc%96%e5%86%99apple-push-notification%e6%9c%8d%e5%8a%a1%e5%99%a8#comments</comments>
		<pubDate>Fri, 29 Jan 2010 04:32:45 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[编程]]></category>
		<category><![CDATA[网络]]></category>
		<category><![CDATA[Push]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=417</guid>
		<description><![CDATA[


iPhone OS 3.0一个引入注目的新特性是push notifications（推送通知），它允许向已安装相关应用程序的各设备直接发送消息。苹果在新闻提示或IM应用中展示了此特性，它也十分完美地适合于我们的服务器监视服务程序Server Density。



我们的程序提供一个选项，当你设定的某个服务器事件发生时，通知会直接发送到你的iPhone上。这是非常有用的因为它提醒用户立即打开我们的程序查看引起此警示的服务器详情。

Apple提供了有关实现和处理设备上提示消息的  iPhone OS 的详细代码文档 ，但它只包括消息提供者服务器端编程指南。

作为消息提供者，我们需要与 Apple推送通知服务 (APNS)连接以发送消息到iPhone。为减少电池使用，一个设备仅需维持与APNS的一个连接。

本教程将从代码的层面介绍关于怎样建立一个推送通知服务器以连接APNS并使用推送通知到我们的服务器监视iPhone程序上。我们是使用PHP进行开发的，我们的示例都是PHP 5兼容的。
  
基本结构

使用唯一的SSL许可证连接到APNS

循环通过你需要发送到消息
为各消息构建有效载荷
断开与 APNS的连接

远程通知数据的流程是单向的。提供者将包括客户程序设备令牌和有效载荷的数据打包，发送到APNS，然后APNS再将通知发送给最终设备。


- Apple 文档

  
限制

有效载荷限制为256字节 &#8211; 它包括了消息主体以及你希望传送带其他属性。推送通知并不适于传送大量的数据。例如，我们仅仅传送一条短消息通知服务器监视的事件已经被触发了。
APNS并不提供消息发送成功与否的回馈状态。一个原因是如果一个设备无法联系那么发送给它的消息将被存于队列中，然而只有最新发送的消息被存于队列中 &#8211; 覆盖了先前发送但不成功的消息。
推送通知不适合用于发送紧急通知，因为消息仅在设备具有wifi或手机服务连接的情况下才能被发送，这也是为什么我们推荐与其它方法如email或SMS一起使用的原因。
用来与APNS通讯的SSL许可证（下面将讨论）是在程序层生成的。本教程涉及到实现方法仅适于单个iPhone程序，所以如果你有多个程序，那么你需要修改代码使之适合于使用多个许可证。

  
设备令牌

每条推送消息都必须针对某特定设备。这是通过使用在你的iPhone程序中由APNS产生的唯一deviceToken（设备令牌）来实现的。一旦获取了此令牌，你需要将其存储于服务器而不是你的iPhone程序内。它看上去像这样：
c9d4c07c fbbc26d6 ef87a44d 53e16983 1096a5d5 fd825475 56659ddd f715defc

在我们的 Server Density iPhone 程序中，我们在程序启动时调用相应的令牌生成方法，然后通过 HTTP API 调用 传回给我们的服务器 。这将使得deviceToken存储于服务器的有关用户的数据库中，从而我们可以使用它与持有此设备的用户进行通讯。
  
反馈服务

Apple 还提供了一个 反馈服务 ，你应该定期查询。它提供了一个以前使用过但不再有效的（例如用户卸载了你的iPhone程序）设备令牌列表。你可以从你的数据库中删除这些设备令牌。

本教程不涉及反馈服务的使用。
  
许可证

要进行推送服务的第一件事就是获取推送许可证。它用来对你通过SSL与APNS通讯进行识别。

在Mac上生成 Apple推送通知SSL许可证：

登录到 iPhone Developer Connection Portal 并点击 App IDs
创建一个不使用通配符的 App ID 。通配符 ID 不能用于推送通知服务。例如，我们的iPhone程序ID像这样：  AB123346CD.com.serverdensity.iphone
点击App ID旁的“Configure”，然后按下按钮生产 推送通知许可证。根据“向导”指导的步骤生成一个签名并上传，最后下载生成的许可证。此步骤在 Apple文档中 也有谈到。
通过双击.cer文件将你的 aps_developer_identity.cer 引入Keychain中。
在Mac上启动 Keychain助手，然后在login [...]]]></description>
			<content:encoded><![CDATA[<div><!--end meta--></div>
<div>
<div>
<p><a href="http://www.apple.com/iphone/softwareupdate/">iPhone OS 3.0</a>一个引入注目的新特性是<a href="http://developer.apple.com/iphone/program/sdk/apns.html">push notifications</a>（推送通知），它允许向已安装相关应用程序的各设备直接发送消息。苹果在新闻提示或IM应用中展示了此特性，它也十分完美地适合于我们的服务器监视服务程序<a href="http://www.serverdensity.com/">Server Density</a>。</p>
<p><br class="spacer_" /></p>
<p><a href="http://boxedice.files.wordpress.com/2009/07/sd-iphone-push-alert.png"><img title="Server monitoring iPhone application alert view" src="http://boxedice.files.wordpress.com/2009/07/sd-iphone-push-alert.png?w=200&amp;h=300" alt="Server monitoring iPhone application alert view" width="200" height="300" /></a></p>
<p><span id="more-417"></span></p>
<p>我们的程序提供一个选项，当你设定的某个服务器事件发生时，通知会直接发送到你的iPhone上。这是非常有用的因为它提醒用户立即打开我们的程序查看引起此警示的服务器详情。</p>
<p><br class="spacer_" /></p>
<p>Apple提供了有关实现和处理设备上提示消息的 <a href="http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/IPhoneOSClientImp/IPhoneOSClientImp.html"> iPhone OS 的详细代码文档</a> ，但它只包括消息提供者服务器端编程指南。</p>
<p><br class="spacer_" /></p>
<p>作为消息提供者，我们需要与 Apple推送通知服务 (APNS)连接以发送消息到iPhone。为减少电池使用，一个设备仅需维持与APNS的一个连接。</p>
<p><br class="spacer_" /></p>
<p>本教程将从代码的层面介绍关于怎样建立一个推送通知服务器以连接APNS并使用推送通知到我们的服务器监视iPhone程序上。我们是使用PHP进行开发的，我们的示例都是PHP 5兼容的。</p>
<h3><strong> </strong> </h3>
<h3><strong>基本结构</strong></h3>
<p><br class="spacer_" /></p>
<p>使用唯一的SSL许可证连接到APNS</p>
<ol>
<li>循环通过你需要发送到消息</li>
<li>为各消息构建有效载荷</li>
<li>断开与 APNS的连接</li>
</ol>
<blockquote><p>远程通知数据的流程是单向的。提供者将包括客户程序设备令牌和有效载荷的数据打包，发送到APNS，然后APNS再将通知发送给最终设备。</p>
</blockquote>
<p><br class="spacer_" /></p>
<p>- <a href="http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html">Apple 文档</a></p>
<p><a href="http://boxedice.files.wordpress.com/2009/07/remote_notif_simple.jpg"><img title="APNS Flow" src="http://boxedice.files.wordpress.com/2009/07/remote_notif_simple.jpg?w=600&amp;h=82" alt="APNS Flow" width="600" height="82" /></a></p>
<h3><strong> </strong> </h3>
<h3><strong>限制</strong></h3>
<ul>
<li>有效载荷限制为256字节 &#8211; 它包括了消息主体以及你希望传送带其他属性。推送通知并不适于传送大量的数据。例如，我们仅仅传送一条短消息通知服务器监视的事件已经被触发了。</li>
<li>APNS并不提供消息发送成功与否的回馈状态。一个原因是如果一个设备无法联系那么发送给它的消息将被存于队列中，然而只有最新发送的消息被存于队列中 &#8211; 覆盖了先前发送但不成功的消息。</li>
<li>推送通知不适合用于发送紧急通知，因为消息仅在设备具有wifi或手机服务连接的情况下才能被发送，这也是为什么我们推荐与其它方法如email或SMS一起使用的原因。</li>
<li>用来与APNS通讯的SSL许可证（下面将讨论）是在程序层生成的。本教程涉及到实现方法仅适于单个iPhone程序，所以如果你有多个程序，那么你需要修改代码使之适合于使用多个许可证。</li>
</ul>
<h3><strong> </strong> </h3>
<h3><strong>设备令牌</strong></h3>
<p><br class="spacer_" /></p>
<p>每条推送消息都必须针对某特定设备。这是通过使用在你的iPhone程序中由APNS产生的唯一deviceToken（设备令牌）来实现的。一旦获取了此令牌，你需要将其存储于服务器而不是你的iPhone程序内。它看上去像这样：</p>
<p>c9d4c07c fbbc26d6 ef87a44d 53e16983 1096a5d5 fd825475 56659ddd f715defc</p>
<p><br class="spacer_" /></p>
<p>在我们的 Server Density iPhone 程序中，我们在程序启动时调用相应的令牌生成方法，然后通过 <a href="http://www.serverdensity.com/docs/api/functions/#iphone-setdevicetoken">HTTP API 调用</a> 传回给我们的服务器 。这将使得deviceToken存储于服务器的有关用户的数据库中，从而我们可以使用它与持有此设备的用户进行通讯。</p>
<h3><strong> </strong> </h3>
<h3><strong>反馈服务</strong></h3>
<p><br class="spacer_" /></p>
<p>Apple 还提供了一个 <a href="http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW3">反馈服务</a> ，你应该定期查询。它提供了一个以前使用过但不再有效的（例如用户卸载了你的iPhone程序）设备令牌列表。你可以从你的数据库中删除这些设备令牌。</p>
<p><br class="spacer_" /></p>
<p>本教程不涉及反馈服务的使用。</p>
<h3><strong> </strong> </h3>
<h3><strong>许可证</strong></h3>
<p><br class="spacer_" /></p>
<p>要进行推送服务的第一件事就是获取推送许可证。它用来对你通过SSL与APNS通讯进行识别。</p>
<p><br class="spacer_" /></p>
<p>在Mac上生成 Apple推送通知SSL许可证：</p>
<ol>
<li>登录到 <a href="http://developer.apple.com/iphone/manage/overview/index.action">iPhone Developer Connection Portal</a> 并点击 App IDs</li>
<li>创建一个不使用通配符的 App ID 。通配符 ID 不能用于推送通知服务。例如，我们的iPhone程序ID像这样：  AB123346CD.com.serverdensity.iphone</li>
<li>点击App ID旁的“Configure”，然后按下按钮生产 推送通知许可证。根据“向导”指导的步骤生成一个签名并上传，最后下载生成的许可证。此步骤在 <a href="http://developer.apple.com/iphone/prerelease/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ProvisioningDevelopment/ProvisioningDevelopment.html#//apple_ref/doc/uid/TP40008194-CH104-SW4">Apple文档中</a> 也有谈到。</li>
<li>通过双击.cer文件将你的 aps_developer_identity.cer 引入Keychain中。</li>
<li>在Mac上启动 Keychain助手，然后在login keychain中选择 Certificates分类。你将看到一个可扩展选项“Apple Development Push Services”</li>
<li>扩展此选项然后右击“Apple Development Push Services” > Export “Apple Development Push Services ID123”。保存为 apns-dev-cert.p12 文件。</li>
<li>扩展“Apple Development Push Services” 对“Private Key”做同样操作，保存为 apns-dev-key.p12 文件。</li>
<li>需要通过终端命令将这些文件转换为PEM格式：
<pre>openssl pkcs12 -clcerts -nokeys -out apns-dev-cert.pem -in apns-dev-cert.p12
openssl pkcs12 -nocerts -out apns-dev-key.pem -in apns-dev-key.p12</pre>
</li>
<li>如果你想要移除密码，要么在导出/转换时不要设定或者执行：
<pre>openssl rsa -in apns-dev-key.pem -out apns-dev-key-noenc.pem</pre>
</li>
<li>最后，你需要将键和许可文件合成为apns-dev.pem文件，此文件在连接到APNS时需要使用：
<pre>cat apns-dev-cert.pem apns-dev-key-noenc.pem > apns-dev.pem</pre>
</li>
</ol>
<p><br class="spacer_" /></p>
<p>将此文件保存为一个易记的名字，你有可能以后会用到它。上述步骤同样适合于生成产品许可证。</p>
<h3><strong> </strong> </h3>
<h3><strong>载荷内容</strong></h3>
<p><br class="spacer_" /></p>
<p><a href="http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html#//apple_ref/doc/uid/TP40008194-CH100-SW1">载荷</a> 格式化为遵循 RFC 4627标准的JSON格式。它由以下几部分组成：</p>
<ul>
<li>提示 &#8211; 显示于设备上的文本串</li>
<li>标识 &#8211; 设备屏幕中程序图标上显示的整数</li>
<li>声音 &#8211;  显示消息在设备的同时发出的声音的文本名字</li>
<li>本教程仅处理发送简单提示文本串，但也可以发送包括诸如显示自定义按钮等在内的各种选项的字典集。</li>
</ul>
<h3><strong> </strong> </h3>
<h3><strong>创建载荷</strong></h3>
<p><br class="spacer_" /></p>
<p>使用 PHP 很容易根据数组并 <a href="http://www.php.net/manual/en/function.json-encode.php">转换成 JSON</a>而创建载荷:</p>
<pre>$payload['aps'] = array('alert' => 'This is the alert text', 'badge' => 1, 'sound' => 'default');
$payload = json_encode($payload);</pre>
<p><br class="spacer_" /></p>
<p>显示 $payload 的内容可以看到传送到<span style="font-family: Georgia;">APNS 的</span> JSON字符串：</p>
<pre>{
     "aps" : { "alert" : "This is the alert text", "badge" : 1, "sound" : "default" }
}</pre>
<p><br class="spacer_" /></p>
<p>这将使消息显示于设备上，触发提升声音并将“1”置于程序图标上。默认按钮“Close”和“View”同时会显示于弹出窗口上。</p>
<p><br class="spacer_" /></p>
<p>对于 Server Density iPhone程序而言，让用户按下“View”直接进入产生此提示的服务器是很重要的，所以我们增加了额外的自定义值：</p>
<pre>$payload['aps'] = array('alert' => 'This is the alert text', 'badge' => 1, 'sound' => 'default');
$payload['server'] = array('serverId' => $serverId, 'name' => $name);
$output = json_encode($payload);</pre>
<p><br class="spacer_" /></p>
<p>当用户按下“View”后，自定义server值将被传递到设备中的程序。JSON 值如下：</p>
<pre>{
     "aps" : { "alert" : "This is the alert text", "badge" : 1, "sound" : "default" },
     "server" : { "serverId" : 1, "name" : "Server name")
}</pre>
<p><br class="spacer_" /></p>
<p>256字节的限制适用于整个载荷，包括自定义字典集。</p>
<h3><strong> </strong> </h3>
<h3><strong>原生接口</strong></h3>
<p><br class="spacer_" /></p>
<p>在Server Density中，一旦产生了一条提示，将建立一个载荷并插入队列中。因此有必要时我们可以同时发送多个载荷。</p>
<p><br class="spacer_" /></p>
<p>Apple推荐使用这种方法，因为如果你在发送各载荷时频繁连接和断开，APNS有可能会封锁你的IP。</p>
<p>如Apple <a href="http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW4">描述</a>:</p>
<blockquote><p>原生接口使用原生socket，具有二进制内容，采用数据流技术，不产生回馈。</p>
</blockquote>
<p><a href="http://boxedice.files.wordpress.com/2009/07/aps_provider_binary.jpg"><img title="APNS Binary Format" src="http://boxedice.files.wordpress.com/2009/07/aps_provider_binary.jpg?w=522&amp;h=111" alt="APNS Binary Format" width="522" height="111" /></a></p>
<h3><strong> </strong> </h3>
<h3><strong>打开连接</strong></h3>
<p><br class="spacer_" /></p>
<p>打开连接的 PHP 5代码如下：</p>
<pre>$apnsHost = 'gateway.sandbox.push.apple.com';
$apnsPort = 2195;
$apnsCert = 'apns-dev.pem';

$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', $apnsCert);

$apns = stream_socket_client('ssl://' . $apnsHost . ':' . $apnsPort, $error, $errorString, 2,
STREAM_CLIENT_CONNECT, $streamContext);</pre>
<p><br class="spacer_" /></p>
<p>如果发送错误，你可以参考$errorString。它也包括了SSL许可证不正确时的详细信息。</p>
<p><br class="spacer_" /></p>
<p>许可证文件处于执行的PHP代码的当前工作目录下，如果需要你可指定其绝对路径。</p>
<p>注意测试时应该使用开发许可证及sandbox。成品主机名为 gateway.push.apple.com ，而且你必须使用不同的产品许可证。</p>
<h3><strong> </strong> </h3>
<h3><strong>发送载荷</strong></h3>
<p><br class="spacer_" /></p>
<p>在此，我们循环整个载荷队列进行发送。构建发送到APNS的二进制内容简单示例如下：</p>
<pre>$apnsMessage = chr(0) . chr(0) . chr(32) . pack('H*', str_replace(' ', '', $deviceToken)) . chr(0) .
chr(strlen($payload)) . $payload;
fwrite($apns, $apnsMessage);</pre>
<p><br class="spacer_" /></p>
<p>注意 $deviceToken 是从数据库中提取并去除空格得到的。我们还应该检查是否$payload超过256个字节。</p>
<p><br class="spacer_" /></p>
<p>$apnsMessage 包括了正确的二进制载荷，而fwrite 将载荷写入当前活动的数据流连接中。</p>
<p><br class="spacer_" /></p>
<p>完成后，应关闭连接：</p>
<pre>socket_close($apns);
fclose($apns);</pre>
<p><strong> </strong> </p>
<p><strong>php-apns</strong></p>
<p><br class="spacer_" /></p>
<p>有一个开源服务器库<a href="http://code.google.com/p/php-apns/">php-apns</a>实现了以上所有功能，它依赖于 <a href="http://www.danga.com/memcached/">memcached</a>。我们不想使用任何第三方代码，所以完全自己编写了自己的服务器。我们使用自定义cron系统，几秒钟运行一次。</p>
<h5> </h5>
<h5>原文见：<a href="http://blog.boxedice.com/2009/07/10/how-to-build-an-apple-push-notification-provider-server-tutorial/">How to build an Apple Push Notification provider server (tutorial)</a></h5>
</div>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e6%80%8e%e6%a0%b7%e7%bc%96%e5%86%99apple-push-notification%e6%9c%8d%e5%8a%a1%e5%99%a8/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>

