[iOS]借助VoiceOver来破解优秀App的实现方式

为了开发出更好的iOS App,我们经常会去破解(我更喜欢叫Hack)优秀的App的实现方式,一般我们做的无非就是拆了ipa包浏览一下App所用到的图片资源,找个抓包工具看看都访问了那些网络资源,更虎点的就是去反编译,当然最基础的就是把玩一下各个功能,凭经验看出实现方法。

今天就介绍一个比较另类的Hack方式——VoiceOver,估计大部分人都没在自己的iOS设备上打开过这个功能,我就简单说一下效果吧,更细节的东西还是要各位亲身体验一下。

VoiceOver需要在:通用 >> 辅助功能 >> VoiceOver 页面里开启,打开后会进入一个相对比较难操作的环境,随着手指在屏幕上移动,会有一个黑框附着在手指附近的UI元素上面,同时随着黑框的移动,iOS设备会自动朗读UI元素的内容,不难发现VoiceOver这个黑框具有以下几个特性:

  1. 黑框所在位置就是当前UI元素的真实边框
  2. 对于一些元素比如UIButton,当黑框附着在起上面时,系统就会朗读“xxxxx按钮”,对于UITableView更是可以朗读出当前所在行号,以及一共有多少行。朗读的内容通常包含了这些UI元素的独有特性。
  3. 由于大部分的App都不会对“辅助功能”有所支持,有些没有文字的UI元素系统甚至会朗读其变量名,这跟反编译已经比较相似了。

借助第一个特性我们可以破解App的UI布局,借助第二个特性我们可以找出一些UI元素的实现方式,比如有些Gird View就是用UITableView实现的,如果系统朗读了行号等UITableView所具有的特征内容,我们就可以基本确定这个Gird View就是用TableView来实现的,TabBar之类的也是如此。借助第三条特性,我们可以通过其变量名判断其功能和实现方法。此外对于那些开源应用,变量名也可以方便我们更快地研究开源代码。

相对的,如果我们想避免App的关键实现细节被破解,就要在代码中加入防止VoiceOver响应的代码。

综上,虽然VoiceOver属于辅助功能范畴,但其一些特性为我们Hack App提供了方便。在一般的应用中加入Accessibility支持可能会使开发成本有所提高,但对于某些领域的应用,Accessibility支持无疑会成为应用的一大两点,在此也推荐大家看一下 WWDC 2011 Session 127 – Design Patterns to Simplify Mac Accessibility,真的挺有爱的。

–以上—

Xcode 4.3+ NSLog中文不输出Bug及解决方法

这个Bug折腾了我近一天,读一个文件,NSLog输出文件内容,结果死活读不完整,不光中文没有,英文也不全,考虑了编码,文件大小,文件位置等各种可能的因素,尝试用各种方式重写这个操作,最后发现是NSLog的问题,跟文件一毛钱关系都没有,感谢这两个链接:link1 link2 。

鉴于第二个链接已经解释的很清楚了,我这里就简要说一下,毕竟Wall还是有些麻烦

重现Bug很简单,Xcode 4.3+,用lldb在真机上运行(模拟器没有问题)下面代码:

NSLog(@"English1");
NSLog(@"中文");
NSLog(@"English2");

中文那行神马都不输出啊!如果NSLog一个NSString,String里面有中文,那么输出也会悲剧。

解决方法两种:

  1. 如果你执着于lldb,那么用Organizer >> Devices >> 你的设备 >> Console 这里会显示中文
  2. 按住Option点Run(或者 Product >> Edit Scheme…),Info >> Debugger 设置为GDB

一切回归正常,WTF!

另外有人说4.3.2解决了这个问题,事实是:没有解决!

祝愿被这个问题折腾死的人能早日看到这篇文章或者link2那篇文章。

Becareful with XCode!!

–以上–

XCode 4.3.1一个小众Bug及解决方法

哦… 首先希望闲来无事发现此文的人,可以回复报下你们一天中XCode Crash的次数,附上版本,大家比比谁受气受得多。(我:4.3.1 10多次crash)

目前测试Bug的环境:XCode 4.3.1,用svn作为版本控制
PS. 4.3可能也有这个Bug,4.2.1貌似没有,但不排除svn下的其他毛病

