WWDC 2012 小结

低清晰度的keynote刚看完,高清1080p那个7G下了个开头就放弃了,作为一个iOS开发者简要谈下体会。

Retina MacBook Pro

之前就预测过Retina Mac几乎没有可能出现,理由就是市面上所有显卡包括台式机显卡,支持的单屏最高分辨率为2560×1600,也就是13寸MBP分辨率的Retina版,15寸的2880×1800想都不要想。可惜失算了,不知道什么时候nVidia的GTX 600M系列最高分辨率居然支持到了3840×2160,也就是Full HD的Retina版,更奇葩的是nVidia的台式机最强显卡最高分辨率依旧为2560×1600,因此不得不怀疑苹果跟nVidia在私下又合作了一次。另外最近又有新闻说iMac和Mac Pro要在明年有重大更新,在此也可以大胆预测一下,21.5寸iMac(1920×1080)上Retina从目前技术角度上来看是可能的,27寸(2560×1440)没戏,最终能否实现主要靠那两家做显卡的。

还有一点疑虑就是Reina MBP集成个Intel的显卡到底有什么用,分辨率的问题导致其不能驱动主屏幕,双屏或三屏的时候正常也是靠独显支持的,希望有条件的人去拿gfxCardStatus切换下试试,如果真的支持2880的分辨率,上面那一段就当我没说把。
如果Intel的显卡不能驱动主屏幕,那也可以看出Air和13寸MBP在以后的一两代内也不可能支持Retina,除非苹果给上独显,就算是Intel的下一代集成显卡支持高分辨率,性能上估计也很难达到要求。

Moutain Lion

Lion如其名可以算是我用过的最烂的操作系统,烂在慢,烂在各种Bug,跟SL比起来简直不在一个层面上,相信SL用户都能感受出来,都到了第四个版本了,虽然修复了很多问题,但是遗留的东西依旧不少。个人感觉,之所以没有太多人喷,是因为对于不少人来说Lion是他们用过的第一个Mac操作系统,另外Air的SSD也很大程度上掩盖了慢的缺点。综合Lion的悲剧表现我对于ML就比较期待,但以苹果最近的情况来看实在不赶抱太大希望,至于介绍的那几个新功能真正有很大用处的就是那个Power Nap了。 Game Center以但前iOS平台的游戏水准来说就是个笑话,那几个iOS游戏也就能在手机平台拿来耍耍,桌面系统玩这种级别的东西不感觉可悲么。至于未来能有什么发展就不是我等屁民可以预测的了。iCloud等附属的一系列软件还是不错的,对于用户来说只需设置个帐号,其他工作都是自动完成的,透明性非常好,但这些难道不应该在Lion中就附带么,由此可见Lion就是一个纯粹的过渡产品。

iOS 6

从升级幅度上看,iOS 6也就是iOS 5.3-5.4这个级别的,估计Bug不能多,所以很放心地升级了,主要功能新闻里都提了,我就主要谈一下他们没涉及的部分吧。

系统的整体UI有一定变化,Navigation Bar和Tool Bar的渐变方式有了改变,不算好看也不算难看,换个样子总还是有些新意的。

系统自带软件比如两个商店UI大幅改变,更美观了,自带的TODO也多了一些内容,值得一提的是Music/iPod,UI向iPad版靠拢,Cover Flow流畅了许多。

新地图对于中国区来说简直是个悲剧,AutoNavi的数据从各方面都不及Google Map,而且没有国外数据,卫星模式下全世界只有一个国家以及一个大大的红五星边上写着帝都。想用TomTom的数据对于iPhone来说也很麻烦,关键就是要开启飞行模式防止基站定位,另外一个国外的ip可能也是必要的,如果还是不行就用XCode模拟个米国地址调试一下。3D地图没条件测试,从keynote里的演示看还是很赞的,

Safai的分享按钮点击后弹出来一堆图标我就忍了,“添加到阅读列表”这个选项居然到了第二页这不是有病么?

