How to take screenshot of UIScrollView visible are

2020-06-09 06:47发布

问题:

How do I take a 1:1 screenshot of UIScrollView visible area? The content may be larger or smaller than UIScrollView bounds as well as half-hidden (I've implemented custom scrolling for smaller content, so it's not in the top-left corner). I've achieved desired result on simulator, but not on device itself:

-(UIImage *)imageFromCombinedContext:(UIView *)background {
      UIImage *image;
      CGRect vis = background.bounds;
      CGSize size = vis.size;
      UIGraphicsBeginImageContext(size);
      [background.layer affineTransform];
      [background.layer renderInontext:UIGraphicsGetCurrentContext()];
      image = UIGraphicsGetImageFromCurrentImageContext();
      UIGraphicsEndImageContext();
      CGImageRef imref = CGImageCreateWithImageInRect([image CGImage], vis);
      image = [UIImage imageWithCGImage:imref];
      CGImageRelease(imref);
      return image;
}

回答1:

Another approach would be to use the contentOffset to adjust the layer's visible area and capture only the currently visible area of UIScrollView.

UIScrollView *contentScrollView;....//scrollview instance

UIGraphicsBeginImageContextWithOptions(contentScrollView.bounds.size, 
                                       YES, 
                                       [UIScreen mainScreen].scale);

//this is the key
CGPoint offset=contentScrollView.contentOffset;
CGContextTranslateCTM(UIGraphicsGetCurrentContext(), -offset.x, -offset.y); 

[contentScrollView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *visibleScrollViewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

Cheers :)



回答2:

Swift version of Abduliam Rehmanius answer.

func screenshot() -> UIImage {

        UIGraphicsBeginImageContextWithOptions(self.scrollCrop.bounds.size, true, UIScreen.mainScreen().scale);
        //this is the key
        let offset:CGPoint = self.scrollCrop.contentOffset;
        CGContextTranslateCTM(UIGraphicsGetCurrentContext(), -offset.x, -offset.y);
        self.scrollCrop.layer.renderInContext(UIGraphicsGetCurrentContext()!);
        let visibleScrollViewImage: UIImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return visibleScrollViewImage;
    }

Swift 4 version:

func screenshot() -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.scrollCrop.bounds.size, false, UIScreen.main.scale)
        let offset = self.scrollCrop.contentOffset
        let thisContext = UIGraphicsGetCurrentContext()
        thisContext?.translateBy(x: -offset.x, y: -offset.y)
        self.scrollCrop.layer.render(in: thisContext!)
        let visibleScrollViewImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
return visibleScrollViewImage
}


回答3:

I've found a solution myself - I took screenshot of the whole view and then crop it to the size and position of UIScrollView frame.

-(UIImage *)imageFromCombinedContext:(UIView *)background 
{
      UIImage *image;
      CGSize size = self.view.frame.size;
      UIGraphicsBeginImageContext(size);
      [background.layer affineTransform];
      [self.view.layer.layer renderInContext:UIGraphicsGetCurrentContext()];
      image = UIGraphicsGetImageFromCurrentImageContext();
      UIGraphicsEndImageContext();
      CGImageRef imgRef = CGImageCreateWithImageInRect([image CGImage],background.frame);
      image = [UIImage imageWithCGImage:imref];
      CGImageRelease(imref);
      return image;
}


回答4:

Jeffery Sun has the right answer. Just put your scroll view inside another view. Get container view to render in context. done.

In the code below, cropView contains the scroll view to be captured. The solution is really just that simple.

As I understand the question and why I found this page, the whole content of the scroll view isn't wanted - just the visible portion.

func captureCrop() -> UIImage {
    UIGraphicsBeginImageContextWithOptions(self.cropView.frame.size, true, 0.0)
    self.cropView.layer.renderInContext(UIGraphicsGetCurrentContext())
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return image;
}


回答5:

Swift 4 version of Abduliam Rehmanius answer as UIScrollView extension with translation, no slow cropping

extension UIScrollView {

    var snapshotVisibleArea: UIImage? {
        UIGraphicsBeginImageContext(bounds.size)
        UIGraphicsGetCurrentContext()?.translateBy(x: -contentOffset.x, y: -contentOffset.y)
        layer.render(in: UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }
}


回答6:

@Abduliam Rehmanius's answer has poor performance, since if the UIScrollView contains a large content area, we will draw that entire content area (even outside the visible bounds). @Concuror's answer has the issue that it will also draw anything that is on top of the UIScrollView.

My solution was to put the UIScrollView inside a UIView called containerView with the same bounds and then render containerView:

containerView.renderInContext(context)


回答7:

Swift 3.0 :

 func captureScreen() -> UIImage? {
    UIGraphicsBeginImageContextWithOptions(self.yourScrollViewName.bounds.size, true, UIScreen.main.scale)
    let offset:CGPoint = self.yourScrollViewName.contentOffset;
    UIGraphicsGetCurrentContext()!.translateBy(x: -offset.x, y: -offset.y);
    self.yourScrollViewName.layer.render(in: UIGraphicsGetCurrentContext()!)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return image
}

and use it as : let Image = captureScreen()



回答8:

Update swift 3+, 4 on @Concuror code

func getImage(fromCombinedContext background: UIView) -> UIImage {
        var image: UIImage?
        let size: CGSize = view.frame.size
        UIGraphicsBeginImageContext(size)
        background.layer.affineTransform()
        view.layer.render(in: UIGraphicsGetCurrentContext()!)
        image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        let imgRef = image?.cgImage?.cropping(to: background.frame)
        image = UIImage(cgImage: imgRef!)
        // CGImageRelease(imgRef!) // Removing on Swift - 'CGImageRelease' is unavailable: Core Foundation objects are automatically memory managed
        return image ?? UIImage()
    }