Bug产生过程:

  1. Project处于svn版本控制之下
  2. (可有可无)向Project中添加文件,并加入版本控制(A)
  3. 从项目中Delete某个文件,并选择Move to trash
  4. 编译的时候会有Wraning提示缺少了之前删除的那个文件(Missing File xxxx)

虽然这是一个误报,对编译没有任何影响,但是有Warning看着就心烦,而且如果删除了很多文件就会造成满屏Warning,既影响心情,又影响工作。

下面是几种解决方法

  1. 如果你对XCode 4.3+移植抱有敌视态度,那么你的/Developer或者废纸篓里面可能还有XCode 4.2.1,用4.2.1进行删除的步骤,那些Warning就不会出现。
  2. 完全用XCode 4.3.1 + Terminal解决
    1. 对那些你要删除的并且有"A"(版本控制)标记的文件,右键 >> Source Control >> Discard Changes…
    2. 右键 >> Source Control >> Ignore
    3. Delete并Move to trash

这样编译就不会有warning,如果你删除的是一个文件夹,那么还会提示Missing这个文件夹,再或者上一步删除文件之前忘记设置ignore了,warning就会出现,用终端解决:
1. 打开终端,cd到项目主目录下
2. svn status,可以看到那个Warning的文件(夹)
3. svn rm xxxxx 从版本控制里去掉那个文件(夹)

这时再编译就不会有那个warning了,世界终于清净了

总结:XCode 4+对svn支持非常废,经常有莫名其妙错误,要手动解决,所以一个更方便的方法就是不用svn,现在git这么火,不怕开源的项目就直接放到github上得了,保密的就放到Bitbucket上,或者找个服务器自己搭一个,总比折腾svn这些破事好,不过广大开发者最终还是应该鄙视苹果的,照这么下去苹果还是回到几年前的小众状态比较好,那时无论是SL,还是Xcode 3都是精品中的精品。现在苹果到了风口浪尖上,做东西都很浮躁,各种神奇,傻X的Bug层出不穷,老乔也不托梦骂他们几句。

懒得喷了,话说中午写的这篇,晚上发的,发现XCode 4.3.2出了,不知道老乔显灵没,如果还Crash记得上App Store评一星啊!link

更新:恭喜,XCode 4.3.2下这个Bug依旧

–以上–

iPad 3 图片解压缩测试

之前翻译过Avoiding Image Decompression Sickness[iOS]如何避免图像解压缩的时间开销,现在iPad 3出了,原文也有了后续iPad 3 Image Decompression Benchmarked,所以我也就简单再翻译个后续,推荐先看一眼之前那篇,以下内容都是建立在之前的基础上的。

此次原文没有太多重要内容,挑重点简要翻译,括号内大部分是我的注释,少部分原文,很好辨别。

iPad !!!(iPad 3,这是跟k-on!!或者working!!学的么?),根据GeekBench测试,CPU几乎没有变化(因为A5X只是显卡4核心,CPU还是和iPad 2一样),根据GLBenchmark的结果,苹果对iPad 3做了很大优化,是的在Retina屏幕上的祯数几乎和iPad 2相同。

一位Australia的同学帮忙在他的iPad 3上帮我们运行了benchmark,并把测试结果发给了我们。(之前的结果参考之前的那篇文章,我就不贴图了)

我们预想iPad 3解压缩速度将是iPad 2的2-4倍,iPad 2默认的launch image需要100ms来解压缩并显示,如果在iPad 3上也有相同的速度就可以了。

测试结果如下

PNG Crushed, 1024*768 (init+decode+draw)

  • iPad 2: 5 ms + 89 ms + 18 ms = 113 ms
  • iPad 3: 1 ms + 50 ms + 18 ms = 69 ms

-47%

JPG 80%, 1024*768 (init+decode+draw)

  • iPad 2: 2 ms + 32 ms + 18 ms = 52 ms
  • iPad 3: 2 ms + 32 ms + 17 ms = 51 ms

-2%

PNG Crushed, 2048*1536 (init+decode+draw)

  • iPad 2: 5 ms + 266 ms + 96 ms = 368 ms
  • iPad 3: 2 ms + 171 ms + 66 ms = 238 ms

-33%

