UIScrollView之截长图

99 阅读2分钟

现在有个需求,需要截取UIScrollView中内容

主要有以下三种方案

  1. 基于UIScreenService 实现屏幕截图,系统要求iOS 13以上
  2. 使用UIGraphicsGetCurrentContext实现或者CoreGraphics 来实现截屏功能
  3. 分屏截取UIScrollView中的内容, 然后把多张图片拼接成一张图片

问题

    1. 传入view,然后生成PDF data, 系统版本有限制,如果要支持iOS 13一下的不适用
    1. 如果UIScrollView中内容太多,会占用太多的内存空间,导致系统崩溃
    1. 自研方案,内存占用小,没有系统限制

方案3介绍

    1. 保存UIScrollView中默认contentsize,contentoffset
    1. 校验UIScrollView中contentsize属性,如果是竖向滑动,重置height属性,为bounds中height属性的整数倍,为了UIScrollView能够按照设置的contentoffset来滚动
    1. 每次滚动屏幕的整数倍,直到超过最大的contentoffset,每次滚动后就获取当前区域的快照
    1. 将截取的多张图片,绘制成一张图片
    1. 重置contentsize,contentoffset属性

代码如下

- (UIImage * __nullable)netexcept_snapshotImageForScrollview{

    @autoreleasepool {

        CGPoint saveContentoffset = self.contentOffset;

        NSMutableArray *images = [NSMutableArray array];

        CGFloat realContentsizeHeight = self.contentSize.height;

        //调整contentsize为视图显示高度的整数倍,并保证scrollview按contentoffset设置的参数滚动

        CGFloat resizecontentsizeheight = [self adjustTheNumberA: realContentsizeHeight numberB: self.bounds.size.height];

        //重新设置contentsize

        self.contentSize = CGSizeMake(self.contentSize.width,  resizecontentsizeheight);

        //获取最大滚动区域

        CGFloat maxcontentoffset = resizecontentsizeheight - self.bounds.size.height;

        CGPoint coffset = CGPointZero;

        do{

            [self setContentOffset: coffset];

            CGFloat cropHeight = MIN(realContentsizeHeight-coffset.y, self.bounds.size.height);

            UIImage *screenshot = [self netexcept_screenshotForCroppingRect:CGRectMake(0, coffset.y, self.contentSize.width, cropHeight)];

            if(screenshot && !(screenshot.size.width <= 0 || screenshot.size.height <= 0 || isnan(screenshot.size.width) || isnan(screenshot.size.height))){
                [images addObject: screenshot];
            }
            coffset.y += self.bounds.size.height;
        }while (maxcontentoffset >= coffset.y);
        UIImage *image = nil;
        if(images.count > 0){
            image = [UIImage netexcept_verticalImageFromArray: images];
        }
        self.contentSize = CGSizeMake(self.contentSize.width, realContentsizeHeight);
        self.contentOffset = saveContentoffset;
        return image;
    }
}

//将numberA调整为numberB的整数倍,要求调整后不小于numberA

- (CGFloat)adjustTheNumberA: (CGFloat)numberA numberB: (CGFloat)numberB{
    //判断numberA能否被numberB整除,能整除就直接返回
    if(numberA/numberB - floor(numberA/numberB) < 0.0000001){
        return numberA;
    }
    //不能整除,就构造一个新的数
    CGFloat numberC = 0;
    while (numberC < numberA) {
        numberC += numberB;
    }
    return numberC;
}

# 截屏相关
- (UIImage *)netexcept_screenshotForCroppingRect:(CGRect)croppingRect

{
    return [self netexcept_screenshotForCroppingRect:croppingRect quantityScale:0.4 sizeScale:0.8];

}

- (UIImage *)netexcept_screenshotForCroppingRect:(CGRect)croppingRect quantityScale: (CGFloat)quantityScale sizeScale: (CGFloat)scale

{
    CGFloat sizeScale = quantityScale;
  UIGraphicsBeginImageContextWithOptions(CGSizeMake(croppingRect.size.width*sizeScale, croppingRect.size.height*sizeScale), NO, 0.0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextScaleCTM(context, sizeScale, sizeScale);
    if (context == NULL) return nil;
    CGContextTranslateCTM(context, -croppingRect.origin.x, -croppingRect.origin.y);
    [self layoutIfNeeded];
    [self.layer renderInContext:context];
    UIImage *screenshotImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    if(screenshotImage){
        NSData *imageData = UIImageJPEGRepresentation(screenshotImage, scale);
        screenshotImage = [UIImage imageWithData: imageData];
    }
    return screenshotImage;
}

# 合并图片相关
+ (UIImage *)netexcept_verticalImageFromArray:(NSArray *)imagesArray

{
    CGFloat sizeScale = 0.8;
    UIImage *unifiedImage = [self mergeImages: imagesArray width:sizeScale*UIScreen.mainScreen.bounds.size.width];
    if(unifiedImage){
        NSData *imageData = UIImageJPEGRepresentation(unifiedImage, 0.4);
        unifiedImage = [UIImage imageWithData: imageData];
    }
    return unifiedImage;
}

+ (UIImage *)mergeImages:(NSArray<UIImage *> *)images width:(CGFloat)width{
    @autoreleasepool {
        CGFloat totalHeight = 0;
        CGFloat maxHeightOfImages = 0;
        NSMutableArray<UIImage *> *resizedImages = [NSMutableArray arrayWithCapacity:images.count];
        for (UIImage *image in images) {
            CGFloat aspectRatio = image.size.width / image.size.height;
            CGFloat newHeight = width / aspectRatio;
            if (newHeight > maxHeightOfImages) {
                maxHeightOfImages = newHeight;
            }
            totalHeight += newHeight;
            UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, newHeight), NO, 0.0);
            [image drawInRect:CGRectMake(0, 0, width, newHeight)];
            UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
            [resizedImages addObject:newImage];
        }
        CGSize size = CGSizeMake(width, totalHeight);
        UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
        CGFloat y = 0;
        for (UIImage *image in resizedImages) {
            [image drawInRect:CGRectMake(0, y, width, image.size.height)];
            y += image.size.height;
        }
        UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return resultImage;
    };

}