<?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/%e7%bc%96%e7%a8%8b/feed" rel="self" type="application/rss+xml" />
	<link>http://www.iphone-geek.cn</link>
	<description>iPhone 新闻，编程，技巧与提示，代码，教程</description>
	<lastBuildDate>Sun, 25 Jul 2010 13:49:13 +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>以密码方式显示UITextField文本</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%a5%e5%af%86%e7%a0%81%e6%96%b9%e5%bc%8f%e6%98%be%e7%a4%bauitextfield%e6%96%87%e6%9c%ac</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%a5%e5%af%86%e7%a0%81%e6%96%b9%e5%bc%8f%e6%98%be%e7%a4%bauitextfield%e6%96%87%e6%9c%ac#comments</comments>
		<pubDate>Sun, 25 Jul 2010 13:44:14 +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=988</guid>
		<description><![CDATA[很简单，如下：
1textField.secureTextEntry = YES;


]]></description>
			<content:encoded><![CDATA[<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 /></div></td><td><div class="objc codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">textField.secureTextEntry <span style="color: #002200;">=</span> <span style="color: #a61390;">YES</span>;</div></td></tr></tbody></table></div>
</p>
<p><br class="spacer_" /></p>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%a5%e5%af%86%e7%a0%81%e6%96%b9%e5%bc%8f%e6%98%be%e7%a4%bauitextfield%e6%96%87%e6%9c%ac/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>iPhone用Expat XML解析器</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/iphone%e7%94%a8expat-xml%e8%a7%a3%e6%9e%90%e5%99%a8</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/iphone%e7%94%a8expat-xml%e8%a7%a3%e6%9e%90%e5%99%a8#comments</comments>
		<pubDate>Fri, 23 Jul 2010 10:29:09 +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=985</guid>
		<description><![CDATA[xml的解析运用的十分广泛，我以前也介绍过使用ticpp（tinyxml的c++版本）进行简单的xml解析，见跨平台代码分享之三–xml解析器，它是采用DOM进行解析。iPhone SDK中自带的NSXMLParsers也可进行xml解析，但是对于解析web server下载的大型文件时效率不够高，有一个第三方的objective  c expat wrapper 采用expat（SAX方式）进行解析，效率非常高。据有人测试，其效率相对于NSXMLParsers约提高了70-80%。

下面是其下载地址。


]]></description>
			<content:encoded><![CDATA[<p>xml的解析运用的十分广泛，我以前也介绍过使用ticpp（tinyxml的c++版本）进行简单的xml解析，见<a title="跨平台代码分享之三–xml解析器" href="编程/跨平台代码分享之三-–-xml解析器">跨平台代码分享之三–xml解析器</a>，它是采用DOM进行解析。iPhone SDK中自带的NSXMLParsers也可进行xml解析，但是对于解析web server下载的大型文件时效率不够高，有一个第三方的<a onclick="javascript:pageTracker._trackPageview('/outbound/article/http://www.robbiehanson.com/expat.html');" href="http://www.robbiehanson.com/expat.html">objective  c expat wrapper</a> 采用expat（SAX方式）进行解析，效率非常高。据有人测试，其效率相对于NSXMLParsers约提高了70-80%。</p>
<p><br class="spacer_" /></p>
<p>下面是其<a title="NSXMLParsers" href="http://github.com/zootreeves/iPhoneExpat">下载地址</a>。</p>
<p><br class="spacer_" /></p>
<p><br class="spacer_" /></p>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/iphone%e7%94%a8expat-xml%e8%a7%a3%e6%9e%90%e5%99%a8/feed</wfw:commentRss>
		<slash:comments>0</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>OpenAL同时处理大量声音</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/openal%e5%90%8c%e6%97%b6%e5%a4%84%e7%90%86%e5%a4%a7%e9%87%8f%e5%a3%b0%e9%9f%b3</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/openal%e5%90%8c%e6%97%b6%e5%a4%84%e7%90%86%e5%a4%a7%e9%87%8f%e5%a3%b0%e9%9f%b3#comments</comments>
		<pubDate>Thu, 27 May 2010 02:10:54 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[编程]]></category>
		<category><![CDATA[音频]]></category>
		<category><![CDATA[OpenAL]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=718</guid>
		<description><![CDATA[（注：这是我在编写MozartBrain时，参考过的一篇文章，虽然年代久远，但对于学习OpenAL编程还是具有指导意义。）

今天要讨论的话题是OpenAL同时处理大量的音效。当你使用OpenAL进行声音播放时，能够同时播放的声音有一个上限（换而言之，最大的source数目）。在iPhone上能够通过下面代码获得的最大source数目大约为32：

1alGenSources&#40;1, &#38;sourceID&#41;;

当你要求超过最大数目的source时，上面的语句将失败，但它并不产生错误。因此，不要要求超过32个source（注：这是iPhone OS 2.2上的数目，其他版本的OS可能会有些差别）

所以，这意味着什么？它意味着任何时候不管什么原因都不要同时播放超过32个音效。这确实是一个问题，如果你的目标是通过分别播放各种乐器来模拟整个交响乐团的演奏，那么你最好不要在iPhone上尝试这样做。
我以前的文章中提到建议为每个音效（音效缓存）分配一个source并在需要播放时都调用此source。这在大部分情形下都可以正常工作。但是，如果在你的应用程序中有超过32个音效时，你应该怎么做？我记得曾经提到过：你应该将未用的source移交给将要被播放的buffer。


这实际上非常简单，我将展示一下怎样实现！

首先，你需要预先创建并加载source。这并不是必须的，但我希望在我想要播放声音时，能够立刻进行，所以我必须预先准备好。

123456789101112131415161718// 注意: MAX_SOURCES 是你需要预先加载的source数目
&#160;// 应该小于32
-&#40;void&#41;preloadSources
&#160;&#123;
&#160; &#160; &#160;// lazy init of my data structure
&#160; &#160; &#160;if &#40;sources == nil&#41; sources = &#91;&#91;NSMutableArray alloc&#93; init&#93;;
&#160; &#160; &#160;// we want to allocate all the sources we will need up front
&#160; &#160; &#160;NSUInteger sourceCount = MAX_SOURCES;
&#160; &#160; &#160;NSInteger sourceIndex;
&#160; &#160; &#160;NSUInteger sourceID;

&#160; &#160; &#160;// build a [...]]]></description>
			<content:encoded><![CDATA[<p>（注：这是我在编写MozartBrain时，参考过的一篇文章，虽然年代久远，但对于学习OpenAL编程还是具有指导意义。）</p>
<p><br class="spacer_" /></p>
<p>今天要讨论的话题是OpenAL同时处理大量的音效。当你使用OpenAL进行声音播放时，能够同时播放的声音有一个上限（换而言之，最大的source数目）。在iPhone上能够通过下面代码获得的最大source数目大约为32：</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 /></div></td><td><div class="objc codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">alGenSources<span style="color: #002200;">&#40;</span>1, <span style="color: #002200;">&amp;</span>sourceID<span style="color: #002200;">&#41;</span>;</div></td></tr></tbody></table></div>
<p><br class="spacer_" /></p>
<p>当你要求超过最大数目的source时，上面的语句将失败，但它并不产生错误。因此，不要要求超过32个source（注：这是iPhone OS 2.2上的数目，其他版本的OS可能会有些差别）</p>
<p><br class="spacer_" /></p>
<p>所以，这意味着什么？它意味着任何时候不管什么原因都不要同时播放超过32个音效。这确实是一个问题，如果你的目标是通过分别播放各种乐器来模拟整个交响乐团的演奏，那么你最好不要在iPhone上尝试这样做。</p>
<p>我以前的文章中提到建议为每个音效（音效缓存）分配一个source并在需要播放时都调用此source。这在大部分情形下都可以正常工作。但是，如果在你的应用程序中有超过32个音效时，你应该怎么做？我记得曾经提到过：你应该将未用的source移交给将要被播放的buffer。</p>
<p><br class="spacer_" /></p>
<p><span id="more-718"></span></p>
<p>这实际上非常简单，我将展示一下怎样实现！</p>
<p><br class="spacer_" /></p>
<p>首先，你需要预先创建并加载source。这并不是必须的，但我希望在我想要播放声音时，能够立刻进行，所以我必须预先准备好。</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 /></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;">// 注意: MAX_SOURCES 是你需要预先加载的source数目</span><br />
&nbsp;<span style="color: #11740a; font-style: italic;">// 应该小于32</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>preloadSources<br />
&nbsp;<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp;<span style="color: #11740a; font-style: italic;">// lazy init of my data structure</span><br />
&nbsp; &nbsp; &nbsp;<span style="color: #a61390;">if</span> <span style="color: #002200;">&#40;</span>sources <span style="color: #002200;">==</span> <span style="color: #a61390;">nil</span><span style="color: #002200;">&#41;</span> sources <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/NSMutableArray_Class/"><span style="color: #400080;">NSMutableArray</span></a> alloc<span style="color: #002200;">&#93;</span> init<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp;<span style="color: #11740a; font-style: italic;">// we want to allocate all the sources we will need up front</span><br />
&nbsp; &nbsp; &nbsp;NSUInteger sourceCount <span style="color: #002200;">=</span> MAX_SOURCES;<br />
&nbsp; &nbsp; &nbsp;NSInteger sourceIndex;<br />
&nbsp; &nbsp; &nbsp;NSUInteger sourceID;<br />
<br />
&nbsp; &nbsp; &nbsp;<span style="color: #11740a; font-style: italic;">// build a bunch of sources and load them into our array.</span><br />
&nbsp; &nbsp; &nbsp;<span style="color: #a61390;">for</span> <span style="color: #002200;">&#40;</span>sourceIndex <span style="color: #002200;">=</span> <span style="color: #2400d9;">0</span>; sourceIndex &lt; sourceCount; sourceIndex<span style="color: #002200;">++</span><span style="color: #002200;">&#41;</span><br />
&nbsp; &nbsp; &nbsp;<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;alGenSources<span style="color: #002200;">&#40;</span>1, <span style="color: #002200;">&amp;</span>amp;sourceID<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span style="color: #002200;">&#91;</span>sources addObject<span style="color: #002200;">:</span><span style="color: #002200;">&#91;</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color: #400080;">NSNumber</span></a> numberWithUnsignedInt<span style="color: #002200;">:</span>sourceID<span style="color: #002200;">&#93;</span><span style="color: #002200;">&#93;</span>;<br />
&nbsp; &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>这实际上很简单，只是让openAL创建一系列 source 并保存它们的 ID。 你可以通过向alGenSources传递所需source数目，让 openAL一次创建所有source，然后将它们存储于 C 数组中,但这里我不想调用诸如malloc/free之类的函数，所有使用的是objc-c的方法。</p>
<p><br class="spacer_" /></p>
<p>老的声音播放方法如下：</p>
<p><br class="spacer_" /></p>
<p>// 获取声音ID，然后启动声音播放</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">&nbsp;<span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>playSound<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>soundKey<br />
&nbsp;<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp;<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color: #400080;">NSNumber</span></a> <span style="color: #002200;">*</span> numVal <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>soundDictionary objectForKey<span style="color: #002200;">:</span>soundKey<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp;<span style="color: #a61390;">if</span> <span style="color: #002200;">&#40;</span>numVal <span style="color: #002200;">==</span> <span style="color: #a61390;">nil</span><span style="color: #002200;">&#41;</span> <span style="color: #a61390;">return</span>;<br />
&nbsp; &nbsp; &nbsp;NSUInteger sourceID <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>numVal unsignedIntValue<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp;alSourcePlay<span style="color: #002200;">&#40;</span>sourceID<span style="color: #002200;">&#41;</span>;<br />
&nbsp;<span style="color: #002200;">&#125;</span></div></td></tr></tbody></table></div>
<p><br class="spacer_" /></p>
<p>但是由于以前我们是为每个buffer都分配一个source。现在我们不再会有预先分配好的source，所以必须在声音播放时进行分配。现在的播放方法如下：</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 /></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: #002200;">-</span> <span style="color: #002200;">&#40;</span>NSUInteger<span style="color: #002200;">&#41;</span>playSound<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>soundKey gain<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span>ALfloat<span style="color: #002200;">&#41;</span>gain pitch<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span>ALfloat<span style="color: #002200;">&#41;</span>pitch loops<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span><span style="color: #a61390;">BOOL</span><span style="color: #002200;">&#41;</span>loops<br />
&nbsp;<span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp;ALenum err <span style="color: #002200;">=</span> alGetError<span style="color: #002200;">&#40;</span><span style="color: #002200;">&#41;</span>; <span style="color: #11740a; font-style: italic;">// clear error code</span><br />
&nbsp; &nbsp; &nbsp;<span style="color: #11740a; font-style: italic;">// first, find the buffer we want to play</span><br />
&nbsp; &nbsp; &nbsp;<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color: #400080;">NSNumber</span></a> <span style="color: #002200;">*</span> numVal <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>soundLibrary objectForKey<span style="color: #002200;">:</span>soundKey<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp;<span style="color: #a61390;">if</span> <span style="color: #002200;">&#40;</span>numVal <span style="color: #002200;">==</span> <span style="color: #a61390;">nil</span><span style="color: #002200;">&#41;</span> <span style="color: #a61390;">return</span> <span style="color: #2400d9;">0</span>;<br />
&nbsp; &nbsp; &nbsp;NSUInteger bufferID <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>numVal unsignedIntValue<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp;<span style="color: #11740a; font-style: italic;">// now find an available source</span><br />
&nbsp; &nbsp; &nbsp;NSUInteger sourceID <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>self _nextAvailableSource<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp;<span style="color: #11740a; font-style: italic;">// make sure it is clean by resetting the source buffer to 0</span><br />
&nbsp; &nbsp; &nbsp;alSourcei<span style="color: #002200;">&#40;</span>sourceID, AL_BUFFER, 0<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; &nbsp;<span style="color: #11740a; font-style: italic;">// attach the buffer to the source</span><br />
&nbsp; &nbsp; &nbsp;alSourcei<span style="color: #002200;">&#40;</span>sourceID, AL_BUFFER, bufferID<span style="color: #002200;">&#41;</span>;          <span style="color: #11740a; font-style: italic;">// set the pitch and gain of the source</span><br />
&nbsp; &nbsp; &nbsp;alSourcef<span style="color: #002200;">&#40;</span>sourceID, AL_PITCH, pitch<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; &nbsp;alSourcef<span style="color: #002200;">&#40;</span>sourceID, AL_GAIN, gain<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; &nbsp;<span style="color: #11740a; font-style: italic;">// set the looping value</span><br />
&nbsp; &nbsp; &nbsp;<span style="color: #a61390;">if</span> <span style="color: #002200;">&#40;</span>loops<span style="color: #002200;">&#41;</span> <span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;alSourcei<span style="color: #002200;">&#40;</span>sourceID, AL_LOOPING, AL_TRUE<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; &nbsp;<span style="color: #002200;">&#125;</span> <span style="color: #a61390;">else</span> <span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;alSourcei<span style="color: #002200;">&#40;</span>sourceID, AL_LOOPING, AL_FALSE<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; &nbsp;<span style="color: #002200;">&#125;</span><br />
&nbsp; &nbsp; &nbsp;<span style="color: #11740a; font-style: italic;">// check to see if there are any errors</span><br />
&nbsp; &nbsp; &nbsp;err <span style="color: #002200;">=</span> alGetError<span style="color: #002200;">&#40;</span><span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; &nbsp;<span style="color: #a61390;">if</span> <span style="color: #002200;">&#40;</span>err <span style="color: #002200;">!=</span> <span style="color: #2400d9;">0</span><span style="color: #002200;">&#41;</span> <span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span style="color: #002200;">&#91;</span>self _error<span style="color: #002200;">:</span>err note<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;Error Playing Sound!&quot;</span><span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span style="color: #a61390;">return</span> <span style="color: #2400d9;">0</span>;<br />
&nbsp; &nbsp; &nbsp;<span style="color: #002200;">&#125;</span><br />
&nbsp; &nbsp; &nbsp;<span style="color: #11740a; font-style: italic;">// now play!</span><br />
&nbsp; &nbsp; &nbsp;alSourcePlay<span style="color: #002200;">&#40;</span>sourceID<span style="color: #002200;">&#41;</span>;<br />
&nbsp; &nbsp; &nbsp;<span style="color: #a61390;">return</span> sourceID; <span style="color: #11740a; font-style: italic;">// return the sourceID so I can stop loops easily</span><br />
&nbsp;<span style="color: #002200;">&#125;</span></div></td></tr></tbody></table></div>
<p><br class="spacer_" /></p>
<p>请注意，这个新方法不再直接从soundLibrary中获取sourceID，所以我们必须保存buffer而不是source。等等，下面一行代码在做什么：</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 /></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;">// now find an available source</span><br />
NSUInteger sourceID <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>self _nextAvailableSource<span style="color: #002200;">&#93;</span>;</div></td></tr></tbody></table></div>
<p><br class="spacer_" /></p>
<p>这实际上是调用我自己的方法，查找未被使用的source，然后返回，请看代码：</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 /></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: #002200;">-</span><span style="color: #002200;">&#40;</span>NSUInteger<span style="color: #002200;">&#41;</span>_nextAvailableSource <br />
<span style="color: #002200;">&#123;</span><br />
&nbsp;     NSInteger sourceState; <span style="color: #11740a; font-style: italic;">// a holder for the state of the current source</span><br />
&nbsp;     <span style="color: #11740a; font-style: italic;">// first check: find a source that is not being used at the moment.</span><br />
&nbsp;     <span style="color: #a61390;">for</span> <span style="color: #002200;">&#40;</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color: #400080;">NSNumber</span></a> <span style="color: #002200;">*</span> sourceNumber <span style="color: #a61390;">in</span> sources<span style="color: #002200;">&#41;</span> <span style="color: #002200;">&#123;</span><br />
&nbsp;         alGetSourcei<span style="color: #002200;">&#40;</span><span style="color: #002200;">&#91;</span>sourceNumber unsignedIntValue<span style="color: #002200;">&#93;</span>, AL_SOURCE_STATE, <span style="color: #002200;">&amp;</span>sourceState<span style="color: #002200;">&#41;</span>;<br />
&nbsp;         <span style="color: #11740a; font-style: italic;">// great! we found one! return it and shunt</span><br />
&nbsp;         <span style="color: #a61390;">if</span> <span style="color: #002200;">&#40;</span>sourceState <span style="color: #002200;">!=</span> AL_PLAYING<span style="color: #002200;">&#41;</span> <span style="color: #a61390;">return</span> <span style="color: #002200;">&#91;</span>sourceNumber unsignedIntValue<span style="color: #002200;">&#93;</span>;<br />
&nbsp;     <span style="color: #002200;">&#125;</span><br />
&nbsp;     <span style="color: #11740a; font-style: italic;">// in the case that all our sources are being used, we will find the first non-looping source</span><br />
&nbsp;     <span style="color: #11740a; font-style: italic;">// and return that.</span><br />
&nbsp;     <span style="color: #11740a; font-style: italic;">// first kick out an error</span><br />
      NSLog<span style="color: #002200;">&#40;</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;available source overrun, increase MAX_SOURCES&quot;</span><span style="color: #002200;">&#41;</span>;<br />
&nbsp;     NSInteger looping;<br />
&nbsp;     <span style="color: #a61390;">for</span> <span style="color: #002200;">&#40;</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color: #400080;">NSNumber</span></a> <span style="color: #002200;">*</span> sourceNumber <span style="color: #a61390;">in</span> sources<span style="color: #002200;">&#41;</span> <span style="color: #002200;">&#123;</span><br />
&nbsp;         alGetSourcei<span style="color: #002200;">&#40;</span><span style="color: #002200;">&#91;</span>sourceNumber unsignedIntValue<span style="color: #002200;">&#93;</span>, AL_LOOPING, <span style="color: #002200;">&amp;</span>looping<span style="color: #002200;">&#41;</span>;<br />
&nbsp;         <span style="color: #a61390;">if</span> <span style="color: #002200;">&#40;</span><span style="color: #002200;">!</span>looping<span style="color: #002200;">&#41;</span> <span style="color: #002200;">&#123;</span><br />
&nbsp;             <span style="color: #11740a; font-style: italic;">// we found one that is not looping, cut it short and return it</span><br />
&nbsp;             NSUInteger sourceID <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>sourceNumber unsignedIntValue<span style="color: #002200;">&#93;</span>;<br />
&nbsp;             alSourceStop<span style="color: #002200;">&#40;</span>sourceID<span style="color: #002200;">&#41;</span>;<br />
&nbsp;             <span style="color: #a61390;">return</span> sourceID;<br />
&nbsp;         <span style="color: #002200;">&#125;</span><br />
&nbsp;     <span style="color: #002200;">&#125;</span><br />
&nbsp;     <span style="color: #11740a; font-style: italic;">// what if they are all loops? arbitrarily grab the first one and cut it short</span><br />
&nbsp;     <span style="color: #11740a; font-style: italic;">// kick out another error</span><br />
      NSLog<span style="color: #002200;">&#40;</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;available source overrun, all used sources looping&quot;</span><span style="color: #002200;">&#41;</span>;<br />
&nbsp;     NSUInteger sourceID <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #002200;">&#91;</span>sources objectAtIndex<span style="color: #002200;">:</span>0<span style="color: #002200;">&#93;</span> unsignedIntegerValue<span style="color: #002200;">&#93;</span>;<br />
&nbsp;     alSourceStop<span style="color: #002200;">&#40;</span>sourceID<span style="color: #002200;">&#41;</span>;<br />
&nbsp;     <span style="color: #a61390;">return</span> sourceID;<br />
<span style="color: #002200;">&#125;</span></div></td></tr></tbody></table></div>
<p><br class="spacer_" /></p>
<p>在 99% 的情况下，它将在第一个循环时找到合适的source并返回。根据我的经验即使在大量使用音效的程序中，你也很少会遇到几个声音同时播放的情形（除非你的音频采样非常长），所以它将很快返回。</p>
<p><br class="spacer_" /></p>
<p>（译者注：实际上，并非完全正确，在我编写的一个未发表的吉他乐器游戏中，我的声音引擎经常遇到大量音效同时播放的情况。之所以会有这种情况发生，是由于你必须在适当的时候<strong>关闭</strong>声音。是的，播放声音非常容易，但要关闭，就有相当的学问。如果你在声音播放的中途直接关闭，那么我可以保证，你将听到爆音。我在App Store下载了许多乐器软件，有几个会产生明显的爆音，就是由于这个原因。要解决这个问题，可能有许多方法。经过我的实验，我的方法是在停止一个声音前，采用定时器逐渐减小音量或将souce移动到很远的地方，确保听不见，但不要停止声音。哪怕声音的音量只有0.00001，如果你关闭它，你仍然会听到爆音。你必须让声音采样自然结束，所以你采用的音效一定不能过长。这就是为什么有许多source会同时存在的原因。为减小source的同时使用的数量，造成source不够用的情况，还必须采用一些其他的技术，比如在将音量减小到接近0（注意不能为0，如果是0，就等同于关闭音量，会产生破音）时，将buffer中的指针指向buffer的结尾处，或采用streaming的方法，另外还可以采用source重用的技术。当然，这些不在本文讨论的范围之内了）</p>
<p><br class="spacer_" /></p>
<p>但是，如果所有source还在播放怎么办？有几个选择：容易的方法：增加max_sources直到问题解决；稍难一点的方法（仍然很容易）：实时生成新的source。</p>
<p><br class="spacer_" /></p>
<p>但是如果你用完了32个source，仍然没有适当的source可用时，最后几行代码将进行处理（译者注，如我上面注释的，这种解决方法并不完美，它很有可能会产生破音。但这只是一个简单的示例，你应根据具体情况进行处理），它假设新声音比旧声音更重要而且循环音比音效更重要：我们采用两个步骤找到需要停止的声音。</p>
<p><br class="spacer_" /></p>
<p>首先我们找到一个非循环音，停止它如何返回其source。第二种情况如果有MAX个循环音在同时播放，那么找到第一个source，停止并返回其source （译者注：其实这也是与你程序的具体情况相关的，你可能会需要采用特殊的算法来决定需要停止的source，比如最远的声音等）。</p>
<p><br class="spacer_" /></p>
<p>（译者注：实际上音效处理的算法是非常复杂，完全看你程序的具体情况。这里的代码只是简单的示例而已。还是那句话：<strong>播放声音很容易，但要正确地关闭声音，一定要仔细考虑</strong>！）</p>
<p><br class="spacer_" /></p>
<h5>原文见：<a href="http://benbritten.com/2009/05/02/lots-and-lots-of-sounds-in-openal/">Lots and Lots of sounds in openAL</a></h5>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/openal%e5%90%8c%e6%97%b6%e5%a4%84%e7%90%86%e5%a4%a7%e9%87%8f%e5%a3%b0%e9%9f%b3/feed</wfw:commentRss>
		<slash:comments>0</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>4</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>3</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>OpenGL ES纹理尺寸限制的处理方法</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/opengl-es%e7%ba%b9%e7%90%86%e5%b0%ba%e5%af%b8%e9%99%90%e5%88%b6%e7%9a%84%e5%a4%84%e7%90%86%e6%96%b9%e6%b3%95</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/opengl-es%e7%ba%b9%e7%90%86%e5%b0%ba%e5%af%b8%e9%99%90%e5%88%b6%e7%9a%84%e5%a4%84%e7%90%86%e6%96%b9%e6%b3%95#comments</comments>
		<pubDate>Tue, 09 Mar 2010 03:13:34 +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=916</guid>
		<description><![CDATA[大家都知道，OpenGL ES对纹理的尺寸有限制，就是长和宽都必须是2的整数次幂。（实际上OpenGL都有此限制，但有一些扩展可以解决此问题）。因此处理方案有两种：


将纹理尺寸限制为2的整数次幂。比如，我有一个480&#215;320的背景图案，我可以用Photoshop将画布设置为512&#215;512，在纹理映射时只使用480&#215;320部分。当然我也可以将多个图案合成在一个纹理中，在纹理映射时根据图案的位置进行映射。 
仍然使用正常的图像尺寸，但在使用时进行转换。下面是源代码：


1234567891011121314151617181920212223242526&#160; &#160; &#160; &#160; // 首先调整纹理的长和宽为2的整数次幂 &#160; &#160; &#160; &#160; 
&#160; &#160; &#160; &#160; if&#40; &#40;_width != 1&#41; &#38;&#38; &#40;_width &#38; &#40;_width - 1&#41;&#41; &#41;
&#160; &#160; &#160; &#160; &#123;
&#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; i = 1;
&#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; while&#40;&#40;sizeToFit ? 2 * i : i&#41; [...]]]></description>
			<content:encoded><![CDATA[<p>大家都知道，OpenGL ES对纹理的尺寸有限制，就是长和宽都必须是2的整数次幂。（实际上OpenGL都有此限制，但有一些扩展可以解决此问题）。因此处理方案有两种：</p>
<p><br class="spacer_" /></p>
<ol>
<li>将纹理尺寸限制为2的整数次幂。比如，我有一个480&#215;320的背景图案，我可以用Photoshop将画布设置为512&#215;512，在纹理映射时只使用480&#215;320部分。当然我也可以将多个图案合成在一个纹理中，在纹理映射时根据图案的位置进行映射。 </li>
<li>仍然使用正常的图像尺寸，但在使用时进行转换。下面是源代码：</li>
</ol>
<p><br class="spacer_" /></p>
<p><div class="codecolorer-container cpp 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 /></div></td><td><div class="cpp codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #666666;">// 首先调整纹理的长和宽为2的整数次幂 &nbsp; &nbsp; &nbsp; &nbsp; </span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #0000ff;">if</span><span style="color: #008000;">&#40;</span> <span style="color: #008000;">&#40;</span>_width <span style="color: #000040;">!</span><span style="color: #000080;">=</span> 1<span style="color: #008000;">&#41;</span> <span style="color: #000040;">&amp;&amp;</span> <span style="color: #008000;">&#40;</span>_width <span style="color: #000040;">&amp;</span> <span style="color: #008000;">&#40;</span>_width <span style="color: #000040;">-</span> 1<span style="color: #008000;">&#41;</span><span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#41;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #008000;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i <span style="color: #000080;">=</span> <span style="color: #0000dd;">1</span><span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #0000ff;">while</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#40;</span>sizeToFit <span style="color: #008080;">?</span> 2 <span style="color: #000040;">*</span> i <span style="color: #008080;">:</span> i<span style="color: #008000;">&#41;</span> <span style="color: #000080;">&lt;</span> _width<span style="color: #008000;">&#41;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i <span style="color: #000040;">*</span><span style="color: #000080;">=</span> <span style="color: #0000dd;">2</span><span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _width <span style="color: #000080;">=</span> i<span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #008000;">&#125;</span><br />
<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #0000ff;">if</span><span style="color: #008000;">&#40;</span> <span style="color: #008000;">&#40;</span>_height <span style="color: #000040;">!</span><span style="color: #000080;">=</span> 1<span style="color: #008000;">&#41;</span> <span style="color: #000040;">&amp;&amp;</span> <span style="color: #008000;">&#40;</span>_height <span style="color: #000040;">&amp;</span> <span style="color: #008000;">&#40;</span>_height <span style="color: #000040;">-</span> 1<span style="color: #008000;">&#41;</span><span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#41;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #008000;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i <span style="color: #000080;">=</span> <span style="color: #0000dd;">1</span><span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #0000ff;">while</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#40;</span>sizeToFit <span style="color: #008080;">?</span> 2 <span style="color: #000040;">*</span> i <span style="color: #008080;">:</span> i<span style="color: #008000;">&#41;</span> <span style="color: #000080;">&lt;</span> _height<span style="color: #008000;">&#41;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i <span style="color: #000040;">*</span><span style="color: #000080;">=</span> <span style="color: #0000dd;">2</span><span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _height <span style="color: #000080;">=</span> i<span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #008000;">&#125;</span><br />
<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #666666;">// 如果调整后的图像尺寸大于最大纹理尺寸（1024），那么需要缩小</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #0000ff;">while</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#40;</span>_width <span style="color: #000040;">&amp;</span>gt<span style="color: #008080;">;</span> kMaxTextureSize<span style="color: #008000;">&#41;</span> <span style="color: #000040;">||</span> <span style="color: #008000;">&#40;</span>_height <span style="color: #000040;">&amp;</span>gt<span style="color: #008080;">;</span> kMaxTextureSize<span style="color: #008000;">&#41;</span><span style="color: #008000;">&#41;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #008000;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _width <span style="color: #000040;">/</span><span style="color: #000080;">=</span> <span style="color: #0000dd;">2</span><span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _height <span style="color: #000040;">/</span><span style="color: #000080;">=</span> <span style="color: #0000dd;">2</span><span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; transform <span style="color: #000080;">=</span> CGAffineTransformScale<span style="color: #008000;">&#40;</span>transform, 0.5, 0.5<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; imageSize.<span style="color: #007788;">x</span> <span style="color: #000040;">*</span><span style="color: #000080;">=</span> <span style="color:#800080;">0.5</span><span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; imageSize.<span style="color: #007788;">y</span> <span style="color: #000040;">*</span><span style="color: #000080;">=</span> <span style="color:#800080;">0.5</span><span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #008000;">&#125;</span></div></td></tr></tbody></table></div>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/opengl-es%e7%ba%b9%e7%90%86%e5%b0%ba%e5%af%b8%e9%99%90%e5%88%b6%e7%9a%84%e5%a4%84%e7%90%86%e6%96%b9%e6%b3%95/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>iPhone不是Mac Pro</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/iphone%e4%b8%8d%e6%98%afmac-pro</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/iphone%e4%b8%8d%e6%98%afmac-pro#comments</comments>
		<pubDate>Tue, 09 Feb 2010 05:21:03 +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=359</guid>
		<description><![CDATA[本文是关于通过Objective C++的强大功能提高iPhone程序的性能。通过讨论现实世界中Savoy的Spots应用程序遇到的问题，本文展示了通过3个步骤对程序进行优化使其顺利运行的过程。




使用Objective C++提高iPhone应用程序的性能

我十分喜爱软件开发的一个原因是总是会遇到这样那样的问题，而总能找到相应的解决方法。在开发Spots时，我多次遇到了UI设计的问题。为  iPhone设计一个精简的用户界面是不容易的。比如，我应该提供一个提供者过滤器（指Hotspot）按钮或将其放在Settings.app中？关于这个问题，我至少改变了二十次主意？

本文不是关于用户界面设计而是有关编程与性能。本文并不是针对用户界面而是有关编程和性能。一个最大的难题是地图的绘制（屏幕下方的长方形）。经过几种方法的试用，我发现在绘制230，000个点时，只有使用OpenGL以及大量的旁门左道才能使绘图的性能勉强被接受。根据放大的级别以及可见点点数量，我最后使用了三种不同的技术。由于我需要地图交互动作（拖动和缩放）尽可能地平滑，理想情况下为60HZ，性能是最为重要的问题。



本文中我将分享一些能提高性能的技术。虽然它们都很简单和直接，但因为在Cocoa中很少见，所以我还是花了不少时间。例如，尽管C++和Objective  C可以非常高效地混合使用，但我在Cocoa中并没有见到许多C++。当你的目标是高性能时，Objective  C++是纯Cocoa的很好的扩展。而在iPhone上，性能是应该首先需要考虑到因素。

难题

对于分布在一个矩形区域包括230，000个热点位置的地图（麦卡托投影的世界地图 the world map in mercator  projection），首先必须清楚当前地图矩形区域中有多少个可见点，因为此数值可以决定使用何种绘图技术。指标值必须归一，因此整个地图覆盖了一个{{0.0,  1.0}, {0.0, 1.0}}的矩形区域。通常情况下，程序使用的地图矩形是很小的：通常小于整个地图宽度和高度的百分之二。




但是我怎样有效地在数据库成千上万的点中找到可见点的数量？

我准备了一个XCode项目，它包括了这里讨论的所有代码。此示例项目并未包括了所有点的原始数据库。它仅包含了一些世界地图上的随机数据。虽然与真实情况有所区别，但它们的处理方法与我收集的原始数据的大同小异。

第一种方法: 普通Cocoa方法

第一种方法十分简单而且并没有采用十分高效的方法。使用Object C对象（@class  Spot）来表示位置，它们以NSArrary的形式被保存在一个数据库对象中（@class SpotsDB1）。
@interface Spot : NSObject {
    CGPoint _position;
}
@property (nonatomic) CGPoint position;
@end

@interface SpotsDB1 : NSObject {
    NSArray* _spots;
}
- (NSUInteger)countSpotsInRect:(CGRect)rect;
@end

为进行一个指定CGRect内地位置计数，数据库对象简单地循环整个数组并检查各位置。

NSUInteger count = 0;
 for (Spot* spot in _spots) {
 [...]]]></description>
			<content:encoded><![CDATA[<p>本文是关于通过Objective C++的强大功能提高iPhone程序的性能。通过讨论现实世界中Savoy的<a href="http://www.savoysoftware.com/spots/">Spots</a>应用程序遇到的问题，本文展示了通过3个步骤对程序进行优化使其顺利运行的过程。</p>
<p><br class="spacer_" /></p>
<p><a href="http://www.savoysoftware.com/spots/"><img title="spots-icon-big" src="http://savoysoftware.com/blog/wp-content/uploads/2008/12/spots-icon-big.png" alt="Spots" width="165" height="165" /></a></p>
<p><br class="spacer_" /></p>
<p><img title="More..." src="../wp-includes/js/tinymce/plugins/wordpress/img/trans.gif" alt="" /><span id="more-359"></span></p>
<h3><strong>使用Objective C++提高iPhone应用程序的性能</strong></h3>
<p><br class="spacer_" /></p>
<p>我十分喜爱软件开发的一个原因是总是会遇到这样那样的问题，而总能找到相应的解决方法。在开发Spots时，我多次遇到了UI设计的问题。为  iPhone设计一个精简的用户界面是不容易的。比如，我应该提供一个提供者过滤器（指Hotspot）按钮或将其放在Settings.app中？关于这个问题，我至少改变了二十次主意？</p>
<p><br class="spacer_" /></p>
<p>本文不是关于用户界面设计而是有关编程与性能。本文并不是针对用户界面而是有关编程和性能。一个最大的难题是地图的绘制（屏幕下方的长方形）。经过几种方法的试用，我发现在绘制230，000个点时，只有使用OpenGL以及大量的旁门左道才能使绘图的性能勉强被接受。根据放大的级别以及可见点点数量，我最后使用了三种不同的技术。由于我需要地图交互动作（拖动和缩放）尽可能地平滑，理想情况下为60HZ，性能是最为重要的问题。</p>
<p><br class="spacer_" /></p>
<p><img title="map_spots" src="http://savoysoftware.com/blog/wp-content/uploads/2009/01/map_spots.png" alt="map_spots" width="320" height="211" /></p>
<p><br class="spacer_" /></p>
<p>本文中我将分享一些能提高性能的技术。虽然它们都很简单和直接，但因为在Cocoa中很少见，所以我还是花了不少时间。例如，尽管C++和Objective  C可以非常高效地混合使用，但我在Cocoa中并没有见到许多C++。当你的目标是高性能时，Objective  C++是纯Cocoa的很好的扩展。而在iPhone上，性能是应该首先需要考虑到因素。</p>
<p><br class="spacer_" /></p>
<h4><strong>难题</strong></h4>
<p><br class="spacer_" /></p>
<p>对于分布在一个矩形区域包括230，000个热点位置的地图（麦卡托投影的世界地图 the world map in mercator  projection），首先必须清楚当前地图矩形区域中有多少个可见点，因为此数值可以决定使用何种绘图技术。指标值必须归一，因此整个地图覆盖了一个{{0.0,  1.0}, {0.0, 1.0}}的矩形区域。通常情况下，程序使用的地图矩形是很小的：通常小于整个地图宽度和高度的百分之二。</p>
<p><br class="spacer_" /></p>
<p><br class="spacer_" /></p>
<p><img title="map" src="http://savoysoftware.com/blog/wp-content/uploads/2009/01/map.png" alt="map" width="453" height="343" /></p>
<p><br class="spacer_" /></p>
<p>但是我怎样有效地在数据库成千上万的点中找到可见点的数量？</p>
<p><br class="spacer_" /></p>
<p>我准备了一个<a href="http://savoysoftware.com/blog/wp-content/uploads/2009/01/spotsdb.zip">XCode项目</a>，它包括了这里讨论的所有代码。此示例项目并未包括了所有点的原始数据库。它仅包含了一些世界地图上的随机数据。虽然与真实情况有所区别，但它们的处理方法与我收集的原始数据的大同小异。</p>
<p><br class="spacer_" /></p>
<h5>第一种方法: 普通Cocoa方法</h5>
<p><br class="spacer_" /></p>
<p>第一种方法十分简单而且并没有采用十分高效的方法。使用Object C对象（@class  Spot）来表示位置，它们以NSArrary的形式被保存在一个数据库对象中（@class SpotsDB1）。</p>
<pre>@interface Spot : NSObject {
    CGPoint _position;
}
@property (nonatomic) CGPoint position;
@end

@interface SpotsDB1 : NSObject {
    NSArray* _spots;
}
- (NSUInteger)countSpotsInRect:(CGRect)rect;
@end</pre>
<p><br class="spacer_" /></p>
<p>为进行一个指定CGRect内地位置计数，数据库对象简单地循环整个数组并检查各位置。</p>
<p><br class="spacer_" /></p>
<p>NSUInteger count = 0;<br />
 for (Spot* spot in _spots) {<br />
 if  (CGRectContainsPoint(aRect, spot.position))<br />
 ++count;<br />
 }</p>
<p><br class="spacer_" /></p>
<p>使用上述代码，在总数为230，000个点的区域上的任意一个{0.02,  0.02}矩形内进行计数大概需要250毫秒。太糟糕了，如果你的目标是刷新率60Hz，那么你只有16  ms绘制一帧。使得此方案过慢的原因不仅在于其缺乏聪明的算法，而且在循环中浪费了许多时间在调用了几个函数上。另一个对性能产生负面影响的因素是位置被保存在NSArray中的对象，这是由于对象占用了大量的内存以及数组指针的非直接性导致了处理器时间的增加。</p>
<p><br class="spacer_" /></p>
<h5>第二种方法: 简单优化</h5>
<p><br class="spacer_" /></p>
<p>为使同样的方案运行得更快，我们只需简单地将位置存储与一个更为紧凑的数据结构中：一个CGPoint结构的标准C数组。为使用方便，我们将C数组保存在一个NSMutableData  对象中。</p>
<p><br class="spacer_" /></p>
<p>_spotCount = [spots count];<br />
 _spotsData = [[NSMutableData alloc]  init];<br />
 for (Spot* spot in spots) {<br />
 CGPoint p = spot.position;<br />
 [data appendBytes:&amp;p length:sizeof(CGPoint)];<br />
 }</p>
<p><br class="spacer_" /></p>
<p>消除了循环内函数调用的负面影响，我们使得性能有四倍的提升：</p>
<p><br class="spacer_" /></p>
<p>CGFloat xMin = CGRectGetMinX(aRect);<br />
 CGFloat yMin =  CGRectGetMinY(aRect);<br />
 CGFloat xMax = CGRectGetMaxX(aRect);<br />
 CGFloat yMax =  CGRectGetMaxY(aRect);</p>
<p>CGPoint* begin = (CGPoint*)[_spotsData  bytes];<br />
 CGPoint* end = begin + _spotCount;</p>
<p>NSUInteger count =  0;<br />
 for (CGPoint* i = begin; i != end; ++i) {<br />
 if (i-&gt;x &gt; xMin  &amp;&amp; i-&gt;y &gt; yMin &amp;&amp; i-&gt;x &lt; xMax &amp;&amp; i-&gt;y  &lt; yMax)<br />
 ++count;<br />
 }</p>
<p><br class="spacer_" /></p>
<p>在没有改变基本算法的基础上，点计数的计算现在只需55毫秒，这大概比原来方法快了四倍。另一个好处是现在使用的内存是原来版本的一半。</p>
<h5>第三种方法：算法优化</h5>
<p><br class="spacer_" /></p>
<p>当然，还有许多聪明的方法来优化平面地图点的查询。例如，你可以使用四边形树来保存这些点。在本例中我使用了一种比较简单的解决方案，所以没有涉及数据存储的方式。我使用了C++的内置功能所以甚至不需要写任何代码来处理数据的存储。</p>
<p><br class="spacer_" /></p>
<p><img title="sorted" src="http://savoysoftware.com/blog/wp-content/uploads/2009/01/sorted.png" alt="sorted" width="355" height="343" /></p>
<p><br class="spacer_" /></p>
<p>我的设想是将数组中的位置从左至右分类。这样做的好处是确定指定正方形的左右边界十分快速（见下图）。当数组中边界已知时，仅需要检查边界间的点。</p>
<p><br class="spacer_" /></p>
<p><img title="sorted2" src="http://savoysoftware.com/blog/wp-content/uploads/2009/01/sorted2.png" alt="sorted2" width="355" height="343" /></p>
<p><br class="spacer_" /></p>
<p>所以此算法是有关快速寻找边界的。我们使用的是<a href="http://en.wikipedia.org/wiki/Binary_search">二进制搜索</a>，  它适用于已排序的数组。它并不检查每个值以找到匹配的位置，而是直接跳到数组的中点，检查其值是小于还是大于给点值，然后跳到剩余部分的中心点，重复以上步骤。使用这种方法，二进制搜索需要  log2(n)的时间来查找最佳元素。对于一个拥有230，000个位置的数组，只需22次搜索！回到代码，我们发现上述算法的实现是很简单的。当然，我们必须为数据库排序：</p>
<pre>// comparison function for array sorting
NSInteger leftToRight(Spot* a, Spot* b, void* context) {
    CGFloat xa = a.position.x;
    CGFloat xb = b.position.x;
    if (xa &lt; xb)
        return NSOrderedAscending;
    return xa &gt; xb ? NSOrderedDescending : NSOrderedSame;
}

...

spots = [spots sortedArrayUsingFunction:leftToRight context:NULL];</pre>
<p><br class="spacer_" /></p>
<p>现在是有趣的部分：使用C++进行二进制搜索。我们使用了#include &lt;algorithm&gt;中的库函数  std::lower_bound。它需要一个起点，终点，要查找的值以及比较函数。它返回比较为false的第一个值。起点和终点可以是简单的<span style="font-family: 微软雅黑;">指向数组的C指针。</span></p>
<p><br class="spacer_" /></p>
<p>#include &lt;algorithm&gt;<br />
 &#8230;</p>
<p>bool cmpX(const  CGPoint&amp; a, const CGPoint&amp; b) {<br />
 return a.x &lt;  b.x;<br />
 }<br />
 &#8230;</p>
<p>CGFloat yMin = CGRectGetMinY(aRect);<br />
 CGFloat yMax =  CGRectGetMaxY(aRect);</p>
<p>CGPoint* begin = (CGPoint*)[_spotsData  bytes];<br />
 CGPoint* end = begin + _spotCount;</p>
<p>CGPoint leftMargin =  aRect.origin;<br />
 CGPoint rightMargin;<br />
 rightMargin.x =  CGRectGetMaxX(aRect);</p>
<p>CGPoint* left  = std::lower_bound(begin, end,  leftMargin, cmpX);<br />
 CGPoint* right = std::lower_bound(left, end, rightMargin,  cmpX);</p>
<p>NSUInteger count = 0;<br />
 for (CGPoint* i = left; i != right; ++i)  {<br />
 if (i-&gt;y &gt; yMin &amp;&amp; i-&gt;y &lt; yMax)<br />
 ++count;<br />
 }</p>
<p><br class="spacer_" /></p>
<p>注意第二个二进制搜索，它仅从左边界而不是整个数组查找右边界。另一个优化是从循环中移除X-轴的检查，这是因为已经通过指定边界完成了这部分工作。</p>
<p><br class="spacer_" /></p>
<p>通过这些优化，只需要1毫秒来对长方形进行计数。这是第一个版本的200倍。</p>
<h5>结果</h5>
<p><br class="spacer_" /></p>
<p>下面是运行在三种不同设备上的<a href="http://savoysoftware.com/blog/wp-content/uploads/2009/01/spotsdb.zip">示例项目</a>的输出。如果你需要运行在你自己的设备上，你只需将目标设定中将代码签名标识符改为自己的。如果你不希望用在通用标识符的程序上，你还需要更改Info.plist中的包标识符（Bundle  Identifier）。</p>
<p><br class="spacer_" /></p>
<p>iPhone 3G：</p>
<pre>SpotsDB1 needed 3.2 seconds to count spots in 13 rects
that's 244.423 ms per rect
SpotsDB2 needed 3.0 seconds to count spots in 55 rects
that's 55.230 ms per rect
SpotsDB3 needed 3.0 seconds to count spots in 2988 rects
that's 1.004 ms per rect</pre>
<p>第二代iPod Touch：</p>
<pre>SpotsDB1 needed 3.1 seconds to count spots in 16 rects
that's 194.756 ms per rect
SpotsDB2 needed 3.0 seconds to count spots in 65 rects
that's 46.450 ms per rect
SpotsDB3 needed 3.0 seconds to count spots in 3399 rects
that's 0.883 ms per rect</pre>
<p>运行在Mac Pro之上的模拟器：</p>
<pre>SpotsDB1 needed 3.0 seconds to count spots in 687 rects
that's 4.372 ms per rect
SpotsDB2 needed 3.0 seconds to count spots in 2687 rects
that's 1.117 ms per rect
SpotsDB3 needed 3.0 seconds to count spots in 187276 rects
that's 0.016 ms per rect</pre>
<h5>结论</h5>
<p><br class="spacer_" /></p>
<p>观察结果我们可以看出在桌面电脑和iPhone上性能有显著的区别。有一个金科玉律是：在Mac上只需一秒的操作在iPhone上要一分钟。或者有一个更悲观的说法：Mac上60Hz的刷新率在iPhone上每秒钟只能更新一次。</p>
<p><br class="spacer_" /></p>
<p>所以显而易见在iPhone上进行优化是完全必须的。本文中我展示了几种方法来减小Object  C的开销以及怎样使用C++来获得最大的性能。当然找到性能的瓶颈并转而使用数据结构使代码性能提升并不总是很容易的事情。但是在设计一个使用大量数据的程序时，绝对有必要考虑使用非Cocoa的方法。</p>
<h6>原文见：<a href="http://www.savoysoftware.com/blog/?p=114">My iPhone is not a  Mac Pro</a></h6>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/iphone%e4%b8%8d%e6%98%afmac-pro/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用UIImagePickerController从iPhone照片库或照相机获取图像</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bd%bf%e7%94%a8uiimagepickercontroller%e4%bb%8eiphone%e7%85%a7%e7%89%87%e5%ba%93%e6%88%96%e7%85%a7%e7%9b%b8%e6%9c%ba%e8%8e%b7%e5%8f%96%e5%9b%be%e5%83%8f</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bd%bf%e7%94%a8uiimagepickercontroller%e4%bb%8eiphone%e7%85%a7%e7%89%87%e5%ba%93%e6%88%96%e7%85%a7%e7%9b%b8%e6%9c%ba%e8%8e%b7%e5%8f%96%e5%9b%be%e5%83%8f#comments</comments>
		<pubDate>Fri, 05 Feb 2010 10:16:10 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[图形图像]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[UIImagePckerController]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=873</guid>
		<description><![CDATA[本文讲述使用3.0的方法怎样从iPhone的照片库或照相机获取图像。

我们将创建一个应用程序从图片库或照相机获取图像并显示与屏幕之上。下面是截图：



1. 创建一个新的 View Based 程序

我将其命名为 photoApp
 
2. 创建IBOutlet 和 IBAction

打开 photoAppViewController.h 加入下面代码：


#import 
 
@interface PhotoAppViewController : UIViewController
    &#60; UIImagePickerControllerDelegate, UINavigationControllerDelegate &#62; {
	UIImageView * imageView;
	UIButton * choosePhotoBtn;
	UIButton * takePhotoBtn;
}
 
@property (nonatomic, retain) IBOutlet UIImageView * imageView;
@property (nonatomic, retain) IBOutlet UIButton * choosePhotoBtn;
@property (nonatomic, retain) IBOutlet UIButton * takePhotoBtn;
 
-(IBAction) getPhoto:(id) sender;
 
@end



注意我们实现了 UIImagePickerControlDelegate 和 UINavigationControllerDelegate 协议。为了与图像拾取器正确地接口，这两者都是必需的。其余部分应该很简单。我们设定了要使用按钮的IBOutlet以及按下这些按钮时被调用的IBAction。getPhoto方法将显示图像拾取器。
 
3. 创建接口

在Interface Builder中打开photoAppViewController.xib [...]]]></description>
			<content:encoded><![CDATA[<p>本文讲述使用3.0的方法怎样从iPhone的照片库或照相机获取图像。</p>
<p><br class="spacer_" /></p>
<p>我们将创建一个应用程序从图片库或照相机获取图像并显示与屏幕之上。下面是截图：</p>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2009/07/photo-21.jpg"><img title="photo 2" src="http://icodeblog.com/wp-content/uploads/2009/07/photo-21.jpg" alt="photo 2" width="320" height="480" /></a></p>
<p><span id="more-873"></span></p>
<h3>1. 创建一个新的 View Based 程序</h3>
<p><br class="spacer_" /></p>
<p>我将其命名为 photoApp</p>
<h2> </h2>
<h2>2. 创建IBOutlet 和 IBAction</h2>
<p><br class="spacer_" /></p>
<p>打开 <strong>photoAppViewController.h </strong>加入下面代码：</p>
<div>
<div>
<pre style="font-family: monospace;"><span style="color: #6e371a;">#import </span>
 
<span style="color: #a61390;">@interface</span> PhotoAppViewController <span style="color: #002200;">:</span> UIViewController
    &lt; UIImagePickerControllerDelegate, UINavigationControllerDelegate &gt; <span style="color: #002200;">{</span>
	UIImageView <span style="color: #002200;">*</span> imageView;
	UIButton <span style="color: #002200;">*</span> choosePhotoBtn;
	UIButton <span style="color: #002200;">*</span> takePhotoBtn;
<span style="color: #002200;">}</span>
 
<span style="color: #a61390;">@property</span> <span style="color: #002200;">(</span>nonatomic, retain<span style="color: #002200;">)</span> IBOutlet UIImageView <span style="color: #002200;">*</span> imageView;
<span style="color: #a61390;">@property</span> <span style="color: #002200;">(</span>nonatomic, retain<span style="color: #002200;">)</span> IBOutlet UIButton <span style="color: #002200;">*</span> choosePhotoBtn;
<span style="color: #a61390;">@property</span> <span style="color: #002200;">(</span>nonatomic, retain<span style="color: #002200;">)</span> IBOutlet UIButton <span style="color: #002200;">*</span> takePhotoBtn;
 
<span style="color: #002200;">-</span><span style="color: #002200;">(</span>IBAction<span style="color: #002200;">)</span> getPhoto<span style="color: #002200;">:</span><span style="color: #002200;">(</span><span style="color: #a61390;">id</span><span style="color: #002200;">)</span> sender;
 
<span style="color: #a61390;">@end</span></pre>
</div>
</div>
<p><br class="spacer_" /></p>
<p>注意我们实现了 <strong>UIImagePickerControlDelegate</strong> 和<strong> UINavigationControllerDelegate </strong>协议。为了与图像拾取器正确地接口，这两者都是必需的。其余部分应该很简单。我们设定了要使用按钮的IBOutlet以及按下这些按钮时被调用的IBAction。getPhoto方法将显示图像拾取器。</p>
<h2> </h2>
<h2>3. 创建接口</h2>
<p><br class="spacer_" /></p>
<p>在Interface Builder中打开photoAppViewController.xib 并遵循下面步骤：</p>
<ol>
<li>将 UIImageView 拖入main view</li>
<li>在Attribute inspector（属性检查器）中将 UIImageView 的 <strong>Mode （模式）</strong> 设置为 <strong>Aspect Fit</strong></li>
<li>将 UIButton 拖入view 中并命名为<strong> Choose Photo （选取照片）</strong></li>
<li>将另一个 UIButton 拖入view 中并命名为 <strong>Take Photo （照相）</strong></li>
</ol>
<p><br class="spacer_" /></p>
<p>接口看上去像这样：</p>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2009/07/screenshot_013.png"><img title="screenshot_01" src="http://icodeblog.com/wp-content/uploads/2009/07/screenshot_013.png" alt="screenshot_01" width="320" height="502" /></a></p>
<h2>4. 连接IBoutlets 和 IBAction</h2>
<ol>
<li>将 choosePhotoBtn 连接到名为 <strong>Choose Photo </strong>的UIButton上</li>
<li>将 imageView 连接到 UIImageView</li>
<li>将各按钮的<strong>Touch Up Inside</strong> 回调连接到 <strong>getPhoto</strong>方法</li>
</ol>
<p><br class="spacer_" /></p>
<p>当你按下 <strong>File’s Owner</strong> 时，Connection Inspector （连接检查器）应该像这样：</p>
<div id="in_post_ad_middle_1" style="margin: 5px; padding: 0px;">
<div id="ad-article"><noscript></noscript><!-- END NetShelter Ad Tag for TGDaily second rectangle 300x250,336x280,300x600 --></div>
</div>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2009/07/screenshot_014.png"><img title="screenshot_01" src="http://icodeblog.com/wp-content/uploads/2009/07/screenshot_014.png" alt="screenshot_01" width="289" height="213" /></a></p>
<p><br class="spacer_" /></p>
<p><br class="spacer_" /></p>
<p>关闭 Interface Builder</p>
<p><br class="spacer_" /></p>
<h2>5. 实现 getPhoto 方法</h2>
<p><br class="spacer_" /></p>
<p>打开 <strong>PhotoAppViewController.m </strong>a并nd add the following code:</p>
<div>
<div>
<pre style="font-family: monospace;"><span style="color: #a61390;">@synthesize</span> imageView,choosePhotoBtn, takePhotoBtn;
 
<span style="color: #002200;">-</span><span style="color: #002200;">(</span>IBAction<span style="color: #002200;">)</span> getPhoto<span style="color: #002200;">:</span><span style="color: #002200;">(</span><span style="color: #a61390;">id</span><span style="color: #002200;">)</span> sender <span style="color: #002200;">{</span>
	UIImagePickerController <span style="color: #002200;">*</span> picker <span style="color: #002200;">=</span> <span style="color: #002200;">[</span><span style="color: #002200;">[</span>UIImagePickerController alloc<span style="color: #002200;">]</span> init<span style="color: #002200;">]</span>;
	picker.delegate <span style="color: #002200;">=</span> self;
 
	<span style="color: #a61390;">if</span><span style="color: #002200;">(</span><span style="color: #002200;">(</span>UIButton <span style="color: #002200;">*</span><span style="color: #002200;">)</span> sender <span style="color: #002200;">==</span> choosePhotoBtn<span style="color: #002200;">)</span> <span style="color: #002200;">{</span>
		picker.sourceType <span style="color: #002200;">=</span> UIImagePickerControllerSourceTypeSavedPhotosAlbum;
	<span style="color: #002200;">}</span> <span style="color: #a61390;">else</span> <span style="color: #002200;">{</span>
		picker.sourceType <span style="color: #002200;">=</span> UIImagePickerControllerSourceTypeCamera;
	<span style="color: #002200;">}</span>
 
	<span style="color: #002200;">[</span>self presentModalViewController<span style="color: #002200;">:</span>picker animated<span style="color: #002200;">:</span><span style="color: #a61390;">YES</span><span style="color: #002200;">]</span>;
<span style="color: #002200;">}</span></pre>
</div>
</div>
<p><br class="spacer_" /></p>
<p>首先创建一个新的UIImagePickerController对象。它是一个视图控制器，可以按任意视图控制器方式正常显示出来（显示于导航视图，加载于表格视图，呈现为模型视图控制器等）。然后，设置代理为我们的视图控制器。它表示当用户选取照片时，拾取器将调用类中的某个方法。</p>
<p><br class="spacer_" /></p>
<p>接着，确定哪个按钮被按下。由于两个按钮都连接到此方法上，我们需要使用 == 来确定到底哪个被按下。到底是显示照相机图片还是显示照片库图片是由拾取器的一个属性决定的。看看代码你就会很清楚。</p>
<p><br class="spacer_" /></p>
<p>最后，调用presentModalViewController。它将使拾取器以动画的形式从屏幕底向上移动，切换到视图。根据按下的按钮的不同，你将看到下面图像：</p>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2009/07/photo-3.jpg"><img title="photo 3" src="http://icodeblog.com/wp-content/uploads/2009/07/photo-3.jpg" alt="photo 3" width="320" height="480" /></a><a href="http://icodeblog.com/wp-content/uploads/2009/07/photo.jpg"><img title="photo" src="http://icodeblog.com/wp-content/uploads/2009/07/photo.jpg" alt="photo" width="320" height="480" /></a></p>
<h2> </h2>
<h2>6. 显示选择的图像</h2>
<p><br class="spacer_" /></p>
<p>一旦选择了照片， ImagePicker 将回调 <strong>didFinishPickingMediaWithInfo。</strong>在<strong>PhotoAppViewController.m </strong>文件中进入下列代码：<strong> </strong></p>
<div>
<div>
<pre style="font-family: monospace;"><span style="color: #002200;">-</span> <span style="color: #002200;">(</span><span style="color: #a61390;">void</span><span style="color: #002200;">)</span>imagePickerController<span style="color: #002200;">:</span><span style="color: #002200;">(</span>UIImagePickerController <span style="color: #002200;">*</span><span style="color: #002200;">)</span>picker
    didFinishPickingMediaWithInfo<span style="color: #002200;">:</span><span style="color: #002200;">(</span><span style="color: #400080;">NSDictionary</span> <span style="color: #002200;">*</span><span style="color: #002200;">)</span>info <span style="color: #002200;">{</span>
	<span style="color: #002200;">[</span>picker dismissModalViewControllerAnimated<span style="color: #002200;">:</span><span style="color: #a61390;">YES</span><span style="color: #002200;">]</span>;
	imageView.image <span style="color: #002200;">=</span> <span style="color: #002200;">[</span>info objectForKey<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">"UIImagePickerControllerOriginalImage"</span><span style="color: #002200;">]</span>;
<span style="color: #002200;">}</span></pre>
<pre style="font-family: monospace;"><span style="color: #002200;"> </span> </pre>
</div>
</div>
<p>第一行代码隐藏拾取器。随后将图像视图的<strong>image</strong> 属性设置为返回的图像。拾取器返回一个NSDictionary对象。这是由于 <a href="http://developer.apple.com/iPhone/library/documentation/UIKit/Reference/UIImagePickerControllerDelegate_Protocol/UIImagePickerControllerDelegate/UIImagePickerControllerDelegate.html#//apple_ref/doc/c_ref/UIImagePickerControllerMediaType">UIImagePickerControllerMediaType</a> 将指示返回的是视频还是图像。</p>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2009/07/PhotoApp.zip">iPhone Tutorial – PhotoApp.zip</a></p>
<p><br class="spacer_" /></p>
<p>原文见：<a title="Permanent Link to Getting Images From The iPhone Photo Library Or Camera Using UIImagePickerController" rel="bookmark" href="http://icodeblog.com/2009/07/28/getting-images-from-the-iphone-photo-library-or-camera-using-uiimagepickercontroller/">Getting Images From The iPhone Photo Library Or Camera Using UIImagePickerController </a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bd%bf%e7%94%a8uiimagepickercontroller%e4%bb%8eiphone%e7%85%a7%e7%89%87%e5%ba%93%e6%88%96%e7%85%a7%e7%9b%b8%e6%9c%ba%e8%8e%b7%e5%8f%96%e5%9b%be%e5%83%8f/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