JPG 80%, 2048*1536 (init+decode+draw)

  • iPad 2: 1 ms + 121 ms + 69 ms = 192 ms
  • iPad 3: 1 ms + 122 ms + 66 ms = 189 ms

-2%

粗略看下我们发现iPad 3的速度并没有我们预想的那样有4倍的提升。退一步讲,iPad 3的效率的确像Apple描述的那样有2倍的提升(-50%)(A5X比A5显卡快了一倍),但这仅仅针对处理PNG Crushed的情形(-47%),在处理JPEG的时候速度仅仅提高2%,这点儿提升也许是来自SSD读取速度的提升,看起来苹果的开发人员似乎都将JPEG遗忘了。

至于"感觉上" iPad 3是不是比iPad 2快了呢?

恐怕不是,iPad 2显示launch image要113ms,iPad 3上却要消耗238ms,如果没有任何优化的话,一个应用启动要多消耗125ms(记住CPU性能是相同的)。

因此最大的问题就是,杂志类应用开发者更期望这种“感觉上”的速度提升,而实际上即使使用80%全屏大小的JPEG也只能得到5-19(128ms-52ms)的祯数,这就导致了如果在主线程绘制图片,从一个页Scroll到下一个页时就会产生一个明显的卡顿。

总结

现在我们知道为什么新的核心命名为A5X了,这仅仅是与A5相同的CPU配上了一个性能稍高点的显示核心。这个“X”我们计算出来大概为1.4,但仅仅针对PNG,对于JPEG,也许在下一个iOS更新中体现吧。

目前发生这种情况的原因很可能是这样:针对JPEG图片的硬件加速处理模块因为某种情况在iOS 5.1中被遗忘了,或者干脆没有准备好。如果事实果真如此,那么对于苹果来说这真的很尴尬。

最后一段原文打广告。大概就是建议用大图的人暂时只留下必须的小图片,使用他们做的DTCoreText来渲染富文本(蛋疼核心文本?LOL)。(要我说按苹果这么个折腾法矢量图才是王道啊!)

–以上–

[iOS]在运行时为类添加方法

Obj-C用起来真是各种happy,比如现在有这样一种情况:有一个类,我们希望它能响应一个消息(message),但是这个类没有相应的方法(method),而你又偏偏不能重写/继承这个类。这时我们可能会想到,能不能动态地给类添加一个方法呢?感谢Obj-C,仅需简单几步就能实现。

先看一段代码

#if TARGET_IPHONE_SIMULATOR
#import 
#else
#import 
#import 
#endif

@interface EmptyClass:NSObject

@end

@implementation EmptyClass

@end

void sayHello(id self, SEL _cmd) {
    NSLog(@"Hello");
}

- (void)addMethod {
    class_addMethod([EmptyClass class], @selector(sayHello2), (IMP)sayHello, "[email protected]:");

    // Test Method
    EmptyClass *instance = [[EmptyClass alloc] init];
    [instance sayHello2];

    [instance release];

}

我们首先定义了一个EmptyClass,继承NSObject,没有任何自带方法,接着定义了一个函数。这里提一句,Obj-C的方法(method)就是一个至少需要两个参数(self,_cmd)的C函数,这个函数仅仅输出一句Hello。接下来在addMethod方法中,我们调用class_addMethod()为EmptyClass添加方法,class_addMethod()是这样定义的:

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

参数说明:

cls:被添加方法的类

name:可以理解为方法名,这个貌似随便起名,比如我们这里叫sayHello2

imp:实现这个方法的函数

types:一个定义该函数返回值类型和参数类型的字符串,这个具体会在后面讲

接着创建EmptyClass的实例,调用sayHello2,运行,输出Hello,添加方法成功。

接下来说一下types参数,
比如我们要添加一个这样的方法:-(int)say:(NSString *)str;
相应的实现函数就应该是这样:

int say(id self, SEL _cmd, NSString *str) {
    NSLog(@"%@", str);
    return 100;//随便返回个值
}

class_addMethod这句就应该这么写:

class_addMethod([EmptyClass class], @selector(say:), (IMP)say, "[email protected]:@");

其中types参数为"[email protected]:@“,按顺序分别表示:

i:返回值类型int,若是v则表示void

@:参数id(self)

::SEL(_cmd)

@:id(str)

这些表示方法都是定义好的(Type Encodings),关于Type Encodings的其他类型定义请参考官方文档

