天天品尝iOS7甜点 :: Day 7 :: Taking Snapshots of UIViews

这篇文章是天天品尝iOS7甜点系列的一部分,你可以查看完整的系列目录:天天品尝iOS7甜点


Introduction - 介绍

本章的实例程序能够在github上面进行访问,访问地址:github.com/ShinobiControls/iOS7-day-by-day

有一些情况你需要得到UIView对象的快照,为了提高分享快照应用程序的性能。已经存在的方法目前遭遇一下一些问题:

  • 代码并不是十分的简单
  • 复杂的渲染选项,例如层就很难复制
  • OpenGL代码层需要特别的代码
  • 制作快照的过程详单缓慢

事实上,并没有制作快照的通用代码可供复制使用。

但是在iOS7上面,一切变得如此简单,在UIViewUIScreen中的新方法可能很简单的实现多种效果的快照功能。

Snapshotting for Animation - 动画快照

我们经常要需要一个动画视图,但是这样做很复杂,并且需要编写很多的代码来进行行为的控制。

本篇我们使用的demo创建一个UIView子类,它由一系列的子view构成。每一个都进行旋转来展现一个优美的几何图形。

需要生成上述情况,我们需要需要下面的代码结构:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)generateRotations {
for (CGFloat angle = 0; angle < 2 * M_PI; angle += M_PI / 20.0) {
UIView *newView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 250)];
newView.center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
newView.layer.borderColor = [UIColor grayColor].CGColor;
newView.layer.borderWidth = 1;
newView.backgroundColor = [UIColor colorWithWhite:0.8 alpha:0.4];
newView.transform = CGAffineTransformMakeRotation(angle);
newView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
[self addSubview:newView];
}
}

在创建这个视图的时候,并不是最好的方式来创建这个效果。尽管这样也是有效的。但是它们都展示在同一个点上面。

在view controller中,我们将会创建一些有用的工具方法,在项目中它会被反复的运用。第一个创建的方法是旋转视图,并且添加成一个子视图:

  • (void)createComplexView{_complexView = [[SCRotatingViews alloc] initWithFrame:self.view.bounds];[self.containerView addSubview:_conplexView];}

第二个简单的动画方法,可以模拟一个视图,压缩它的大小为{0, 0}:

  • (void)animateViewAwayAndReset:(UIView *)view {[UIView animateWithDuration:2.0 animations:^{view.bounds = CGRectZero;} completion:^(BOOL finished){[view removeFromSuperview:]; [self performSelector:@selector(createComplexView) withObject:nil afterDelay:1];}];};

当动画运行完成之后就会删除提供的视图,并且延迟创建一个新的_complexView来重置app

工具栏中的按钮Animate实现的方法如下:

1
2
3
- (IBAction)handleAnimate:(id)sender {
[self animateViewAwayAndReset:_complexView];
}

下图就展示了我们创建的旋转视图时候的问题:

这个问题肯定是需要修改的,所以我们将会修改SCRotatingViews的结构.

一个新的快照的方法由此产生,通过添加工具拦中的SShot按钮调用下面的方法:

1
2
3
4
5
6
- (IBAction)handleSnapshot:(id)sender {
UIView *snapshotView = [_complexView snapshotViewAfterScreenUpdates:NO];
[self.containerView addSubview:snapshotView];
[_complexView removeFromSuperview];
[self animateViewAwayAndReset:snapshotView];
}

我们调用snapshotViewAfterScreenUpdates:来创建一个复合视图的快照。这返回的UIView代表了调用视图的整体外观。由于我们可以看出来创建一个快照是如此的简单,令人振奋,而且比以前的旧方法(需要生成一个位图)要快得多。

一旦我们获得了快照之后,我们就可以把它添加到容器中,并且删除存在的复合视图,然后我们就可以让快照视图实现动画效果:

Pre/post View Updates

方法snapshotViewAfterScreenUpdates:有一个BOOL类型的入参,它是指定是否立即生成快照,还是在需要视图更新的时候生成。

在实例中,我们需要在SCRotatingViews中添加下面的方法:

1
2
3
4
5
- (void)recolorSubviews:(UIColor *)newColor {
for (UIView *subview in self.subviews) {
subview.backgroundColor = newColor;
}
}

这个方法调用时候就是重新设置所有子视图的背景色。

为了验证上述BOOL类型入参形成的效果,我们创建两个方法,对应到工具栏中的Pre按钮和Post按钮。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (IBAction)handlePreUpdateSnapshot:(id)sender {
// Change the views
[_complexView recolorSubViews:[[UIColor redColor] colorWithAlphaComponent:0.3]];
// Take a snapshot, Don't wait for changes to be applied
UIView *snapshotView = [_complexView snapshotViewAfterScreenUpdates:NO];
[self.containerView addSubview:snapshotView];
[_complexView removeFromSuperview];
[self animateViewAwayAndReset:snapshotView];
}