设置>>开发者 这里多了一些内容,其中Network Link Conditioner真是相见恨晚啊,使用这个功能就可以模拟多种网络情况比如100%丢包等等,前一阵子还考虑要不要写一个这方面的程序来方便调试,iOS 6就实现了。

总结:Mac和iOS融合得好了,苹果的生态系统也更加完善了。Google和微软未来的道路又会如何呢?Google的眼镜能带来怎样的变革呢?至于iOS 6,Moutain Lion到底能带来多大变化还要看WWDC的Session才知道。

ps. 一开头Siri同学将用那蛋疼的发音讲了几个冷笑话,我是多么希望能换成GLaDOS啊!

–以上–

[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传上来)

—以上—

[WWDC 2011 Summary & Practice] [AppFramwork] Session 100

What’s New in Cocoa Touch


WWDC 2011的视频真心不错,身为iOS开发者必看,但是光看也记不住什么,就发个博文一方面当做笔记,一方面与懒得看视频的人分享一下其中的部分内容.

虽然很久以前就把Session 100看完了,但还是决定先用这个练练手,内容我尽量覆盖全,并且提供一些我测试的例子,也会贴些图.写过博客的都知道贴图是很繁琐的,所以请原谅的我的懒惰,我真的没有那么充裕的时间去截很多图.

WWDC 2011视频以及 Keynote(PDF格式)下载详见这里http://developer.apple.com/videos/(需要注册个开发者账号,但是不是交$99那种,“野生"开发者即可)


UIStepper

UIStepper

一个新控件,附带很少的属性,和UIButton用法差不多:

UIStepper *stepper = [[UIStepper alloc] initWithFrame:CGRectMake(44, 44, 100, 44)];
[self.view addSubview:stepper];
[stepper addTarget:self action:@selector(stepperTouched:) forControlEvents:UIControlEventTouchUpInside];
 
- (void)stepperTouched:(id)sender {
NSLog(@"stepper touched");
}

UIAlertView

终于可以在AlerView里输入文字了,真是佩服iOS5之前那些牛人怎么想到AlertView的各种使用技巧的.

typedef enum {
      UIAlertViewStyleDefault = 0,
      UIAlertViewStyleSecureTextInput,
      UIAlertViewStylePlainTextInput,
      UIAlertViewStyleLoginAndPasswordInput
  } UIAlertViewStyle;
@property(nonatomic,assign) UIAlertViewStyle alertViewStyle;
- (UITextField *)textFieldAtIndex:(NSInteger)textFieldIndex

UIScreenOverscanCompensation

跟外接屏幕有关,本人目前为止还没遇到使用这个功能的情景,不多评论

typedef enum {
     UIScreenOverscanCompensationScale,
     UIScreenOverscanCompensationInsetBounds,
     UIScreenOverscanCompensationInsetApplicationFrame
 } UIScreenOverscanCompensation;
 @property(nonatomic) UIScreenOverscanCompensation
 overscanCompensation;

UIScreen

可以像iOS一样调节亮度了,两个新属性:

@property (nonatomic) CGFloat brightness;
@property (nonatomic) BOOL wantsSoftwareDimming;

结合之前的UIStepper使用:

UIStepper *stepper = [[UIStepper alloc] initWithFrame:CGRectMake(44, 44, 100, 44)];
[stepper setMaximumValue:2.0];
[stepper setMinimumValue:-2.0];
[stepper setStepValue:0.1];
[self.view addSubview:stepper];
[stepper addTarget:self action:@selector(stepperTouched:) forControlEvents:UIControlEventTouchUpInside];
[[UIScreen mainScreen] setWantsSoftwareDimming:YES];
 
- (void)stepperTouched:(id)sender {
    NSLog(@"stepper touched with value %f", [(UIStepper *)sender value]);
    [[UIScreen mainScreen] setBrightness:[(UIStepper *)sender value]];
}

brightness范围在0.0-1.0,wantsSoftwareDimming是使用软件模拟来获得更低的亮度,但是开启会损失性能,本人实测似乎没有效果,或许是方法不对.鉴于不太实用,暂且搁置.

 

继续阅读[WWDC 2011 Summary & Practice] [AppFramwork] Session 100