最后调用say:方法:

int a = [instance say:@"something"];
NSLog(@"%d", a);

输出something和100。

关于本文所涉及内容的详细信息请参考Objective-C Runtime Reference

本文参考了:

推荐去看看
—以上—

[iOS]如何避免图像解压缩的时间开销

这是一篇译文,(原文"Avoiding Image Decompression Sickness"在此),原文是我看过的非常不错的一篇关于iOS图片显示的一些文章,解决了我的一些疑惑和问题,因此翻译过来分享,为保证一定的通顺性其中一部分内容与原文有些许出入,但我尽量保证了意思的一致性,欢迎指正批评,横线之间为译文,略挫,见谅:


当开始iCatalog.framework的工作时,我发现使用大尺寸图片会引起一些恼人的问题,“大”意味着这个图片有足够大的分辨率(1024×768)来覆盖iPad的整个屏幕,或者覆盖未来Retina Display iPad(如果有的话)的双倍分辨率(2048×1536)屏幕。

想像一个杂志类型的App,一个分页的UIScrollView,每页显示一个UIImageView,一旦某一页进入屏幕区域你就要为这个页创建或者重用一个UIImageView并把它放到scrollView的当前显示区域,即使这个页只有一个像素进入到屏幕区域,你还是要做这些工作。这在模拟器上运行得非常好,但在真机上进行测试,你会发现每次进入下一页时都会有一个明显的延迟。这个延迟来自于将图片从文件解压缩并渲染到屏幕上这一系列的工作。不幸的是UIImage仅在图片将要显示的时候做这个解压工作。

因为添加一个view到当前的view层次结构中必须在主线程上进行,所以图片的解压缩和之后渲染到屏幕上的工作也在主线程进行,这就是这个延迟产生的原因,这个问题也可以在store里的其他有类似这种效果的app中发现。

