iOS截屏和控件截取

Author Avatar
xiaoLit Created: Nov 08, 2018 Updated: May 16, 2019

先上干货懒得看的直接跑Demo


iOS7之后,苹果开放出一个通知:UIApplicationUserDidTakeScreenshotNotification,截屏时系统就会发出这个通知,需要你注册这个通知,就能捕捉到截屏图片。

 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getScreenShot:) name:UIApplicationUserDidTakeScreenshotNotification object:nil];
/**
 通知回调
 */
- (void)getScreenShot:(NSNotification *)notification{    
    //获取屏幕的截图
    UIImage *image = [UIView lit_screenShotImage];
    //展示图片
    [self showScreenShotImage:image];}

获取截图的方法有很多

方法一、 用于全屏截图最稳妥的方法目前iOS12.1也可以使用不过效率低些

/**
 获取截屏
 */
+ (UIImage *)lit_screenShotImage {
    /**
     创建一个基于位图的上下文(context),并将其设置为当前上下文(context)
     @param size 参数size为新创建的位图上下文的大小。它同时是由UIGraphicsGetImageFromCurrentImageContext函数返回的图形大小
     @param opaque 透明开关,如果图形完全不用透明,设置为YES以优化位图的存储,我们得到的图片背景将会是黑色,使用NO,表示透明,图片背景色正常
     @param scale 缩放因子 iPhone 4是2.0,其他是1.0。虽然这里可以用[UIScreen mainScreen].scale来获取,但实际上设为0后,系统就会自动设置正确的比例了
     */
    UIGraphicsBeginImageContextWithOptions([UIScreen mainScreen].bounds.size, NO, 0);

    //获取当前上下文
    CGContextRef context = UIGraphicsGetCurrentContext();

    //遍历所有窗口 用于完善处理一些多层windows显示问题
    for (UIWindow *window in [[UIApplication sharedApplication] windows]) {

        if ([window respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
            [window drawViewHierarchyInRect:window.bounds afterScreenUpdates:YES];
        } else {
            //layer是不能够直接绘制的.要用渲染的方法才能够让它绘制到上下文当中。
            [window.layer renderInContext:context];
        }
    }
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return image;
}

注意:iOS 7上UIView上提供了drawViewHierarchyInRect:afterScreenUpdates:来截图,速度比renderInContext:快15倍

配合剪裁图片我们可以获得更加合适截图区域

/**
 剪裁图片 
 */
- (UIImage *)lit_clipRect:(CGRect)rect {

    //将view的转换成图片
    UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, 0.0);

    CGContextRef context =UIGraphicsGetCurrentContext();

    if ([self respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
        [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
    } else {
        [self.layer renderInContext:context];
    }
    UIImage *targetImage =UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();
    //对这个图片进行裁剪。
    CGImageRef imageRef = targetImage.CGImage;
    //计算截图区域时需要按比例来
    CGFloat scale = [UIScreen mainScreen].scale;
    //这里可以设置想要截图的区域
    CGRect tempRect =  CGRectMake(rect.origin.x * scale, rect.origin.y * scale, rect.size.width * scale, rect.size.height * scale);
    //是C的函数,使用CGRect的坐标都是像素
    CGImageRef imageRefRect = CGImageCreateWithImageInRect(imageRef, tempRect);

    UIImage *clipImage =[[UIImage alloc]initWithCGImage:imageRefRect scale:scale orientation:(UIImageOrientationUp)];
    return clipImage;
}

方法二、 适用于单个控件截图

/**
 截图控件
 */
- (UIView *)customSnapshotFromView:(UIView *)inputView {
    UIGraphicsBeginImageContextWithOptions(inputView.frame.size, NO, 0.0);

    //把控制器View的内容绘制到上下文当中.
    CGContextRef context =UIGraphicsGetCurrentContext();

    //layer是不能够直接绘制的.要用渲染的方法才能够让它绘制到上下文当中。UIGraphicsGetCurrentContext()
    [inputView.layer renderInContext:context];

    //从上下文当中生成一张图片
    UIImage*targetImage =UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    UIImageView*imageView = [[UIImageView alloc]initWithImage:targetImage];

    imageView.frame= inputView.frame;

    return imageView;
}

方法三、 iOS7就支持非常便捷的截图方法snapshotViewAfterScreenUpdates

- (UIView *)customSnapshotFromView:(UIView *)inputView {
    //afterUpdates参数表示是否在所有效果应用在视图上了以后再获取快照。
    //例如,如果该参数为NO,则立马获取该视图现在状态的快照,反之,以下代码只能得到一个空白快照:
    UIView *snapshot = [inputView snapshotViewAfterScreenUpdates:YES];

    snapshot.layer.masksToBounds = YES;

    return snapshot;
}

但是据说iOS10之后就不好使了,我用的iOS12测试发现和iOS9模拟器都无法使用,所以现在使用的话此方法有待考察。


有可能有特殊的情况,会要求横屏等其他方向截屏变成正常竖屏样式展示,将截屏的图片进行仿射变换就可以了。Demo也有相关代码。