这篇文章是天天品尝iOS7甜点系列的一部分,你可以查看完整的系列目录:天天品尝iOS7甜点
Introduction – 介绍
本章的实例程序能够在github上面进行访问,访问地址:github.com/ShinobiControls/iOS7-day-by-day
有一些情况你需要得到UIView
对象的快照,为了提高分享快照应用程序的性能。已经存在的方法目前遭遇一下一些问题:
- 代码并不是十分的简单
- 复杂的渲染选项,例如层就很难复制
- OpenGL代码层需要特别的代码
- 制作快照的过程详单缓慢
事实上,并没有制作快照的通用代码可供复制使用。
但是在iOS7上面,一切变得如此简单,在UIView
和UIScreen
中的新方法可能很简单的实现多种效果的快照功能。
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