一般我们使用的图片有两种主要格式,jpeg和png。Apple通常推荐你使用png作为用户界面的图片格式,这些图片会被一个叫pngcrush开源的工具优化(译者注:这个工具就在/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/pngcrush ),这样对于iOS设备就可以在显示时更快地进行解压和渲染。iPad平台上首批出现的杂志应用,比如Wired,就曾用过png作为杂志内容图片的格式,这导致了这个应用的某一版本大小超过了500MB(link)[http://www.cocoanetics.com/2010/05/saturday-morning-breakfast-wired-emag/]

虽然png格式的图片会被事先优化好,但是这并不意味着在所有情况下png都是最佳的图片格式,png对于那些app中自带的图片来说非常好,但是对于要从internet上down下来的图片来说又会怎样呢。png和jpeg这两种格式都有各自的优缺点:

png格式的图片有alpha通道,jpeg则没有。png无损压缩,jpeg允许你选择0-100%的压缩质量。如果需要alpha通道(透明),就只能用png格式。但是如果你不需要一个完美的图片,就可以使用jpeg格式,jpeg格式会忽略那些你看不到的信息,对于大部分的图片可以使用60-70%的压缩质量而不对图片造成明显的影响,对于比如文字那样有"sharp pixels"的图片就可能需要较高的压缩质量,对于照片可以使用较低的压缩质量。

来看一下一个图片的空间消耗:

  1. 磁盘空间或者通过internet传输所消耗的空间
  2. 解压缩空间,通常是长X宽X高X4字节(RGBA)
  3. 当显示在一个view中时,view本身也需要空间来存储layer

对于这里的第一个问题,有一个可能的优化方法:将压缩的文件拷贝到内存中不如映射到内存中,NSData有能力来假设一块磁盘空间是在内存中的,这样当访问这个图片时实际上就是从磁盘访问而不是从内存。据说CGImage知道哪种访问方式是最高效的,UIImage只是将CGImage封装了一下。

对于“将这些像素显示到屏幕上最快要多久?”这个问题,显示一个图片所消耗的时间由以下三个因素决定:

  1. 从磁盘上alloc/init UIImage的时间
  2. 解压缩的时间
  3. 将解压缩后的比特转换成CGContext的时间,通常需要改变尺寸,混合,抗锯齿工作。

要逐一解答各个问题,我们需要一个benchmark来测量。

测试环境和测试内容

继续阅读[iOS]如何避免图像解压缩的时间开销

编译你自己的MobileTerminal

先扯点题外话,没兴趣看的直接跳过这一段:话说iOS 5.0.1完美越狱终于出来了,虽然还有一些Bug(比如ibooks),以及一些4.x时代的越狱软件不能用,但是终归是出来了,最近还有weiphone的好心人分享4s的siri认证,就是那个Spire一直下不下来很蛋疼。再吐槽下Sbsettings,虽然放到通知栏里了,但是也太影响加载速度了吧,你就不能在viewdidappear里面加载那几个图标么(也许通知栏的控件没有这个方法,但是那个天气股票啥的都挺流畅的啊),搞得通知栏没有一回是流畅拉下来的。依赖的Activator显然已经没什么用了,就应该老实儿做个通知栏版的Sbsettings,也不能费多少劲。

吐完槽进入正题:MobileTerminal是一个写的很蛋疼的app,为什么很蛋疼,原因有很多:

  • 删除(backspace/delete)操作总是有历史字母残留,这个不知道为什么,对终端的实现不是很了解
  • 左下角那个快捷菜单不能添加ESC这种常用的快捷键
  • swipe left right up down 的手势识别率太低,就不应该用这种不好识别的手势,本来终端就是个没什么触摸交互的东西,手势操作看不到结果。
  • 等等…

这里先说下环境:Xcode 4.2,iOS SDK 5.0, 有开发者帐号证书

幸好编译MobileTerminal还算简单,编译脚本该有的都有了,不用去操心这些实在是很方便,大概步骤如下:

  • 找个目录 svn checkout http://mobileterminal.googlecode.com/svn/ mobileterminal-read-only 这个目录路径最好全英文,无空格,否则编译deb时有问题
  • check下来的东西有好几个project,真正有用的在branches/applesdk/MobileTerminal 打开这个project
  • Target选择成MobileTerminal,编译到真机
  • 编译遇到错误 Command ./build_svnversion.sh failed with exit code 1 问题,这个svnversion似乎是用来生成版本号的,我们也不需要就可以无视。打开项目配置文件,选择Targets下面的MobileTerminal,BuildPhases >> Target Dependencies里面删除svnversion(MobileTerminal)
  • 继续编译,多了一个错误:TerminalGroupView.m 文件里#import部分,找不到 Perferences/Settings.h;直接改成#import "Settings.h"
  • 继续编译,可能有签名问题,对于有开发者帐号的就直接设置好签名就OK了,没有帐号的如果之前已经做好XCode免证书调试的各种工作了这个错误应该不会出现,具体没有条件尝试,大家自行解决吧,不是大问题。
  • 继续编译,第一次遇到的错误没有了,现在在 MobileTerminalAppDelegate.m 文件里提示找不到 svnversion.h;删除这句import,修改applicationDidFinishLaunching:里面的SVN_VEFRSION为任意数字,比如999
  • 过程中还可能遇到找不到gen_entitlements.py的问题,这个估计是XCode3.x版本免证书调试遗留下来的东西(参考这里),解决方法,终端中输入如下命令(要sudo):

    mkdir /Developer/iphoneentitlements312
    cd /Developer/iphoneentitlements312
    curl -O http://www.alexwhittemore.com/iphone/gen_entitlements.txt
    mv gen_entitlements.txt gen_entitlements.py
    chmod 777 gen_entitlements.py

经过以上修改,编译就应该成功,在你的机器上就应该会出现沙盒版的MobileTerminal,运行会提示在沙盒里不能fork,这就需要将App安装到/Application/下,直接将编译好的terminal.app拖过去运行会秒退,需要编译deb包,像Cydia那样安装。

Target选择deb,编译,可能会提示缺少dpkg-deb;直接下载这个二进制文件dpkg-deb-fat(参考这里),放到/usr/local/bin/下面(或者/usr/bin/),重命名为dpkg-deb,在终端直接敲dpkg-deb命令检查以下,接着编译成功,编译好的deb在~/Library/Developer/Xcode/DerivedData/MobileTerminal-xxxxxxxxxx/Build/Products/Debug-iphoneos/ 下面,这个路径可以通过在项目Products文件夹右键点击已经编译好的Terminal.app >> Show in Finder来找到

至于Deb包的安装我就不用多说了,iFile傻瓜式还是命令行dpkg,再或者蛋疼地架个repo,方法很多了。

至于Terminal的修改我还没有研究,最近也比较忙没那闲工夫折腾,有兴趣的自己看看吧,牛人可以挑几个bug啥的上传到code上以后也可以作为炫耀的资本(开完笑),以后Cydia上的那个版本就可能时你编译的了。

PS.其实MobleTerminal可以直接在模拟器上运行,没有沙盒问题,证书问题,也方便调试

最后感谢一下为MobileTerminal作出贡献的我不认识的大牛们,虽然我之前吐了很多槽,幸好你们看不懂中文。

—以上—

[WWDC][AppFramework]Session 121 Understanding UIKit Rendering

Session 121

Understanding UIKit Rendering


个人感觉是WWDC 2011里比较精品的Session,强烈建议各位同学反复看几遍视频,可以了解到许多iOS的实现机制

提要
  • UIView and CALayer
  • CATransaction and when views get rendered
  • Quality and Performance

     

    • Clipping and masking
    • Edge anti-aliasing
    • Group opacity
    • Shadows

UIView and CALayer

Geometry

iPhone 4
UIKit 左上为原点,320 x 480, 横屏原点不变 CoreAnimation 左上为原点,640 x 960

iPad 2
UIKit 左上原点 768 x 1024 CoreAnimation 左下为原点 1024 x 768

关于坐标问题直接参看官方文档就可以了

Drawing
  • view.layer.contents
  • UIImageView
  • drawRect

参考Practical Drawing for iOS Developers

UIImageView 与 drawRect 对比

放大一个图片,drawRect会占用更多内存,UIImageView只占原始图片所占内存,没有额外开销 用一个基础图片(tile)填充一个更大的区域,drawRect同样占用更多内存,UIImageView会在性能与最小内存占用之间选择一个平衡点(将多个tile拼成一个大一些的tile)

CATransaction

转换在runloop结束时生效

Performance
Avoid Offscreen Rendering

“正常"的渲染都直接在当前可视区域进行 屏幕外渲染,让显卡指向一个屏幕外的内存,alloc那段内存并开始绘图,渲染结束后,显卡指向主屏幕,使用之前在屏幕外渲染来在屏幕内绘图,这个过程需要消耗额外的内存,显卡从一个buffer切换到另一个buffer也需要消耗额外的时间,还需要刷新缓冲区,所以在屏幕内的和屏幕外的buffer之间切换代价是很昂贵的。 由于CoreAnimation是逐帧渲染的,所以屏幕外的渲染将会带来极大的开销, 每次屏幕刷新都会有屏幕外的渲染发生,比如一个scrollView,每次scroll的时候,都会在屏幕内进行一些渲染,屏幕外进行一些渲染,然后又将屏幕外的绘制到屏幕内。

Layer Rasterization

这个算是offscreen rendering的一种解决方法吧,屏幕外的渲染会被缓存起来,这样就不用每次都重新绘制一下屏幕外的buffer. ([CALayer setShouldRasterize:])
使用这个特性要保证你的内容是固定不变的,因为如果是个变化的内容,那么每一帧的内容都会与前一帧不同,缓存不能被重复利用,反而影响了性能。


比如要实现图中所示效果
CuriousFrog

Clipping and Masking

这里有两个技术点,一是圆角的实现,我们可以直接用CALayer的cornerRadius来达到这个效果,虽然很方,但是效率很低。二是倒影的实现,实现倒影的方法是首先取得上半部分原始View hierarchy的一个副本,y轴翻转绘制在下面,然后根据剃度来设置mask,但是这个mask开销是很大的,一方面要对整个View hierachy进行操作,另一方面需要进行Offscreen Render。

下面的方法效率都比较低,虽然很方便 * [CALayer cornerRadius] * [CALayer mask] * [UIView clipsToBounds] 或 [CALayer masksToBounds]

一些Trick * [CALayer contentsRect] 设置需要渲染的范围,本例中并不适用。 * [UIView drawRect:] 预先渲染好来防止每一帧都进行渲染 * Transparent overlay 比如圆角就可以通过覆盖黑色来让它看起来是圆的,但是本例中还要看到圆角后面的内容,所以不采用

Group Opacity

设置Group Opacity和没设置的区别

GroupOpacity

可以通过一下几个方法实现: * 在Info.plist里设置UIViewGroupOpacity键值 这样就又会产生offscreen rendering * 在drawRect:里预先渲染 * 设置shouldRasterize为YES

Shadowed text

使用CALayer的shadow属性很方便,但是开销也很昂贵

Demo

首先给出检测offscreen rendering的方法:使用Instruments的Core Animation模板,选中Color Offscreen-Rendered Yellow就可以让所有的Offscreen rendering都染成黄色

首先使用

UIBezierPath *capsulePath = [UIBezierPath bezierPathWithRoundedRect:myBounds cornerRadius:myBounds.size.height / 2.0];
[[UIColor grayColor] set];
[capsulePath fill];

来绘制圆角的背景,免去setCornerRadius的开销。

接着使用

CGContextSetShadow(context, CGSizeMake(3.0f, 3.0f), 10.0f);
CGContextSetShadowWithColor(context, CGSizeMake(3.0f, 3.0f), 10.0f, [UIColor colorWithWhite:0 alpha:.4].CGColor);

[[UIColor blackColor] set];
[_labelTitle drawInRect:titleFrame withFont:[UIFont boldSystemFontOfSize:28]];

来代替setShadowOffset: 和 setShadowColor:,这里注意一点,CoreGraphics在你执行绘制的代码后立即就会进行绘制,所以要在绘制之前先把属性设置好;而Core Animation在Transaction commit之后才开始渲染,所以属性可以在之后设置。

倒影的绘制,预先绘制成一个图片来防止逐帧渲染

if (_isReflected) {
    // create mask image

    UIGraphicsBeginImageContext([self bounds].size);
    CGColorSpaceRef deviceGray = CGColorSpaceCreateDeviceGray();
    CGFloat locations[2] = {1.0, 0.0);
    NSArray *colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithWhite:0.0 alpha:0.0] CGColor], (id)[[UIColor colorWithWhite:0..0 alpha:0.5] CGColor], nil];
    CGGradientRef gradient = CGGradientCreateWithColors(deviceGray, (CFArrayRef)colors, locations);

    CGContextDrawLinearGradient(UIGraphicsGetCurrentContext(), gradient, CGPointMake(0, 0), CGPointMake(0, [self bounds].size.height), 0);
    CGGradientRelease(gradient);
    CGColorSpaceRelease(deviceGray);
    CGImageRef maskImage = [UIGraphicsGetImageFromCurrentImageContext() CGImage];
    UIGraphicsEndImageContext();

    CGContextClipToMask(context, myBounds, maskImage);
}
Edge Antialiasing