- (IBAction)handlePostUpdateSnapshot:(id)sender {
// Change the views
[_complexView recolorSubViews:[[UIColor redColor] colorWithAlphaComponent:0.3]];
// Take a snapshot, Don't wait for changes to be applied
UIView *snapshotView = [_complexView snapshotViewAfterScreenUpdates:YES];
[self.containerView addSubview:snapshotView];
[_complexView removeFromSuperview];
[self animateViewAwayAndReset:snapshotView];
}

上面两个方法除了snapshotViewAfterUpdates:方法的入参不一样,其他都是相同的。首先我们调用recolorSubviews:方法,然后执行相同的生成快照的程序。下面是展示两种方法生成快照不同的效果图:

正如预期,设置NO将会立即生成快照,并且没有包含调用重新设置颜色的方法。设置YES会等到当前队列中所有的方法运行完成之后才会进行生成快照。

Snapshotting to an image - 生成快照图片

在进行上述动画的时候,我们实际上是把快照生成为一个UIView,然而有些时候生成一个真实的图片是很有帮助的。例如,我们希望在进行动画之前,让当前的视图模糊化。有另外一个方法可能实现这样的意图drawViewHierarchyInRect:afterScreenUpdates:.这样可以允许你用一个核心的图形上下文进行绘制视图,所以你可以得到当前视图的位图。
很显然,这个方法比snapshotViewAfterScreenUpdates:效率低,但是如果你需要一个位图,那这是最好的方法了。

我们绑定工具栏的Image按钮,来实现下面的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (IBAction)handleImageSnapshot:(id)sender {
// Want to create an image context - the size of complex view and the scale of the device screen
UIGraphicsBeginImageContextWithOptions(_complexView.bounds.size, NO, 0.0);
// Render our snapshot into the image context
[_complexView drawViewHierarchyInRect:_complexView.bounds afterScreenUpdates:NO];

// Grab the image from the context
UIImage *complexViewImage = UIGraphicsGetImageFromCurrentImageContext();
// Finish using the context
UIGraphicsEndImageContext();

UIImageView *iv = [[UIImageView alloc] initWithImage:[self applyBlurToImage:complexViewImage]];
iv.center = _complexView.center;
[self.containerView addSubview:iv];
[_complexView removeFromSuperview];
// Let's wait a bit before we animate away
[self performSelector:@selector(animateViewAwayAndReset:) withObject:iv afterDelay:1.0];
}

我们首先创建一个图像上下文,为_complexView正确的大小和缩放比例,然后再次调用drawHierarchyInRect:afterScreenUpdates:方法,这个方法的第二个入参和上面介绍的是一样的效果。

然后我们将图像上下文放入UIImage中,然后将会把这个图像显示到UIImageView上面,然后和上述一样替换视图并且让它运行动画。为了说明我们为什么需要UIImage而不是UIView,我们创建一个方法来模糊化UIImage:

1
2
3
4
5
6
7
8
9
10
- (UIImage *)applyBlurToImage:(UIImage *)image {
CIContext *context = [CIContext contextWithOptions:nil];
CIImage *ci_image = [CIImage imageWithCGImage:image.CGImage];
CIFilter *filter = [CIFilter filterWithName:@"CIGaussianBlur"];
[filter setValue:ci_image forKey:kCIInputImageKey];
[filter setValue:@5 forKey:kCIInputRadiusKey];
CIImage *result = [filter valueForKey:kCIOutputImageKey];
CGImageRef cgImage = [context createCGImage:result fromRect:[result extent]];
return [UIImage imageWithCGImage:cgImage scale:image.scale orientation:image.imageOrientation];
}

这是一个简单的应用程序的CoreImage过滤器,应用高斯滤波器和返回一个新的UIImage。

下面就是我们刚刚创建的一个效果:

Limitaions - 局限性

以前生成快照需要调用OpenGL-backed的很复杂的方法。振奋的是新版本的UIView可以与OpenGL无缝的结合。

因为创建的快照必须是能够在手机屏幕上面看到的部分,如果超出了屏幕范围,生成的快照就会如下图所示:

Conclusion - 总结

在iOS快照UIView元素一直是非常有用的,和iOS7我们终于有了一个合理的API方法允许我们采取的通用的方式生成视图快照。这并不意味着没有限制,你仍然需要使用替代方法对于某些场景,但是这个方法可以解决90%的有关快照的问题。

本文翻译自:iOS7 Day-by-Day :: Day 7 :: Taking Snapshots of UIViews

文章目录
  1. 1. Introduction - 介绍
  2. 2. Snapshotting for Animation - 动画快照
  3. 3. Pre/post View Updates
  4. 4. Snapshotting to an image - 生成快照图片
  5. 5. Limitaions - 局限性
  6. 6. Conclusion - 总结
,