<?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>Wangling &#187; Mac</title>
	<atom:link href="http://wangling.me/category/mac/feed/" rel="self" type="application/rss+xml" />
	<link>http://wangling.me</link>
	<description>I&#039;m Wang Ling. I&#039;m wangling you.</description>
	<lastBuildDate>Tue, 15 May 2012 14:17:55 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.2</generator>
		<item>
		<title>Three Things Apple Didn’t Tell You About In-App Purchase</title>
		<link>http://wangling.me/2012/04/three-things-apple-didnt-tell-you-about-in-app-purchase/</link>
		<comments>http://wangling.me/2012/04/three-things-apple-didnt-tell-you-about-in-app-purchase/#comments</comments>
		<pubDate>Tue, 17 Apr 2012 16:38:39 +0000</pubDate>
		<dc:creator>an0</dc:creator>
				<category><![CDATA[development]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[Mac]]></category>
		<category><![CDATA[App Store]]></category>
		<category><![CDATA[IAP]]></category>

		<guid isPermaLink="false">http://wangling.me/?p=827</guid>
		<description><![CDATA[You read once again In-App Purchase Programming Guide and Technical Note TN2259 completely, yet still can not make the sandbox work. Now may be the time for a break to get something fresh: Bundle ID is case-sensitive. If it is com.moke.app in the iTunes Connect but com.moke.App in App-Info.plist — that is default Bundle ID [...]]]></description>
			<content:encoded><![CDATA[<p>You read once again <a href="https://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/StoreKitGuide/Introduction/Introduction.html">In-App Purchase Programming Guide</a> and <a href="http://developer.apple.com/library/ios/#technotes/tn2259/_index.html">Technical Note TN2259</a> completely, yet still can not make the sandbox work. Now may be the time for a break to get something fresh:</p>
<ol>
<li>Bundle ID is case-sensitive. If it is com.moke.app in the iTunes Connect but com.moke.App in App-Info.plist — that is default Bundle ID set in your app if you named your Xcode project &#8220;App&#8221; — IAP will not work.</li>
<li>&#8220;Waiting for Screenshot&#8221; is not a good status. At least you should upload a placeholder screenshot and let the product status be &#8220;Ready to Submit&#8221;.</li>
<li>No new Development Provisioning Profile is required. Though <a href="http://developer.apple.com/library/ios/#technotes/tn2259/_index.html">Technical Note TN2259</a> tells you to &#8220;Create, download, and install a new Development Provisioning Profile that uses your App ID enabled for In-App Purchase as seen in…&#8221;, it is not required if you are using a Development Provisioning Profile &#8220;Managed by Xcode&#8221; — it works for IAP apps even though it has a different App ID. Of course, it works for development installations only and you need setup Distribution Provisioning Profiles to distribute any apps no matter they have IAP or not.</li>
</ol>
<p>One more thing. It&#8217;s not Apple&#8217;s One More Thing. In fact, it is one more thing that Apple does not like. If you are using a jailbroken device, make sure you uninstall AppSync from Cydia before testing IAP.</p>
<div class="social">			<a href="https://twitter.com/share?url=http%3A%2F%2Fwangling.me%2F2012%2F04%2Fthree-things-apple-didnt-tell-you-about-in-app-purchase%2F" class="twitter-share-button" data-url="http%3A%2F%2Fwangling.me%2F2012%2F04%2Fthree-things-apple-didnt-tell-you-about-in-app-purchase%2F" data-text="Three Things Apple Didn’t Tell You About In-App Purchase" data-via="an0" data-count="none">Tweet</a>
			<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>			<script type="text/javascript" charset="utf-8">
(function(){
  var _w = 86 , _h = 18;
  var param = {
    url:"http%3A%2F%2Fwangling.me%2F2012%2F04%2Fthree-things-apple-didnt-tell-you-about-in-app-purchase%2F",
    type:'6',
    count:'', /**是否显示分享数，1显示(可选)*/
    appkey:'', /**您申请的应用appkey,显示分享来源(可选)*/
    title:'Three Things Apple Didn’t Tell You About In-App Purchase', /**分享的文字内容(可选，默认为所在页面的title)*/
    pic:'', /**分享图片的路径(可选)*/
    ralateUid:'1676354212', /**关联用户的UID，分享微博会@该用户(可选)*/
    rnd:new Date().valueOf()
  }
  var temp = [];
  for( var p in param ){
    temp.push(p + '=' + encodeURIComponent( param[p] || '' ) )
  }
  document.write('<iframe allowTransparency="true" frameborder="0" scrolling="no" src="http://hits.sinajs.cn/A1/weiboshare.html?' + temp.join('&') + '" width="'+ _w+'" height="'+_h+'" style="margin-left:5px;"></iframe>')
})()
</script></div>]]></content:encoded>
			<wfw:commentRss>http://wangling.me/2012/04/three-things-apple-didnt-tell-you-about-in-app-purchase/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>坑爹的 iA Writer &amp; App Store 退款经历</title>
		<link>http://wangling.me/2012/03/app-store-refund-time-frame/</link>
		<comments>http://wangling.me/2012/03/app-store-refund-time-frame/#comments</comments>
		<pubDate>Wed, 14 Mar 2012 16:48:48 +0000</pubDate>
		<dc:creator>an0</dc:creator>
				<category><![CDATA[中文]]></category>
		<category><![CDATA[Mac]]></category>
		<category><![CDATA[App Store]]></category>

		<guid isPermaLink="false">http://wangling.me/?p=815</guid>
		<description><![CDATA[iA Writer 很坑爹，中文输入有问题。App Store 退款有时间限制，须在购买后 60 天之内提出。]]></description>
			<content:encoded><![CDATA[<h3>坑爹的 <a href="http://www.iawriter.com/">iA Writer</a></h3>
<p>我早就向 iA Writer 的开发者反映过中文输入时的<a href="http://www.youtube.com/watch?v=fOtSBvmSgyQ">跳动问题</a>。当时他们感谢了我反馈这个 bug，并声称尽力在后续版本中解决。</p>
<p>半年过去了，iA Writer 1.2.1 都出了，这个问题仍未解决。我再次去询问。这次他们说：“iA Writer 本来就没有声称支持中文输入。” Fuck！</p>
<h3>App Store 退款</h3>
<p>于是，我一怒之下首次在 App Store 申请退款，但是为时已晚：</p>
<blockquote><p>Dear Ling,</p>
<p>When you first noticed that you could not use the application for the purpose you intended you should have contacted the iTunes Store for a refund.</p>
<p>Regrettably we cannot be held responsible for developers giving false information on updates that they plan on releasing in the future.</p>
<p>The iTunes Store will not be able to offer you a refund. Since the system does not allow refunds past the 60 day point we very strongly encourage customers to notify the iTunes Store of any issues you have with your purchases within that 60 days time frame.</p>
<p>In light of the current circumstances I do hope you have a pleasant day, Ling.</p>
<p>Sincerely,</p>
<p>Missy<br/><br />
iTunes Store/Mac App Store Customer Support Senior Advisor</p>
</blockquote>
<p>简单复述：</p>
<ol>
<li>一旦发现 app 不好用，第一时间申请退款。</li>
<li>相信开发者所谓的“以后会修正”是你的事，App Store 不对此负责。</li>
<li>退款有时间限制，须在购买后 60 天之内提出。</li>
<li>虽然你被坑、退款不成，我们还是希望你有开心的一天。</li>
</ol>
<div class="social">			<a href="https://twitter.com/share?url=http%3A%2F%2Fwangling.me%2F2012%2F03%2Fapp-store-refund-time-frame%2F" class="twitter-share-button" data-url="http%3A%2F%2Fwangling.me%2F2012%2F03%2Fapp-store-refund-time-frame%2F" data-text="坑爹的 iA Writer &#038; App Store 退款经历: iA Writer 很坑爹，中文输入有问题。App Store 退款有时间限制，须在购买后 60 天之内提出。" data-via="an0" data-count="none">Tweet</a>
			<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>			<script type="text/javascript" charset="utf-8">
(function(){
  var _w = 86 , _h = 18;
  var param = {
    url:"http%3A%2F%2Fwangling.me%2F2012%2F03%2Fapp-store-refund-time-frame%2F",
    type:'6',
    count:'', /**是否显示分享数，1显示(可选)*/
    appkey:'', /**您申请的应用appkey,显示分享来源(可选)*/
    title:'坑爹的 iA Writer &#038; App Store 退款经历: iA Writer 很坑爹，中文输入有问题。App Store 退款有时间限制，须在购买后 60 天之内提出。', /**分享的文字内容(可选，默认为所在页面的title)*/
    pic:'', /**分享图片的路径(可选)*/
    ralateUid:'1676354212', /**关联用户的UID，分享微博会@该用户(可选)*/
    rnd:new Date().valueOf()
  }
  var temp = [];
  for( var p in param ){
    temp.push(p + '=' + encodeURIComponent( param[p] || '' ) )
  }
  document.write('<iframe allowTransparency="true" frameborder="0" scrolling="no" src="http://hits.sinajs.cn/A1/weiboshare.html?' + temp.join('&') + '" width="'+ _w+'" height="'+_h+'" style="margin-left:5px;"></iframe>')
})()
</script></div>]]></content:encoded>
			<wfw:commentRss>http://wangling.me/2012/03/app-store-refund-time-frame/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Zenburn Theme for Xcode 4</title>
		<link>http://wangling.me/2011/09/zenburn-theme-for-xcode-4/</link>
		<comments>http://wangling.me/2011/09/zenburn-theme-for-xcode-4/#comments</comments>
		<pubDate>Fri, 23 Sep 2011 14:22:36 +0000</pubDate>
		<dc:creator>an0</dc:creator>
				<category><![CDATA[中文]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[Mac]]></category>
		<category><![CDATA[Xcode]]></category>

		<guid isPermaLink="false">http://wangling.me/?p=730</guid>
		<description><![CDATA[如果默认主题让你眼睛疲倦的话，试试 Zenburn。暗色系，低对比度，适合长时间编码。]]></description>
			<content:encoded><![CDATA[<p>长久对着 Xcode 写代码，默认 Theme 的白亮和高对比度让我的眼睛很疲倦。自带的 Midnight Theme 太艳俗，而且对比度依然很高。我在 Emacs 里用 <a href="http://slinky.imukuppi.org/zenburnpage/">Zenburn</a>，感觉不错，于是就自己动手 port 到 Xcode 里，并针对性的做了微调。</p>
<div class="thumbnail"><a href="https://skitch.com/an00na/f5c3e/zenburn-for-xcode"><img src="https://img.skitch.com/20110923-tudq2cm22x6bmmahqhtuh6f6qq.preview.jpg" alt="Zenburn for Xcode" /></a></div>
<p>喜欢的话，可从 <a href="https://github.com/an0/Zenburn-for-Xcode">GitHub</a> 上获取。</p>
<div class="social">			<a href="https://twitter.com/share?url=http%3A%2F%2Fwangling.me%2F2011%2F09%2Fzenburn-theme-for-xcode-4%2F" class="twitter-share-button" data-url="http%3A%2F%2Fwangling.me%2F2011%2F09%2Fzenburn-theme-for-xcode-4%2F" data-text="Zenburn Theme for Xcode 4: 如果默认主题让你眼睛疲倦的话，试试 Zenburn。暗色系，低对比度，适合长时间编码。" data-via="an0" data-count="none">Tweet</a>
			<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>			<script type="text/javascript" charset="utf-8">
(function(){
  var _w = 86 , _h = 18;
  var param = {
    url:"http%3A%2F%2Fwangling.me%2F2011%2F09%2Fzenburn-theme-for-xcode-4%2F",
    type:'6',
    count:'', /**是否显示分享数，1显示(可选)*/
    appkey:'', /**您申请的应用appkey,显示分享来源(可选)*/
    title:'Zenburn Theme for Xcode 4: 如果默认主题让你眼睛疲倦的话，试试 Zenburn。暗色系，低对比度，适合长时间编码。', /**分享的文字内容(可选，默认为所在页面的title)*/
    pic:'', /**分享图片的路径(可选)*/
    ralateUid:'1676354212', /**关联用户的UID，分享微博会@该用户(可选)*/
    rnd:new Date().valueOf()
  }
  var temp = [];
  for( var p in param ){
    temp.push(p + '=' + encodeURIComponent( param[p] || '' ) )
  }
  document.write('<iframe allowTransparency="true" frameborder="0" scrolling="no" src="http://hits.sinajs.cn/A1/weiboshare.html?' + temp.join('&') + '" width="'+ _w+'" height="'+_h+'" style="margin-left:5px;"></iframe>')
})()
</script></div>]]></content:encoded>
			<wfw:commentRss>http://wangling.me/2011/09/zenburn-theme-for-xcode-4/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>在文件选取窗中快速选定文件</title>
		<link>http://wangling.me/2011/08/quick-selection-in-file-picker/</link>
		<comments>http://wangling.me/2011/08/quick-selection-in-file-picker/#comments</comments>
		<pubDate>Sat, 13 Aug 2011 23:35:40 +0000</pubDate>
		<dc:creator>an0</dc:creator>
				<category><![CDATA[中文]]></category>
		<category><![CDATA[Mac]]></category>
		<category><![CDATA[Finder]]></category>

		<guid isPermaLink="false">http://wangling.me/?p=611</guid>
		<description><![CDATA[用文件选取窗选定目标文件是件麻烦事，用任何一种 View（Icon, List, Columns, Cover Flow）都避免不了在文件系统中游走。 例如，在下图中，我在给 Apple 报 bug，要提交一个 Sample Code，位于 /Users/an0/dev/Projects/iOS/Bugs/CoreDataBug.zip。可见我要往下走三层目录，而且考虑到每一层目录下不光有我的目标路径，还有其他干扰项，这一路走到最后不容易。 我所知道的快速方法有两种，一种应该是资深用户所知道的，另一种是我自己刚发现的。 Finder 快捷键法 显然，文件选取窗长的很像 Finder 窗口，事实上也确实是。不显然的是，Finder 的快捷键在文件选取窗也是有效的。所以我们可以使用 Go 菜单下的这些快捷键来快速定位。其中最强大的是 Go to Folder…，但是需要你熟知目标路径，而且 Finder 的路径补全很慢。 直接拖拽法 这我是用 Lion 后无意间发现的，不确定 Lion 之前是否就支持了。 一般来说，我们要选取的目标文件是刚操作过的或常操作的文件，它们所在的目录已经在 Finder 中打开。比如，我这里要上传的 Sample Code 就是刚压缩好的，就在眼前。 很自然的，我就想把它直接拖拽到文件选取窗。令人惊喜的是，竟然真的可以！文件选取窗会自动跳转到目标目录并选中目标文件！太爽快了！ 事实上，直接拖拽的源头不仅限于 Finder，其他支持拖拽的 app 也可作为文件来源。比如，我们可以直接从 iPhoto 中拖拽一张照片到文件选取窗。 Tweet]]></description>
			<content:encoded><![CDATA[<p>用文件选取窗选定目标文件是件麻烦事，用任何一种 View（Icon, List, Columns, Cover Flow）都避免不了在文件系统中游走。</p>
<p>例如，在下图中，我在给 Apple 报 bug，要提交一个 Sample Code，位于 <code>/Users/an0/dev/Projects/iOS/Bugs/CoreDataBug.zip</code>。可见我要往下走三层目录，而且考虑到每一层目录下不光有我的目标路径，还有其他干扰项，这一路走到最后不容易。</p>
<div class="thumbnail"><a href="https://skitch.com/an00na/fxfna/file-picker-window"><img style="max-width:638px" src="https://img.skitch.com/20110814-k7f628bnqn68anqmwru5rtuqhk.medium.jpg" alt="File Picker Window" /></a></div>
<p>我所知道的快速方法有两种，一种应该是资深用户所知道的，另一种是我自己刚发现的。</p>
<h4 id="finder">Finder 快捷键法</h4>
<p>显然，文件选取窗长的很像 Finder 窗口，事实上也确实是。不显然的是，Finder  的快捷键在文件选取窗也是有效的。所以我们可以使用 <code>Go</code> 菜单下的这些快捷键来快速定位。其中最强大的是 <code>Go to Folder…</code>，但是需要你熟知目标路径，而且 Finder 的路径补全很慢。<br />
<img src="https://img.skitch.com/20110814-m31pcrygf3e85agsikfijs8yri.png" alt="Go Menu of Finder" /></p>
<h4>直接拖拽法</h4>
<p>这我是用 Lion 后无意间发现的，不确定 Lion 之前是否就支持了。</p>
<p>一般来说，我们要选取的目标文件是刚操作过的或常操作的文件，它们所在的目录已经在 Finder 中打开。比如，我这里要上传的 Sample Code 就是刚压缩好的，就在眼前。</p>
<p>很自然的，我就想把它直接拖拽到文件选取窗。令人惊喜的是，竟然真的可以！文件选取窗会自动跳转到目标目录并选中目标文件！太爽快了！</p>
<div class="thumbnail"><a href="https://skitch.com/an00na/fxfn2/drag-to-file-picker-window"><img style="max-width:638px" src="https://img.skitch.com/20110814-mdj5xetmujwe2qi2a239853gth.medium.jpg" alt="Drag to File Picker Window" /></a></div>
<div class="thumbnail"><a href="https://skitch.com/an00na/fxfn7/drop-on-file-picker-window"><img style="max-width:638px" src="https://img.skitch.com/20110814-g4acbx64a7cuqbnimnui93e46p.medium.jpg" alt="Drop on File Picker Window" /></a></div>
<p>事实上，直接拖拽的源头不仅限于 Finder，其他支持拖拽的 app 也可作为文件来源。比如，我们可以直接从 iPhoto 中拖拽一张照片到文件选取窗。</p>
<div class="social">			<a href="https://twitter.com/share?url=http%3A%2F%2Fwangling.me%2F2011%2F08%2Fquick-selection-in-file-picker%2F" class="twitter-share-button" data-url="http%3A%2F%2Fwangling.me%2F2011%2F08%2Fquick-selection-in-file-picker%2F" data-text="在文件选取窗中快速选定文件" data-via="an0" data-count="none">Tweet</a>
			<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>			<script type="text/javascript" charset="utf-8">
(function(){
  var _w = 86 , _h = 18;
  var param = {
    url:"http%3A%2F%2Fwangling.me%2F2011%2F08%2Fquick-selection-in-file-picker%2F",
    type:'6',
    count:'', /**是否显示分享数，1显示(可选)*/
    appkey:'', /**您申请的应用appkey,显示分享来源(可选)*/
    title:'在文件选取窗中快速选定文件', /**分享的文字内容(可选，默认为所在页面的title)*/
    pic:'', /**分享图片的路径(可选)*/
    ralateUid:'1676354212', /**关联用户的UID，分享微博会@该用户(可选)*/
    rnd:new Date().valueOf()
  }
  var temp = [];
  for( var p in param ){
    temp.push(p + '=' + encodeURIComponent( param[p] || '' ) )
  }
  document.write('<iframe allowTransparency="true" frameborder="0" scrolling="no" src="http://hits.sinajs.cn/A1/weiboshare.html?' + temp.join('&') + '" width="'+ _w+'" height="'+_h+'" style="margin-left:5px;"></iframe>')
})()
</script></div>]]></content:encoded>
			<wfw:commentRss>http://wangling.me/2011/08/quick-selection-in-file-picker/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Time Warp in Animation</title>
		<link>http://wangling.me/2011/06/time-warp-in-animation/</link>
		<comments>http://wangling.me/2011/06/time-warp-in-animation/#comments</comments>
		<pubDate>Fri, 01 Jul 2011 00:16:30 +0000</pubDate>
		<dc:creator>an0</dc:creator>
				<category><![CDATA[development]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[Mac]]></category>
		<category><![CDATA[Core Animation]]></category>

		<guid isPermaLink="false">http://wangling.me/?p=513</guid>
		<description><![CDATA[CAMediaTiming Protocol offers a small set of eight properties. But it is sufficient to do all kinds of time warps, if you know what exactly every property means and how to use it. The most used and simple one is duration: duration Specifies the basic duration of the animation, in seconds. Nothing needs further explanation [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://developer.apple.com/library/ios/#documentation/GraphicsImaging/Reference/CAMediaTiming_protocol/Introduction/Introduction.html">CAMediaTiming Protocol</a> offers a small set of eight properties. But it is sufficient to do all kinds of time warps, if you know what exactly every property means and how to use it.</p>
<p>The most used and simple one is <em>duration</em>:</p>
<blockquote><p>
<strong>duration</strong><br />
Specifies the basic duration of the animation, in seconds.
</p></blockquote>
<p>Nothing needs further explanation here.</p>
<p>The <em>duration</em> you specify may differ from the real duration you experience, depending on the parent time space or <em>speed</em>:</p>
<blockquote><p>
<strong>speed</strong><br />
Specifies how time is mapped to receiver’s time space from the parent time space.
</p></blockquote>
<p>So an animation with <em>duration</em> of 1 second and <em>speed</em> of 1 is the same as an animation with <em>duration</em> of 2 seconds and <em>speed</em> of 2, when mapped to real time space.</p>
<p>The two repeating properties — <em>repeatCount</em> and <em>repeatDuration</em> — are as clear as they are named. So is <em>autoreverses</em>.</p>
<p>Finally, we are talking about the three interesting but not intuitive properties:</p>
<h4>beginTime</h4>
<blockquote><p>
<strong>beginTime</strong><br />
Specifies the begin time of the receiver in relation to its parent object, if applicable.
</p></blockquote>
<p>If an animation is in an animation group, <em>beginTime</em> is the offset from the beginning of its parent object — the animation group. So if <em>beginTime</em> of the animation is 5, it begins 5 seconds after the animation group begins.</p>
<p>If an animation is added directly to a layer, <em>beginTime</em> is still the offset from the beginning of its parent object — the layer. But since the beginning of a layer is in the past<sup><a href="http://wangling.me/2011/06/time-warp-in-animation/#footnote_0_513" id="identifier_0_513" class="footnote-link footnote-identifier-link" title="I guess it is the time when it is added to a layer tree">1</a></sup>, I can not simply set <em>beginTime</em> to 5 to delay the animation 5 seconds, because 5 seconds after the beginning of a layer is probably still a past time. What I usually really want is a delay relative to when the animation is added to the layer — denoted by addTime. So</p>
<p><span class="code">animation.beginTime <span style="color: #002200;">=</span> addTime <span style="color: #002200;">+</span> delay;</span></p>
<p>In order to get <em>addTime</em>, I can use <em>CACurrentMediaTime</em> and <em>convertTime:fromLayer:</em>:</p>
<p><span class="code">addTime <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>layer convertTime<span style="color: #002200;">:</span>CACurrentMediaTime<span style="color: #002200;">&#40;</span><span style="color: #002200;">&#41;</span> fromLayer<span style="color: #002200;">:</span><span style="color: #a61390;">nil</span><span style="color: #002200;">&#93;</span>;</span></p>
<p>If <em>beginTime</em> of the layer itself is to be set, addTime must be calculated after it is set because <em>beginTime</em> shifts the time space of the layer.</p>
<p>These things can be chained up:</p>
<p><span class="code">CFTimeInterval currentTime <span style="color: #002200;">=</span> CACurrentMediaTime<span style="color: #002200;">&#40;</span><span style="color: #002200;">&#41;</span>;<br />
CFTimeInterval currentTimeInSuperLayer <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>superLayer convertTime<span style="color: #002200;">:</span>currentTime fromLayer<span style="color: #002200;">:</span><span style="color: #a61390;">nil</span><span style="color: #002200;">&#93;</span>;<br />
layer.beginTime <span style="color: #002200;">=</span> currentTimeInSuperLayer <span style="color: #002200;">+</span> <span style="color: #2400d9;">2</span>;<br />
CFTimeInterval currentTimeInLayer <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>layer convertTime<span style="color: #002200;">:</span>currentTimeInSuperLayer fromLayer<span style="color: #002200;">:</span>superLayer<span style="color: #002200;">&#93;</span>;<br />
CFTimeInterval addTime <span style="color: #002200;">=</span> currentTimeInLayer;<br />
CAAnimationGroup <span style="color: #002200;">*</span>group <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>CAAnimationGroup animation<span style="color: #002200;">&#93;</span>;<br />
group.beginTime <span style="color: #002200;">=</span> addTime <span style="color: #002200;">+</span> <span style="color: #2400d9;">1</span>;<br />
group.animations <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #400080;">NSArray</span> arrayWithObject<span style="color: #002200;">:</span>anim<span style="color: #002200;">&#93;</span>;<br />
group.duration <span style="color: #002200;">=</span> <span style="color: #2400d9;">2</span>;<br />
anim.beginTime <span style="color: #002200;">=</span> <span style="color: #2400d9;">0.5</span>;<br />
<span style="color: #002200;">&#91;</span>layer addAnimation<span style="color: #002200;">:</span>group forKey<span style="color: #002200;">:</span><span style="color: #a61390;">nil</span><span style="color: #002200;">&#93;</span>;</span></p>
<h4>timeOffset</h4>
<blockquote><p>
<strong>timeOffset</strong><br />
Specifies an additional time offset in active local time.
</p></blockquote>
<p>It’s not clear what it means from the words. But with a very simple example you’ll get it.</p>
<p>Supposing a 3-second animation with states <em>s<sub>0</sub></em>, <em>s<sub>1</sub></em>, <em>s<sub>2</sub></em>, <em>s<sub>3</sub></em>, wherein <em>s<sub>n</sub></em> denotes the <em>state</em> at second <em>n</em>. The normal state sequence without <em>timeOffset</em> is:<br />
<em>s<sub>0</sub></em> ➔ <em>s<sub>1</sub></em> ➔ <em>s<sub>2</sub></em> ➔ <em>s<sub>3</sub></em></p>
<p>With <em>timeOffset</em> set to 1, the state sequence becomes:<br />
<em>s<sub>1</sub></em> ➔ <em>s<sub>2</sub></em> ➔ <em>s<sub>3</sub></em> ➔ <em>s<sub>0</sub></em></p>
<p>With <em>timeOffset</em> set to 2:<br />
<em>s<sub>2</sub></em> ➔ <em>s<sub>3</sub></em> ➔ <em>s<sub>0</sub></em> ➔ <em>s<sub>1</sub></em></p>
<p>Shift and wrap around. That’s it.</p>
<h4>fillMode</h4>
<p><em>fillMode</em> is really <a href="http://developer.apple.com/library/ios/#documentation/GraphicsImaging/Reference/CAMediaTiming_protocol/Introduction/Introduction.html%23//apple_ref/occ/intfm/CAMediaTiming/fillMode">badly documented</a>. I can figure out kCAFillModeRemoved and kCAFillModeForwards but have no idea what kCAFillModeBackwards and kCAFillModeBoth are about.</p>
<p>Fortunately, they turn out very straightforward if you see them in practice.</p>
<p>Supposing an animation in an animation group configured as follow:</p>
<p><span class="code">anim.beginTime <span style="color: #002200;">=</span> <span style="color: #2400d9;">1</span>;<br />
anim.duration <span style="color: #002200;">=</span> <span style="color: #2400d9;">3</span>;<br />
CAAnimationGroup <span style="color: #002200;">*</span>group <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>CAAnimationGroup animation<span style="color: #002200;">&#93;</span>;<br />
group.animations <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #400080;">NSArray</span> arrayWithObject<span style="color: #002200;">:</span>anim<span style="color: #002200;">&#93;</span>;<br />
group.duration <span style="color: #002200;">=</span> <span style="color: #2400d9;">5</span>;</span></p>
<p>Denote initial state of <em>anim</em> as <em>s<sub>i</sub></em> and final state as <em>s<sub>f</sub></em>. Hence in <em>group</em>’s time space:<br />
<em>s<sub>i</sub></em> = <em>s<sub>1</sub></em><br />
<em>s<sub>f</sub></em> = <em>s<sub>4</sub></em></p>
<p>Normally, there is no <em>s<sub>0</sub></em> or <em>s<sub>5</sub></em>, because <em>anim</em> is not in effect during [0, 1) or (4, 5]. And it is what kCAFillModeRemoved means.</p>
<p>With kCAFillModeForwards, the state sequence of <em>anim</em> extends forward to the end of its parent time space, that is:<br />
<em>s<sub>1</sub></em> ➔ <em>s<sub>2</sub></em> ➔ <em>s<sub>3</sub></em> ➔ <em>s<sub>4</sub></em> ➔ <em>s<sub>5</sub></em></p>
<p>But what should <em>s<sub>5</sub></em> be? I believe you can guess it.<br />
<em>s<sub>5</sub></em> = <em>s<sub>f</sub></em> = <em>s<sub>4</sub></em></p>
<p>After <em>anim</em> is completed, it remains. It is what filling forward means. Quite intuitive, if put clearly, isn’t it?</p>
<p>kCAFillModeBackwards is the mirror of kCAFillModeForwards, which we can easily tell from their names. Actually, it is.</p>
<p>If <em>fillMode</em> of <em>anim</em> is set to kCAFillModeBackwards, the state sequence extends backward to the beginning of its parent time space, that is:<br />
<em>s<sub>0</sub> ➔ </em><em>s<sub>1</sub></em> ➔ <em>s<sub>2</sub></em> ➔ <em>s<sub>3</sub></em> ➔ <em>s<sub>4</sub></em><br />
with<br />
<em>s<sub>0</sub></em> = <em>s<sub>i</sub></em> = <em>s<sub>1</sub></em></p>
<p>Before <em>anim</em> begins, it appears. It is, indeed, filling backward.</p>
<p>At this time, kCAFillModeBoth is a nothing mysterious at all. It is just kCAFillModeForwards + kCAFillModeBackwards, filling both way:<br />
<em>s<sub>0</sub> ➔ </em><em>s<sub>1</sub></em> ➔ <em>s<sub>2</sub></em> ➔ <em>s<sub>3</sub></em> ➔ <em>s<sub>4</sub></em> ➔ <em>s<sub>5</sub></em><br />
with<br />
<em>s<sub>0</sub></em> = <em>s<sub>i</sub></em> = <em>s<sub>1</sub></em><br />
<em>s<sub>5</sub></em> = <em>s<sub>f</sub></em> = <em>s<sub>4</sub></em></p>
<p>To do you a further favor, I re-document <em>fillMode</em> below, in the way it should have been documented in the first place:</p>
<blockquote><p>
<strong>fillMode</strong><br />
Determines the receiver’s presentation during its inactive duration.</p>
<p>Discussion<br />
The possible values are described in “Fill Modes”. The default is kCAFillModeRemoved.
</p></blockquote>
<blockquote><p>
Fill Modes<br />
These constants determine how the timed object behaves in its inactive duration. They are used with the fillMode property.</p>
<p>NSString * const kCAFillModeRemoved;<br />
NSString * const kCAFillModeForwards;<br />
NSString * const kCAFillModeBackwards;<br />
NSString * const kCAFillModeBoth;<br />
NSString * const kCAFillModeFrozen;</p>
<p>Constants<br />
kCAFillModeRemoved<br />
The receiver does not appear until it begins and is removed from the presentation when it is completed.</p>
<p>kCAFillModeForwards<br />
The receiver does not appear until it begins but remains visible in its final state when it is completed.</p>
<p>kCAFillModeBackwards<br />
The receiver appears in its initial state before it begins but is removed from the presentation when it is completed.</p>
<p>kCAFillModeBoth<br />
The receiver appears in its initial state before it begins and remains visible in its final state when it is completed.
</p></blockquote>
<p>That’s all. I think I’ve filled your mind quite full. You can get the sample code on <a href="https://github.com/an0/AnimationTimeWarp">github</a>.</p>
<div class="footnotes"><ol ><li id="footnote_0_513" class="footnote">I guess it is the time when it is added to a layer tree</li></ol></div><div class="social">			<a href="https://twitter.com/share?url=http%3A%2F%2Fwangling.me%2F2011%2F06%2Ftime-warp-in-animation%2F" class="twitter-share-button" data-url="http%3A%2F%2Fwangling.me%2F2011%2F06%2Ftime-warp-in-animation%2F" data-text="Time Warp in Animation" data-via="an0" data-count="none">Tweet</a>
			<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>			<script type="text/javascript" charset="utf-8">
(function(){
  var _w = 86 , _h = 18;
  var param = {
    url:"http%3A%2F%2Fwangling.me%2F2011%2F06%2Ftime-warp-in-animation%2F",
    type:'6',
    count:'', /**是否显示分享数，1显示(可选)*/
    appkey:'', /**您申请的应用appkey,显示分享来源(可选)*/
    title:'Time Warp in Animation', /**分享的文字内容(可选，默认为所在页面的title)*/
    pic:'', /**分享图片的路径(可选)*/
    ralateUid:'1676354212', /**关联用户的UID，分享微博会@该用户(可选)*/
    rnd:new Date().valueOf()
  }
  var temp = [];
  for( var p in param ){
    temp.push(p + '=' + encodeURIComponent( param[p] || '' ) )
  }
  document.write('<iframe allowTransparency="true" frameborder="0" scrolling="no" src="http://hits.sinajs.cn/A1/weiboshare.html?' + temp.join('&') + '" width="'+ _w+'" height="'+_h+'" style="margin-left:5px;"></iframe>')
})()
</script></div>]]></content:encoded>
			<wfw:commentRss>http://wangling.me/2011/06/time-warp-in-animation/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Core Animation 101: From and To</title>
		<link>http://wangling.me/2011/06/core-animation-101-from-and-to/</link>
		<comments>http://wangling.me/2011/06/core-animation-101-from-and-to/#comments</comments>
		<pubDate>Mon, 27 Jun 2011 18:53:51 +0000</pubDate>
		<dc:creator>an0</dc:creator>
				<category><![CDATA[development]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[Mac]]></category>
		<category><![CDATA[Core Animation]]></category>

		<guid isPermaLink="false">http://wangling.me/?p=475</guid>
		<description><![CDATA[Core Animation is cool, beautiful, and easy to use; but not easy to use correctly. First of all, you should RTFM(Read The Fabulous Manual). Secondly, of course, you should watch the nice Session Video 424 of WWDC 2010 and play with the sample code Animation101. Now, I assume you&#8217;ve done all the prerequisite work so [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html">Core Animation</a> is cool, beautiful, and easy to use; but not easy to use correctly.</p>
<p>First of all, you should <a href="http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html">RTFM</a>(Read The Fabulous Manual). Secondly, of course, you should watch the nice <a href="https://deimos.apple.com/WebObjects/Core.woa/BrowsePrivately/adc.apple.com.4088182973.04088182975.4092394252?i=2079986024">Session Video 424</a> of WWDC 2010 and play with the sample code Animation101.</p>
<p>Now, I assume you&#8217;ve done all the prerequisite work so you must be familiar with that bouncing ball which is our protagonist.</p>
<p>Bouncing is one of the simplest form of animation, just moving from a position to another position and back. But as you are going to see below, with some extra flexibility requirements, such a straight moving is not so straightforward in code.</p>
<p>Here is bouncing I want:</p>
<ol>
<li>First tap triggers the fall of the ball.</li>
<li>Second tap triggers the rise of the ball.</li>
<li>Second tap can happen half-way along the fall and the bouncing should start immediately at that time and position.</li>
</ol>
<p><video src="http://wangling.me/wp-content/uploads/2011/06/bouncing_ball.mov" width="320" height="480" controls></video></p>
<h4>Method 1 — Property animation</h4>
<p>This is the most straightforward and recommended method. Nothing could go wrong.</p>
<p><span class="code"><span style="color: #a61390;">if</span><span style="color: #002200;">&#40;</span>viewState <span style="color: #002200;">==</span> <span style="color: #2400d9;">0</span><span style="color: #002200;">&#41;</span> <span style="color: #002200;">&#123;</span> <span style="color: #11740a; font-style: italic;">// First tap.</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span>CATransaction setAnimationDuration<span style="color: #002200;">:</span>2<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; ball.position <span style="color: #002200;">=</span> p2;<br />
<span style="color: #002200;">&#125;</span> &nbsp;<span style="color: #a61390;">else</span> <span style="color: #002200;">&#123;</span> <span style="color: #11740a; font-style: italic;">// Second tap.</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; ball.position <span style="color: #002200;">=</span> p1;<br />
<span style="color: #002200;">&#125;</span></span></p>
<h4>Method 2 — Explicit animation with cancelation via delegate</h4>
<p>Because the second animation may happen half-way during the first animation, I need to cancel the first one.</p>
<p>But when?</p>
<p>To ask this question, you should know <a href="http://developer.apple.com/library/ios/#documentation/GraphicsImaging/Reference/CABasicAnimation_class/Introduction/Introduction.html%23//apple_ref/doc/uid/TP40004496-CH1-SW4">how Core Animation interpolates values</a>.</p>
<p>The relevant rule here is the fifth one:</p>
<blockquote><p>toValue is non-nil. Interpolates between the current value of keyPath in the target layer’s presentation layer and toValue.</p></blockquote>
<p>It is a good one for my purpose &#8211; bouncing from the current presentation position. If only I can keep the current presentation position value.</p>
<p>As soon as an property animation is canceled, the presentation value of the property is gone, and the model value is used instead. So if I cancel the first animation before the second one begins, the start position(the model value, which is never changed and always the initial position) and end position will be the same, hence no visible animation will happen.</p>
<p>Following preceding analysis, I should not cancel the first animation until the second one begins; and I must cancel it no later than the second one ends, otherwise the first one may continue after the second one ends — assuming the first one is longer than the second one.</p>
<p>Fortunately, CAAnimation has two delegate methods reporting animation progress:<br />
–animationDidStart:<br />
–animationDidStop:finished:</p>
<p>Taking them at face value, I think –animationDidStart: offers a perfect cancel point. However, it turns out that it is a bit too early<sup><a href="http://wangling.me/2011/06/core-animation-101-from-and-to/#footnote_0_475" id="identifier_0_475" class="footnote-link footnote-identifier-link" title="I guess it is because Core Animation caches the from value after this delegate method is called.">1</a></sup>, so I have to use –animationDidStop:finished:.</p>
<p><span class="code"><span style="color: #a61390;">if</span><span style="color: #002200;">&#40;</span>viewState <span style="color: #002200;">==</span> <span style="color: #2400d9;">0</span><span style="color: #002200;">&#41;</span> <span style="color: #002200;">&#123;</span> <span style="color: #11740a; font-style: italic;">// First tap.</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; CABasicAnimation <span style="color: #002200;">*</span>move <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>CABasicAnimation animationWithKeyPath<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;position&quot;</span><span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; move.removedOnCompletion <span style="color: #002200;">=</span> <span style="color: #a61390;">NO</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; move.fillMode <span style="color: #002200;">=</span> kCAFillModeForwards;<br />
&nbsp; &nbsp; &nbsp; &nbsp; move.duration <span style="color: #002200;">=</span> <span style="color: #2400d9;">2</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; move.toValue <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #400080;">NSValue</span> valueWithCGPoint<span style="color: #002200;">:</span>p2<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span>ball addAnimation<span style="color: #002200;">:</span>move forKey<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;move&quot;</span><span style="color: #002200;">&#93;</span>;<br />
<span style="color: #002200;">&#125;</span> &nbsp;<span style="color: #a61390;">else</span> <span style="color: #002200;">&#123;</span> <span style="color: #11740a; font-style: italic;">// Second tap.</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; CABasicAnimation<span style="color: #002200;">*</span> stop <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>CABasicAnimation animationWithKeyPath<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;position&quot;</span><span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span>stop setValue<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;stop&quot;</span> forKey<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;tag&quot;</span><span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; stop.delegate <span style="color: #002200;">=</span> self;<br />
&nbsp; &nbsp; &nbsp; &nbsp; stop.toValue <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #400080;">NSValue</span> valueWithCGPoint<span style="color: #002200;">:</span>p1<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span>ball addAnimation<span style="color: #002200;">:</span>stop forKey<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;stop&quot;</span><span style="color: #002200;">&#93;</span>;<br />
<span style="color: #002200;">&#125;</span></span></p>
<p><span class="code"><span style="color: #002200;">-</span> <span style="color: #002200;">&#40;</span><span style="color: #a61390;">void</span><span style="color: #002200;">&#41;</span>animationDidStop<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span>CAAnimation <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>anim finished<span style="color: #002200;">:</span><span style="color: #002200;">&#40;</span><span style="color: #a61390;">BOOL</span><span style="color: #002200;">&#41;</span>flag <span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #400080;">NSString</span> <span style="color: #002200;">*</span>tag <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>anim valueForKey<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;tag&quot;</span><span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #a61390;">if</span> <span style="color: #002200;">&#40;</span><span style="color: #002200;">&#91;</span>tag isEqualToString<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;stop&quot;</span><span style="color: #002200;">&#93;</span><span style="color: #002200;">&#41;</span> <span style="color: #002200;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span>ball removeAnimationForKey<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;move&quot;</span><span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#125;</span><br />
<span style="color: #002200;">&#125;</span></span></p>
<h4>Method 3 — Explicit animation with overwriting</h4>
<p>As you can see, canceling the first animation <em>after</em> the second one begins is tricky. And to be honest, I&#8217;m not sure what&#8217;s the behavior of two animations of the same property occurring simultaneous.</p>
<p>A better approach is to capture the current presentation value myself and use it as the <strong>fromValue</strong>, and to cancel the first animation <em>before</em> the second one begins. It is much cleaner logically and reliable behaviorally.</p>
<p>There is a nice feature I can use to cancel an animation with another by overwriting — simply assigning the same key as the first one to the second one when adding the second one to the layer.</p>
<p><span class="code"><span style="color: #a61390;">if</span><span style="color: #002200;">&#40;</span>viewState <span style="color: #002200;">==</span> <span style="color: #2400d9;">0</span><span style="color: #002200;">&#41;</span> <span style="color: #002200;">&#123;</span> <span style="color: #11740a; font-style: italic;">// First tap.</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; CABasicAnimation <span style="color: #002200;">*</span>move <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>CABasicAnimation animationWithKeyPath<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;position&quot;</span><span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; move.removedOnCompletion <span style="color: #002200;">=</span> <span style="color: #a61390;">NO</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; move.fillMode <span style="color: #002200;">=</span> kCAFillModeForwards;<br />
&nbsp; &nbsp; &nbsp; &nbsp; move.duration <span style="color: #002200;">=</span> <span style="color: #2400d9;">2</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; move.toValue <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #400080;">NSValue</span> valueWithCGPoint<span style="color: #002200;">:</span>p2<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span>ball addAnimation<span style="color: #002200;">:</span>move forKey<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;position&quot;</span><span style="color: #002200;">&#93;</span>;<br />
<span style="color: #002200;">&#125;</span> &nbsp;<span style="color: #a61390;">else</span> <span style="color: #002200;">&#123;</span> <span style="color: #11740a; font-style: italic;">// Second tap.</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; CGPoint startPoint <span style="color: #002200;">=</span> <span style="color: #002200;">&#40;</span><span style="color: #002200;">&#40;</span>CALayer <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>ball.presentationLayer<span style="color: #002200;">&#41;</span>.position;<br />
&nbsp; &nbsp; &nbsp; &nbsp; CABasicAnimation<span style="color: #002200;">*</span> stop <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>CABasicAnimation animationWithKeyPath<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;position&quot;</span><span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; stop.fromValue <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #400080;">NSValue</span> valueWithCGPoint<span style="color: #002200;">:</span>startPoint<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; stop.toValue <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #400080;">NSValue</span> valueWithCGPoint<span style="color: #002200;">:</span>p1<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span>ball addAnimation<span style="color: #002200;">:</span>stop forKey<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;position&quot;</span><span style="color: #002200;">&#93;</span>;<br />
<span style="color: #002200;">&#125;</span></span></p>
<h4>Method 4 — Fall with property animation and rise with explicit animation</h4>
<p>Though I don&#8217;t know why you would want to use this manner, I want to show you how to properly implement it.</p>
<p>The fall part is exactly the same as that of Method 1, while the rise part is almost the same as that of Method 3. Reasonable enough. However, there are two things you must pay attention to:</p>
<ol>
<li>Property animations carry implicit keys named after their key paths. So the key &#8220;position&#8221; is used for the second animation to overwrite the first one which is a position property animation.</li>
<li>Even though I&#8217;m using explicit animation for the rise part, I still need to reset the model value because presentation changes do not update modal layers while modal changes do update presentation layers.</li>
</ol>
<p><span class="code"><span style="color: #a61390;">if</span><span style="color: #002200;">&#40;</span>viewState <span style="color: #002200;">==</span> <span style="color: #2400d9;">0</span><span style="color: #002200;">&#41;</span> <span style="color: #002200;">&#123;</span> <span style="color: #11740a; font-style: italic;">// First tap.</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span>CATransaction setAnimationDuration<span style="color: #002200;">:</span>2<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; ball.position <span style="color: #002200;">=</span> p2;<br />
<span style="color: #002200;">&#125;</span> &nbsp;<span style="color: #a61390;">else</span> <span style="color: #002200;">&#123;</span> <span style="color: #11740a; font-style: italic;">// Second tap.</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; CGPoint startPoint <span style="color: #002200;">=</span> <span style="color: #002200;">&#40;</span><span style="color: #002200;">&#40;</span>CALayer <span style="color: #002200;">*</span><span style="color: #002200;">&#41;</span>ball.presentationLayer<span style="color: #002200;">&#41;</span>.position;<br />
&nbsp; &nbsp; &nbsp; &nbsp; ball.position <span style="color: #002200;">=</span> p1;<br />
&nbsp; &nbsp; &nbsp; &nbsp; CABasicAnimation<span style="color: #002200;">*</span> stop <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>CABasicAnimation animationWithKeyPath<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;position&quot;</span><span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; stop.fromValue <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #400080;">NSValue</span> valueWithCGPoint<span style="color: #002200;">:</span>startPoint<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; stop.toValue <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #400080;">NSValue</span> valueWithCGPoint<span style="color: #002200;">:</span>p1<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span>ball addAnimation<span style="color: #002200;">:</span>stop forKey<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;position&quot;</span><span style="color: #002200;">&#93;</span>;<br />
<span style="color: #002200;">&#125;</span></span></p>
<h4>Method 5 — Fall with explicit animation and rise with property animation</h4>
<p>Just to be complete. Still two notice points:</p>
<ol>
<li>The key &#8220;position&#8221; is used for the first animation so that the second one, which is a position property animation, can implicitly overwrite the first one.</li>
<li>I must set the model value of position away from the initial value<sup><a href="http://wangling.me/2011/06/core-animation-101-from-and-to/#footnote_1_475" id="identifier_1_475" class="footnote-link footnote-identifier-link" title="Though I set it to the destination position of fall for consistency, you can set it to any value that is different from the initial value.">2</a></sup>, otherwise when I set the modal value back to the initial value Core Animation will not notice any change of the model value and will not trigger any animation.</li>
</ol>
<p><span class="code"><span style="color: #a61390;">if</span><span style="color: #002200;">&#40;</span>viewState <span style="color: #002200;">==</span> <span style="color: #2400d9;">0</span><span style="color: #002200;">&#41;</span> <span style="color: #002200;">&#123;</span> <span style="color: #11740a; font-style: italic;">// First tap.</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; CGPoint startPoint <span style="color: #002200;">=</span> ball.position;<br />
&nbsp; &nbsp; &nbsp; &nbsp; ball.position <span style="color: #002200;">=</span> p2;<br />
&nbsp; &nbsp; &nbsp; &nbsp; CABasicAnimation <span style="color: #002200;">*</span>move <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span>CABasicAnimation animationWithKeyPath<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;position&quot;</span><span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; move.removedOnCompletion <span style="color: #002200;">=</span> <span style="color: #a61390;">NO</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; move.fillMode <span style="color: #002200;">=</span> kCAFillModeForwards;<br />
&nbsp; &nbsp; &nbsp; &nbsp; move.duration <span style="color: #002200;">=</span> <span style="color: #2400d9;">2</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; move.fromValue <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #400080;">NSValue</span> valueWithCGPoint<span style="color: #002200;">:</span>startPoint<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; move.toValue <span style="color: #002200;">=</span> <span style="color: #002200;">&#91;</span><span style="color: #400080;">NSValue</span> valueWithCGPoint<span style="color: #002200;">:</span>p2<span style="color: #002200;">&#93;</span>;<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #002200;">&#91;</span>ball addAnimation<span style="color: #002200;">:</span>move forKey<span style="color: #002200;">:</span><span style="color: #bf1d1a;">@</span><span style="color: #bf1d1a;">&quot;position&quot;</span><span style="color: #002200;">&#93;</span>;<br />
<span style="color: #002200;">&#125;</span> &nbsp;<span style="color: #a61390;">else</span> <span style="color: #002200;">&#123;</span> <span style="color: #11740a; font-style: italic;">// Second tap.</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; ball.position <span style="color: #002200;">=</span> p1;<br />
<span style="color: #002200;">&#125;</span></span></p>
<p>You can get the sample code on <a href="https://github.com/an0/AnimationFromTo">github</a>.</p>
<div class="footnotes"><ol ><li id="footnote_0_475" class="footnote">I guess it is because Core Animation caches the <em>from value</em> <strong>after</strong> this delegate method is called.</li><li id="footnote_1_475" class="footnote">Though I set it to the destination position of fall for consistency, you can set it to any value that is different from the initial value.</li></ol></div><div class="social">			<a href="https://twitter.com/share?url=http%3A%2F%2Fwangling.me%2F2011%2F06%2Fcore-animation-101-from-and-to%2F" class="twitter-share-button" data-url="http%3A%2F%2Fwangling.me%2F2011%2F06%2Fcore-animation-101-from-and-to%2F" data-text="Core Animation 101: From and To" data-via="an0" data-count="none">Tweet</a>
			<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>			<script type="text/javascript" charset="utf-8">
(function(){
  var _w = 86 , _h = 18;
  var param = {
    url:"http%3A%2F%2Fwangling.me%2F2011%2F06%2Fcore-animation-101-from-and-to%2F",
    type:'6',
    count:'', /**是否显示分享数，1显示(可选)*/
    appkey:'', /**您申请的应用appkey,显示分享来源(可选)*/
    title:'Core Animation 101: From and To', /**分享的文字内容(可选，默认为所在页面的title)*/
    pic:'', /**分享图片的路径(可选)*/
    ralateUid:'1676354212', /**关联用户的UID，分享微博会@该用户(可选)*/
    rnd:new Date().valueOf()
  }
  var temp = [];
  for( var p in param ){
    temp.push(p + '=' + encodeURIComponent( param[p] || '' ) )
  }
  document.write('<iframe allowTransparency="true" frameborder="0" scrolling="no" src="http://hits.sinajs.cn/A1/weiboshare.html?' + temp.join('&') + '" width="'+ _w+'" height="'+_h+'" style="margin-left:5px;"></iframe>')
})()
</script></div>]]></content:encoded>
			<wfw:commentRss>http://wangling.me/2011/06/core-animation-101-from-and-to/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
<enclosure url="http://wangling.me/wp-content/uploads/2011/06/bouncing_ball.mov" length="90643" type="video/quicktime" />
		</item>
		<item>
		<title>Call for Better Private API Detecting Tools and Policy</title>
		<link>http://wangling.me/2011/02/call-for-better-private-api-detecting-tools-and-policy/</link>
		<comments>http://wangling.me/2011/02/call-for-better-private-api-detecting-tools-and-policy/#comments</comments>
		<pubDate>Thu, 17 Feb 2011 15:41:33 +0000</pubDate>
		<dc:creator>an0</dc:creator>
				<category><![CDATA[iOS]]></category>
		<category><![CDATA[Mac]]></category>
		<category><![CDATA[App Store]]></category>

		<guid isPermaLink="false">http://wangling.me/?p=348</guid>
		<description><![CDATA[Many developers have had the experience of waiting for a week before their apps get rejected just for suspicion of using Private API. This is a frustrating waste of time. The problem is that sometimes we are not sure whether there are Private API used in our code, especially with third-party frameworks involved. Since Apple [...]]]></description>
			<content:encoded><![CDATA[<p>Many developers have had the experience of waiting for a week before their apps get rejected just for <em>suspicion</em> of using Private API. This is a frustrating waste of time.</p>
<p>The problem is that sometimes we are not sure whether there are Private API used in our code, especially with third-party frameworks involved.</p>
<p>Since Apple can automatically detect Private API usage, why not:</p>
<ol>
<li>Run the auto detection as soon as apps are submitted, so that API-incompliant apps can be rejected immediately, rather than after a week of waiting.</li>
<li>Better yet, offer this auto detection tool to us developers, so that we can self-detect our apps before submission. It can save huge amount of time for both Apple and developers.</li>
</ol>
<p>Another baffling problem is that any occurrence of selectors with the same name as any mysterious-unknown-to-developers Private API seems to cause apps to be identified as incompliant and rejected. This is the equivalence of forbidding people to call their innocent friends by their names, just because they bear the same names as some unknown bad guys. In this case, we even don&#8217;t know the existence of these bad guys; they are Private.</p>
<p>As a developer, I want things to become better for all of us. Please fix this, Apple.</p>
<hr />
<p>If you are a developer who also want better Private API detecting tools and policy from Apple, please join me by duplicating my <a href="https://bugreport.apple.com/">bug report</a> #9016940. You can simply quote the content of this article above &#8211; it is Public.</p>
<div class="social">			<a href="https://twitter.com/share?url=http%3A%2F%2Fwangling.me%2F2011%2F02%2Fcall-for-better-private-api-detecting-tools-and-policy%2F" class="twitter-share-button" data-url="http%3A%2F%2Fwangling.me%2F2011%2F02%2Fcall-for-better-private-api-detecting-tools-and-policy%2F" data-text="Call for Better Private API Detecting Tools and Policy" data-via="an0" data-count="none">Tweet</a>
			<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>			<script type="text/javascript" charset="utf-8">
(function(){
  var _w = 86 , _h = 18;
  var param = {
    url:"http%3A%2F%2Fwangling.me%2F2011%2F02%2Fcall-for-better-private-api-detecting-tools-and-policy%2F",
    type:'6',
    count:'', /**是否显示分享数，1显示(可选)*/
    appkey:'', /**您申请的应用appkey,显示分享来源(可选)*/
    title:'Call for Better Private API Detecting Tools and Policy', /**分享的文字内容(可选，默认为所在页面的title)*/
    pic:'', /**分享图片的路径(可选)*/
    ralateUid:'1676354212', /**关联用户的UID，分享微博会@该用户(可选)*/
    rnd:new Date().valueOf()
  }
  var temp = [];
  for( var p in param ){
    temp.push(p + '=' + encodeURIComponent( param[p] || '' ) )
  }
  document.write('<iframe allowTransparency="true" frameborder="0" scrolling="no" src="http://hits.sinajs.cn/A1/weiboshare.html?' + temp.join('&') + '" width="'+ _w+'" height="'+_h+'" style="margin-left:5px;"></iframe>')
})()
</script></div>]]></content:encoded>
			<wfw:commentRss>http://wangling.me/2011/02/call-for-better-private-api-detecting-tools-and-policy/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Defects of KVO</title>
		<link>http://wangling.me/2010/07/defects-of-kvo/</link>
		<comments>http://wangling.me/2010/07/defects-of-kvo/#comments</comments>
		<pubDate>Sat, 17 Jul 2010 15:12:13 +0000</pubDate>
		<dc:creator>an0</dc:creator>
				<category><![CDATA[development]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[Mac]]></category>

		<guid isPermaLink="false">http://wangling.me/?p=272</guid>
		<description><![CDATA[KVO(Key-Value Observing) is a very nice programming facility from Apple. Working together with KVC(Key-Value Coding), it makes the life of Apple developers a lot easier and happier. However, I recently found some annoying problems of KVO while building some reusable programming components1 for Voodo and future iOS projects. Observers can not be queried Observers are [...]]]></description>
			<content:encoded><![CDATA[<p>KVO(<a href="http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html">Key-Value Observing</a>) is a very nice programming facility from Apple. Working together with KVC(<a href="http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/KeyValueCoding/KeyValueCoding.html">Key-Value Coding</a>), it makes the life of Apple developers a lot easier and happier.</p>
<p>However, I recently found some annoying problems of KVO while building some reusable programming components<sup><a href="http://wangling.me/2010/07/defects-of-kvo/#footnote_0_272" id="identifier_0_272" class="footnote-link footnote-identifier-link" title="I&amp;#8217;ll open source as much as possible when ready.">1</a></sup> for <a href="http://iwonderphone.com/voodo/">Voodo</a> and future iOS projects.</p>
<h4>Observers can not be queried</h4>
<p>Observers are stored in dictionaries<sup><a href="http://wangling.me/2010/07/defects-of-kvo/#footnote_1_272" id="identifier_1_272" class="footnote-link footnote-identifier-link" title="See NSKeyValueObserving protocol&amp;#8217;s observationInfo.">2</a></sup>, so it should be very easy to query whether an object A is a registered observer of object B. But one can&#8217;t do that.</p>
<h4>Unregistering an unregistered observer throws exception</h4>
<p>One can not query whether A is an observer of B in the first place, and can neither try to unregister it from B when one wants to ensure that A is unregistered from B. It is just ridiculous! And why is removing a nonexistent observer so fatal while removing a nonexistent entry from a collection is reasonably ignored?<sup><a href="http://wangling.me/2010/07/defects-of-kvo/#footnote_2_272" id="identifier_2_272" class="footnote-link footnote-identifier-link" title="See NSMutableArray&amp;#8217;s removeObject: and NSMutableDictionary&amp;#8217;s removeObjectForKey:">3</a></sup></p>
<h4>Observers/observees don&#8217;t auto-dissolve the KVO relationship in dealloc</h4>
<p>It can be easily done in KVO framework code since it already has the data structure keeping the observer-observee relationship information, and is very reasonable things to do &#8211; when any part of a relationship is gone the relationship is ended. Without the auto-unregistration, developers&#8217; responsibility is unnecessarily heavier. In fact, it is no easy work in cases where observers outlive observees, because an observee needs to notify its observers of its death so the observers can detach themselves from the dying observee.<sup><a href="http://wangling.me/2010/07/defects-of-kvo/#footnote_3_272" id="identifier_3_272" class="footnote-link footnote-identifier-link" title="Normally it is the observers that are doing the KVO relationship management and it is the job of whoever did the connection to do the disconnection, but vice versa">4</a></sup></p>
<h3>All in all</h3>
<p>KVO leaves some extra, tedious, and burdensome relationship management work to us while it could be easily and efficiently done at the root of itself.<sup><a href="http://wangling.me/2010/07/defects-of-kvo/#footnote_4_272" id="identifier_4_272" class="footnote-link footnote-identifier-link" title="Well, as Appler eskimo1 said in devforums, it is harder than one might think because KVO needs to be both thread safe and GC clean, and must be careful to not impact the performance of code that isn&amp;#8217;t using it.">5</a></sup></p>
<p>The KVO relationship management is especially stressful for observers watching many dynamic observees which is not unusual for generic components &#8211; <a href="https://developer.apple.com/iphone/library/documentation/UIKit/Reference/UINavigationController_Class/Reference/Reference.html">UINavigationController</a> and <a href="https://developer.apple.com/iphone/library/documentation/UIKit/Reference/UITabBarController_Class/Reference/Reference.html">UITabBarController</a> both manage a dynamic group of UIViewControllers and may observe some properties of them. It is even worse in my component because not only the observee but also the observed properties of them are dynamic. May Apple save me (and you?).</p>
<div class="footnotes"><ol ><li id="footnote_0_272" class="footnote">I&#8217;ll open source as much as possible when ready.</li><li id="footnote_1_272" class="footnote">See NSKeyValueObserving protocol&#8217;s <a href="https://developer.apple.com/iphone/library/documentation/Cocoa/Reference/Foundation/Protocols/NSKeyValueObserving_Protocol/Reference/Reference.html#//apple_ref/occ/instm/NSObject/observationInfo">observationInfo</a>.</li><li id="footnote_2_272" class="footnote">See NSMutableArray&#8217;s <a href="http://developer.apple.com/iphone/library/documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/Reference/Reference.html#//apple_ref/occ/instm/NSMutableArray/removeObject:">removeObject:</a> and NSMutableDictionary&#8217;s <a href="http://developer.apple.com/iphone/library/documentation/Cocoa/Reference/Foundation/Classes/NSMutableDictionary_Class/Reference/Reference.html#//apple_ref/occ/instm/NSMutableDictionary/removeObjectForKey:">removeObjectForKey:</a></li><li id="footnote_3_272" class="footnote">Normally it is the observers that are doing the KVO relationship management and it is the job of whoever did the connection to do the disconnection, but vice versa</li><li id="footnote_4_272" class="footnote">Well, as Appler eskimo1 said in <a href="https://devforums.apple.com/message/259690#259690">devforums</a>, it is harder than one might think because <q>KVO needs to be both thread safe and GC clean, and must be careful to not impact the performance of code that isn&#8217;t using it</q>.</li></ol></div><div class="social">			<a href="https://twitter.com/share?url=http%3A%2F%2Fwangling.me%2F2010%2F07%2Fdefects-of-kvo%2F" class="twitter-share-button" data-url="http%3A%2F%2Fwangling.me%2F2010%2F07%2Fdefects-of-kvo%2F" data-text="Defects of KVO" data-via="an0" data-count="none">Tweet</a>
			<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>			<script type="text/javascript" charset="utf-8">
(function(){
  var _w = 86 , _h = 18;
  var param = {
    url:"http%3A%2F%2Fwangling.me%2F2010%2F07%2Fdefects-of-kvo%2F",
    type:'6',
    count:'', /**是否显示分享数，1显示(可选)*/
    appkey:'', /**您申请的应用appkey,显示分享来源(可选)*/
    title:'Defects of KVO', /**分享的文字内容(可选，默认为所在页面的title)*/
    pic:'', /**分享图片的路径(可选)*/
    ralateUid:'1676354212', /**关联用户的UID，分享微博会@该用户(可选)*/
    rnd:new Date().valueOf()
  }
  var temp = [];
  for( var p in param ){
    temp.push(p + '=' + encodeURIComponent( param[p] || '' ) )
  }
  document.write('<iframe allowTransparency="true" frameborder="0" scrolling="no" src="http://hits.sinajs.cn/A1/weiboshare.html?' + temp.join('&') + '" width="'+ _w+'" height="'+_h+'" style="margin-left:5px;"></iframe>')
})()
</script></div>]]></content:encoded>
			<wfw:commentRss>http://wangling.me/2010/07/defects-of-kvo/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Documentation Set Generation Tool in Xcode is Wanted</title>
		<link>http://wangling.me/2010/07/documentation-set-generation-tool-in-xcode-is-wanted/</link>
		<comments>http://wangling.me/2010/07/documentation-set-generation-tool-in-xcode-is-wanted/#comments</comments>
		<pubDate>Thu, 15 Jul 2010 02:30:50 +0000</pubDate>
		<dc:creator>an0</dc:creator>
				<category><![CDATA[development]]></category>
		<category><![CDATA[Mac]]></category>
		<category><![CDATA[Xcode]]></category>

		<guid isPermaLink="false">http://wangling.me/?p=250</guid>
		<description><![CDATA[Documentation set generation tool in Xcode to generate documentation sets in the style of Apple Developer Documentation is necessary for a prosperous Apple developer community. The current HeaderDoc and Doxygen are both far from Apple standard. HeaderDoc is obviously not favored by developers. Doxygen is better and even officially advocated by Apple, but still tedious [...]]]></description>
			<content:encoded><![CDATA[<p>Documentation set generation tool in Xcode to generate documentation sets in the style of Apple Developer Documentation is necessary for a prosperous Apple developer community. The current <a href="http://developer.apple.com/opensource/tools/headerdoc.html">HeaderDoc</a> and <a href="http://www.stack.nl/~dimitri/doxygen/index.html">Doxygen</a> are both far from Apple standard.</p>
<p>HeaderDoc is obviously <a href="http://stackoverflow.com/questions/174315/how-do-you-document-your-source-code-in-xcode">not favored by developers</a>. Doxygen is better and even <a href="http://developer.apple.com/tools/creatingdocsetswithdoxygen.html">officially advocated by Apple</a>, but still tedious to use and just can&#8217;t generate Apple standard documentations(see a nice example <a href="http://dysart.cs.byu.edu/CHDataStructures/">here</a>). <a href="http://github.com/mattball/doxyclean">Doxyclean</a> is very helpful in converting Doxygen output to resemble Apple Developer Documentation, but seems not capable to generate Xcode Documentation Sets. Following the work of Doxyclean, <a href="http://github.com/tomaz/appledoc">appledoc</a> supplements some missing features like Xcode Documentation Set generation.</p>
<p>To sum up, there are several third-party tools doing the work that should be better done by Apple in Apple way. Even though they can do their best to generate very Apple like documentations, they can not offer the smooth integration in Xcode that is otherwise possible if it is done by Apple.</p>
<p>So, I&#8217;ve posted a bug report(Problem ID: 8193210) to request such a Documentation Set generation tool. If you also want it, please go to duplicate a <a href="http://bugreport.apple.com/">bug report</a> with following content:</p>
<blockquote><p>I vote for bug report ‘8193210′.</p>
<p>Summary:<br />
Documentation set generation tool in Xcode to generate documentation set in the style of Apple Developer Documentation is necessary for a prosperous Apple developer community. The current HeaderDoc and doxygen are both far from the Apple standard.</p>
<p>Steps to Reproduce:</p>
<p>Expected Results:</p>
<p>Actual Results:</p>
<p>Regression:</p>
<p>Notes:
</p></blockquote>
<p>Read <a href="http://finbarrbrady.com/2010/07/help-us-get-international-promo-codes/">this article</a> to learn why and how to vote for a bug report.<sup><a href="http://wangling.me/2010/07/documentation-set-generation-tool-in-xcode-is-wanted/#footnote_0_250" id="identifier_0_250" class="footnote-link footnote-identifier-link" title="Surely I&amp;#8217;ve voted this &amp;#8220;international promo codes&amp;#8221; bug report since I&amp;#8217;m from China.">1</a></sup></p>
<div class="footnotes"><ol ><li id="footnote_0_250" class="footnote">Surely I&#8217;ve voted this &#8220;international promo codes&#8221; bug report since I&#8217;m from China.</li></ol></div><div class="social">			<a href="https://twitter.com/share?url=http%3A%2F%2Fwangling.me%2F2010%2F07%2Fdocumentation-set-generation-tool-in-xcode-is-wanted%2F" class="twitter-share-button" data-url="http%3A%2F%2Fwangling.me%2F2010%2F07%2Fdocumentation-set-generation-tool-in-xcode-is-wanted%2F" data-text="Documentation Set Generation Tool in Xcode is Wanted" data-via="an0" data-count="none">Tweet</a>
			<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>			<script type="text/javascript" charset="utf-8">
(function(){
  var _w = 86 , _h = 18;
  var param = {
    url:"http%3A%2F%2Fwangling.me%2F2010%2F07%2Fdocumentation-set-generation-tool-in-xcode-is-wanted%2F",
    type:'6',
    count:'', /**是否显示分享数，1显示(可选)*/
    appkey:'', /**您申请的应用appkey,显示分享来源(可选)*/
    title:'Documentation Set Generation Tool in Xcode is Wanted', /**分享的文字内容(可选，默认为所在页面的title)*/
    pic:'', /**分享图片的路径(可选)*/
    ralateUid:'1676354212', /**关联用户的UID，分享微博会@该用户(可选)*/
    rnd:new Date().valueOf()
  }
  var temp = [];
  for( var p in param ){
    temp.push(p + '=' + encodeURIComponent( param[p] || '' ) )
  }
  document.write('<iframe allowTransparency="true" frameborder="0" scrolling="no" src="http://hits.sinajs.cn/A1/weiboshare.html?' + temp.join('&') + '" width="'+ _w+'" height="'+_h+'" style="margin-left:5px;"></iframe>')
})()
</script></div>]]></content:encoded>
			<wfw:commentRss>http://wangling.me/2010/07/documentation-set-generation-tool-in-xcode-is-wanted/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>说说 Mac 下的截屏软件</title>
		<link>http://wangling.me/2010/03/%e8%af%b4%e8%af%b4-mac-%e4%b8%8b%e7%9a%84%e6%88%aa%e5%b1%8f%e8%bd%af%e4%bb%b6/</link>
		<comments>http://wangling.me/2010/03/%e8%af%b4%e8%af%b4-mac-%e4%b8%8b%e7%9a%84%e6%88%aa%e5%b1%8f%e8%bd%af%e4%bb%b6/#comments</comments>
		<pubDate>Sun, 28 Mar 2010 07:37:11 +0000</pubDate>
		<dc:creator>an0</dc:creator>
				<category><![CDATA[中文]]></category>
		<category><![CDATA[Mac]]></category>
		<category><![CDATA[Voodo]]></category>

		<guid isPermaLink="false">http://blog.wangling.me/?p=200</guid>
		<description><![CDATA[刚完成了 Voodo 2 的开发，正在写用户手册，其中用到不少截图。 对 app 自身的截图，用真机 + Xcode 就能完美解决，但是对截图做注释就需额外的工具来完成。 一说到对图片进行操作，大家容易先想到 Photoshop, Illustrator，OmniGraffle 等重器，轻一点的也是 Acorn，Pixelmator 等。 这些专业的图片编辑软件确实强大，有些功能只有它们能提供。比如，我试了一圈，最终发现还是 Illustrator 里的 gradient 强大，是唯一一个直接支持 ellipse gradient 效果的，于是在对截图做聚光效果时都是用的 Illustrator。 既然我在对 Voodo 2 做截图时用的是 Xcode，后续工作是图片注释，为什么还需要截屏软件呢？ 这是因为，其实大多截屏软件都自带注释功能，足以应付绝大多数需求；而且因为 Voodo 有同步 Google Calendar 的功能，我需要对 Google Calendar 上的操作进行一些截图。 下面就我用过的一些截屏软件做一个简单的比较（排名无明显先后）： Snagit Snagit 不愧是老牌领头羊，beta 就已经多方面超越其他先入场的小弟了，操作的灵活性，注释工具都是最好的。beta 版免费使用，很爽。 没有单独的 Full Screen Capture，那是因为根本不需要，人家的 All-in-One Capture 太帅了，自动识别 Full Screen，Window，甚至 Region [...]]]></description>
			<content:encoded><![CDATA[<p>刚完成了 <a href="http://blog.iwonderphone.com/2010/03/27/try-voodo-2-early-and-win-promo-code/">Voodo 2</a> 的开发，正在写用户手册，其中用到不少截图。</p>
<p>对 app 自身的截图，用真机 + Xcode 就能完美解决，但是对截图做注释就需额外的工具来完成。</p>
<p>一说到对图片进行操作，大家容易先想到 Photoshop, Illustrator，OmniGraffle 等重器，轻一点的也是 Acorn，Pixelmator 等。</p>
<p>这些专业的图片编辑软件确实强大，有些功能只有它们能提供。比如，我试了一圈，最终发现还是 Illustrator 里的 gradient 强大，是唯一一个直接支持 ellipse gradient 效果的，于是在对截图做聚光效果时都是用的 Illustrator。<br />
<img src="http://blog.wangling.me/wp-content/uploads/2010/03/inactive_search_bar.png" alt="" title="Inactive Search Bar" width="320" height="460" class="aligncenter size-full wp-image-204" /></p>
<p>既然我在对 <a href="http://iwonderphone.com/voodo/">Voodo</a> 2 做截图时用的是 Xcode，后续工作是图片注释，为什么还需要截屏软件呢？</p>
<p>这是因为，其实大多截屏软件都自带注释功能，足以应付绝大多数需求；而且因为 <a href="http://iwonderphone.com/voodo/">Voodo</a> 有同步 Google Calendar 的功能，我需要对 Google Calendar 上的操作进行一些截图。</p>
<p>下面就我用过的一些截屏软件做一个简单的比较（排名无明显先后）：</p>
<h3><a href="http://www.techsmith.com/snagitmac/">Snagit</a></h3>
<p>Snagit 不愧是老牌领头羊，beta 就已经多方面超越其他先入场的小弟了，操作的灵活性，注释工具都是最好的。beta 版免费使用，很爽。<br />
<a href="http://blog.wangling.me/wp-content/uploads/2010/03/Snagit.png"><img src="http://blog.wangling.me/wp-content/uploads/2010/03/Snagit.png" alt="" title="Snagit" width="893" height="710" class="aligncenter size-full wp-image-215" /></a></p>
<p>没有单独的 Full Screen Capture，那是因为根本不需要，人家的 All-in-One Capture 太帅了，自动识别 Full Screen，Window，甚至 Region in Window，还有 Capture Scrolling Area 能对需要滚动才能看全的页面做完整的截图。<br />
<a href="http://blog.wangling.me/wp-content/uploads/2010/03/Snagit-Capturing.png"><img src="http://blog.wangling.me/wp-content/uploads/2010/03/Snagit-Capturing.png" alt="" title="Snagit-Capturing" width="890" height="754" class="aligncenter size-full wp-image-214" /></a></p>
<p>Share 功能尚未成型，暂时只内嵌了 Email 功能。相信正式版肯定会有完善的 Share 功能，看看 <a href="http://www.techsmith.com/">TechSmith</a> 的其他软件就知道了，何况人家还拥有自己的专业多媒体资源分享网站 <a href="http://www.screencast.com/">Screencast.com</a>。</p>
<h3><a href="http://www.ambrosiasw.com/utilities/snapzprox/">Snapz Pro X</a></h3>
<p>Snapz Pro X 没有注释功能，也不带 Share。但就截图的一个“截”字来说，Snapz Pro X 确实可算是最强悍的，算是名副其实。看一个它特有的截图功能：<br />
<a href="http://blog.wangling.me/wp-content/uploads/2010/03/Snapz-Pro-X.png"><img src="http://blog.wangling.me/wp-content/uploads/2010/03/Snapz-Pro-X.png" alt="" title="Snapz Pro X" width="610" height="214" class="aligncenter size-full wp-image-208" /></a></p>
<p>其实把它放到这里并不完全合适，因为 Snapz Pro X 同时还是强大的 Screencast 录制软件。我购买它就是为了给 <a href="http://iwonderphone.com/voodo/">Voodo</a> 录制 <a href="http://blog.iwonderphone.com/2010/03/27/try-voodo-2-early-and-win-promo-code/">demo</a> 及 <a href="http://iwonderphone.com/voodo/">promo video</a>，配合着 iMovie 做的后期制作，一般的效果都能达到。</p>
<h3><a href="http://skitch.com/">Skitch</a></h3>
<p>Skitch 的 UI 设计相当独特，类似一个相框，上传分享特别方便（skitch.com，flickr，Mobile Me 等一应俱全），截图质量和注释工具都不错，而且免费。性价比最高，估计是目前最普及的截图软件。我一般都通过 Skitch 上传到 <a href="http://skitch.com/an00na/nhdsx/iphone-simulator">skitch.com</a> 秀图给别人看。<br />
<a href="http://blog.wangling.me/wp-content/uploads/2010/03/Skitch-Capturing.png"><img src="http://blog.wangling.me/wp-content/uploads/2010/03/Skitch-Capturing.png" alt="" title="Skitch-Capturing" width="508" height="892" class="aligncenter size-full wp-image-210" /></a><br />
<a href="http://blog.wangling.me/wp-content/uploads/2010/03/Skitch.png"><img src="http://blog.wangling.me/wp-content/uploads/2010/03/Skitch.png" alt="" title="Skitch" width="543" height="910" class="aligncenter size-full wp-image-211" /></a></p>
<h3><a href="http://www.realmacsoftware.com/littlesnapper/">LittleSnapper</a></h3>
<p>LittleSnapper 可能是最眼熟的一个名字。软件本身 UI 也算清爽，功能也算齐全，支持多种 Share 方式，包括 flickr，但上手感觉不利索，不像名字取的那样 little。<br />
<a href="http://blog.wangling.me/wp-content/uploads/2010/03/LittleSnapper.png"><img src="http://blog.wangling.me/wp-content/uploads/2010/03/LittleSnapper.png" alt="" title="LittleSnapper" width="958" height="668" class="aligncenter size-full wp-image-220" /></a></p>
<p>我特别不喜欢它的截图方式，定位不如其他三个的十字交叉线来的方便：<br />
<img src="http://blog.wangling.me/wp-content/uploads/2010/03/LittleSnapper-Capturing.png" alt="" title="LittleSnapper-Capturing" width="385" height="741" class="aligncenter size-full wp-image-219" /></p>
<p>不过，LittleSnapper 也有亮点：1) 组织管理功能比较强大，适合剪贴报爱好者；2）强大的网页截图功能，它自带 HTML 解析器，能定位到 Element，适合网页设计者。<br />
<a href="http://blog.wangling.me/wp-content/uploads/2010/03/LittleSnapper-Websnap.png"><img src="http://blog.wangling.me/wp-content/uploads/2010/03/LittleSnapper-Websnap.png" alt="" title="LittleSnapper-Websnap" width="1050" height="747" class="aligncenter size-full wp-image-226" /></a></p>
<h3>系统自带</h3>
<p>最后，不得不说，其实 Mac 系统自带的截图功能对付日常工作已经绰绰有余，截图质量更是没得说。再配上 Preview 做点简单的注释，80%的活都能应付。<br />
<a href="http://blog.wangling.me/wp-content/uploads/2010/03/System-Screen-Capture.png"><img src="http://blog.wangling.me/wp-content/uploads/2010/03/System-Screen-Capture.png" alt="" title="System Screen Capture" width="602" height="817" class="aligncenter size-full wp-image-232" /></a><br />
<a href="http://blog.wangling.me/wp-content/uploads/2010/03/Preview-Annotation.png"><img src="http://blog.wangling.me/wp-content/uploads/2010/03/Preview-Annotation.png" alt="" title="Preview Annotation" width="730" height="900" class="aligncenter size-full wp-image-229" /></a></p>
<div class="social">			<a href="https://twitter.com/share?url=http%3A%2F%2Fwangling.me%2F2010%2F03%2F%25e8%25af%25b4%25e8%25af%25b4-mac-%25e4%25b8%258b%25e7%259a%2584%25e6%2588%25aa%25e5%25b1%258f%25e8%25bd%25af%25e4%25bb%25b6%2F" class="twitter-share-button" data-url="http%3A%2F%2Fwangling.me%2F2010%2F03%2F%25e8%25af%25b4%25e8%25af%25b4-mac-%25e4%25b8%258b%25e7%259a%2584%25e6%2588%25aa%25e5%25b1%258f%25e8%25bd%25af%25e4%25bb%25b6%2F" data-text="说说 Mac 下的截屏软件" data-via="an0" data-count="none">Tweet</a>
			<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>			<script type="text/javascript" charset="utf-8">
(function(){
  var _w = 86 , _h = 18;
  var param = {
    url:"http%3A%2F%2Fwangling.me%2F2010%2F03%2F%25e8%25af%25b4%25e8%25af%25b4-mac-%25e4%25b8%258b%25e7%259a%2584%25e6%2588%25aa%25e5%25b1%258f%25e8%25bd%25af%25e4%25bb%25b6%2F",
    type:'6',
    count:'', /**是否显示分享数，1显示(可选)*/
    appkey:'', /**您申请的应用appkey,显示分享来源(可选)*/
    title:'说说 Mac 下的截屏软件', /**分享的文字内容(可选，默认为所在页面的title)*/
    pic:'', /**分享图片的路径(可选)*/
    ralateUid:'1676354212', /**关联用户的UID，分享微博会@该用户(可选)*/
    rnd:new Date().valueOf()
  }
  var temp = [];
  for( var p in param ){
    temp.push(p + '=' + encodeURIComponent( param[p] || '' ) )
  }
  document.write('<iframe allowTransparency="true" frameborder="0" scrolling="no" src="http://hits.sinajs.cn/A1/weiboshare.html?' + temp.join('&') + '" width="'+ _w+'" height="'+_h+'" style="margin-left:5px;"></iframe>')
})()
</script></div>]]></content:encoded>
			<wfw:commentRss>http://wangling.me/2010/03/%e8%af%b4%e8%af%b4-mac-%e4%b8%8b%e7%9a%84%e6%88%aa%e5%b1%8f%e8%bd%af%e4%bb%b6/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>