抗锯齿的实现有下面两个方法

  • Info.plist里设置UIViewEdgeAntialiasing键值 开销很大
  • 用一个像素的边来模拟

代码如下:

- (UIImageView *)createImageViewForImage:(UIImage *)image
{
    UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    [imageView setContentMode:UIViewContentModeScaleAspectFit];

    //Add a shadow around the image.
    [[imageView layer] setShadowOffset:CGSizeMake(10, 10)];
    [[imageView layer] setShadowColor:[[UIColor blackColor] CGColor]];
    [[imageView layer] setShadowOpacity:.5];

    //Rotate the image by some small random angle.
    [imageView setTransform:CGAffineTransformMakeRotation((((float)random() / RAND_MAX) * MAX_ROTATION) - MAX_ROTATION / 2)];

    //Rather than using Core Animation's edge antialiasing, which requires an off-screen rendering pass, we'll draw our image int own image with an empty 1px border on each side. That way, when it's rotated, the edges will appear smooth because the outermost pixels of the original image will be sampled with the clear pixels in the outer border we add. This sampling is much faster but not as high-quality.

    CGSize imageSizeWithBorder = CGSizeMake([image size].width + 2, [image size].height + 2);
    UIGraphicsBeginImageContext(imageSizeWithBorder);
    // The image starts off filled with clear pixels, so we don't need to explicitly fill them here.
    [image drawInRect:(CGRect){{1,1}, [image size]}];
    [imageView setImage:UIGraphicsGetImageFromCurrentImageContext()];
    UIGraphicsEndImageContext();

    // We no longer need CA's edge antialiasing on this layer
    [[imageView layer] setEdgeAntialiasingMask:0];

    return imageView;
}

阴影绘制:

- (void)layoutImageView:(UIImageView *)imageView inFrame:(CGRect)celFrame withAnimationDuration:(NSTimeInterval)duration
{
    // The image view may be rotated, so set its frame in terms of the identity transform and then reapply the transform.
    CGAffineTransform imageViewTransform = [image View transform];
    [imageView setTransform:CGAffineTransformIdentity];
    [imageView setFrame:CenteredRectInRect(CGRectMake(0, 0, [imageView image].size.width, [imageView image].size.height), celFrame)];
    [imageView setTransform:imageViewTransform];

    // Save Core Animation a pass to figure out where the transparent pixels are by informing it explicitly of the contents's shape.
    CGPathRef oldPath = CGPathRetain([[imageView layer] shadowPath]);
    [[imageView layer] setShadowPath:[[UIBezierPath bezierPathWithRect:[imageView bounds]] CGPath]];

    // Since the layer's delegate (its UIView) will not create an action for this change (via the CALayerDelegate method actionForLayer:forKey:), we must explicitly create the animation between these values.
    CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"shadowPath"];
    [pathAnimation setFromValue:(id)oldPath];
    [pathAnimation setToValue:(id)[[imageView layer] shadowPath]];
    [pathAnimation setDuration:duration];
    [pathAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
    [pathAnimation setRemovedOnCompletion:YES];

    [[imageView layer] addAnimation:pathAnimation forKey:@"shadowPath"];
    CGPathRelease(oldPath);
}

以上就是这个session中个人觉得比较精髓的部分了,一些地方用中文表达的也不是太好,代码都是手打的,难免有错误,如有问题还请留言指正 对这篇文章涉及的内容感兴趣的同学也看看这篇文章(在墙外,如有需要我可以帮忙做个pdf传上来)

—以上—

Mac下用Python链接MySQL数据库方法简介

先说下环境:Mac OS X Lion,自带的Python 2.7.1,MySQL 5.5.18

Python自带了不需要安装,MySQL安装直接到官网下载dmg就可以了,没什么难度。

至于Python与MySQL的连接,就是使用MySQL-python,目前版本1.2.3,最近一次更新在2010-07-22,下载地址

下载后解压,到解压后的文件夹内看下README就知道如何安装了,简单两步

  • python setup.py build
  • sudo python setup.py install

可是在Mac下只执行这两步回在import MySQLdb时报错(Library not loaded: libmysqlclient.18.dylib),原因就是libmysqlclient.18.dylib这个库实际上在/usr/local/mysql/lib/下,而默认是在/usr/lib/下找,自然就找不到这个库,所以简单创建个link过去就解决了:

  • ln -s /usr/local/mysql/lib/libmysqlclient.18.dylib /usr/lib/libmysqlclient.18.dylib

现在import的时候就不会出错了,至于使用方法参考这里的例子官方手册就差不多了。

参考
  1. http://pypi.python.org/pypi/MySQL-python/1.2.3
  2. http://stackoverflow.com/questions/6383310/python-mysqldb-library-not-loaded-libmysqlclient-18-dylib

XCode 4.2 地点模拟技巧

XCode 4.2终于支持地点模拟了,不用忍受真机调试的各种不便了,模拟方法也很简单(恕我盗用几个别人的图):

当Debug一个需要地理位置信息的App时,在Debug栏默认就会有地点模拟的图标,如下图所示:

Xcode simulator location services3

这个有一个前提:必须是iOS5的模拟器.

默认只提供了几个地点,但是可以通过GPX文件来添加.你可以选择到网上找现成的GPX,但是Apple还是很为广大开发者考虑的,提供了GPX的模版,创建方法 新建>>Resource>>GPX File 如下图:

Xcode simulator location services2

其实所谓的GPX就是一XML文件,默认的内容如下:

<?xml version="1.0"?>

<gpx version="1.1" creator="Xcode">

    <wpt lat="37.331705" lon="-122.030237">

         <name>Cupertino</name>

    </wpt>

</gpx>

只要改下经纬度,改下名字,就是你想要的地点了,经纬度可以用GoogleEarth获取.

添加了GPX文件后就可以在之前选择地点那里看到你的GPX文件了.

—以上—