Getting a screenshot of a UIScrollView, including

2019-01-01 00:28发布

I have a UIScrollView decendent that implements a takeScreenshot method that looks like this:

-(void)takeScreenshot {  
  CGRect contextRect  = CGRectMake(0, 0, 768, 1004);
  UIGraphicsBeginImageContext(contextRect.size);    
  [self.layer renderInContext:UIGraphicsGetCurrentContext()];
  UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
  UIGraphicsEndImageContext();

  // do something with the viewImage here.
}

This basically moves to the top of the scroll view, and takes a screenshot of the visible area. It works fine when the iPad is oriented portrait, but when it's in landscape the bottom of the image is cut off (as the height of the visible area is only 748, not 1004).

Is it possible to get a snapshot of the UIScrollView, including areas not on screen? Or do I need to scroll the view down, take a second photo and stitch them together?

10条回答
不流泪的眼
2楼-- · 2019-01-01 00:35

SWIFT 3 version:

func snapshot() -> UIImage?
{      
    UIGraphicsBeginImageContext(scrollView.contentSize)

    let savedContentOffset = scrollView.contentOffset
    let savedFrame = scrollView.frame

    scrollView.contentOffset = CGPoint.zero
    scrollView.frame = CGRect(x: 0, y: 0, width: scrollView.contentSize.width, height: scrollView.contentSize.height)

    scrollView.layer.render(in: UIGraphicsGetCurrentContext()!)
    let image = UIGraphicsGetImageFromCurrentImageContext()

    scrollView.contentOffset = savedContentOffset
    scrollView.frame = savedFrame

    UIGraphicsEndImageContext()

    return image
}

This worked for me

查看更多
宁负流年不负卿
3楼-- · 2019-01-01 00:36

SWIFT 3 version thanks to @gleb vodovozov:

func getImageOfScrollView()->UIImage{
    var image = UIImage();

    UIGraphicsBeginImageContextWithOptions(self.scrollView.contentSize, false, UIScreen.main.scale)

    // save initial values
    let savedContentOffset = self.scrollView.contentOffset;
    let savedFrame = self.scrollView.frame;
    let savedBackgroundColor = self.scrollView.backgroundColor

    // reset offset to top left point
    self.scrollView.contentOffset = CGPoint.zero;
    // set frame to content size
    self.scrollView.frame = CGRect(x: 0, y: 0, width: self.scrollView.contentSize.width, height: self.scrollView.contentSize.height)
    // remove background
    self.scrollView.backgroundColor = UIColor.clear

    // make temp view with scroll view content size
    // a workaround for issue when image on ipad was drawn incorrectly
    let tempView = UIView(frame: CGRect(x: 0, y: 0, width: self.scrollView.contentSize.width, height: self.scrollView.contentSize.height))

    // save superview
    let tempSuperView = self.scrollView.superview
    // remove scrollView from old superview
    self.scrollView.removeFromSuperview()
    // and add to tempView
    tempView.addSubview(self.scrollView)

    // render view
    // drawViewHierarchyInRect not working correctly
    tempView.layer.render(in: UIGraphicsGetCurrentContext()!)
    // and get image
    image = UIGraphicsGetImageFromCurrentImageContext()!;

    // and return everything back
    tempView.subviews[0].removeFromSuperview()
    tempSuperView?.addSubview(self.scrollView)

    // restore saved settings
    self.scrollView.contentOffset = savedContentOffset;
    self.scrollView.frame = savedFrame;
    self.scrollView.backgroundColor = savedBackgroundColor

    UIGraphicsEndImageContext();

    return image
}
查看更多
倾城一夜雪
4楼-- · 2019-01-01 00:43

I took the solution above from @Roopesh Mittal and made it safer/cleaner.

Swift 4 compatible

fileprivate extension UIScrollView {
    func screenshot() -> UIImage? {
        let savedContentOffset = contentOffset
        let savedFrame = frame

        UIGraphicsBeginImageContext(contentSize)
        contentOffset = .zero
        frame = CGRect(x: 0, y: 0, width: contentSize.width, height: contentSize.height)

        guard let context = UIGraphicsGetCurrentContext() else { return nil }

        layer.render(in: context)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext();

        contentOffset = savedContentOffset
        frame = savedFrame

        return image
    }
}
查看更多
姐姐魅力值爆表
5楼-- · 2019-01-01 00:44

Here's another way of doing it, which takes the zoom level into account. I have a scrollview with 4 different UIImageView layers in it, and I want to take a screenshot of their current state:

float theScale = 1.0f / theScrollView.zoomScale;
// The viewing rectangle in absolute coordinates
CGRect visibleArea = CGRectMake((int)(theScrollView.contentOffset.x * theScale), (int)(theScrollView.contentOffset.y * theScale),
                                (int)(theScrollView.bounds.size.width * theScale), (int)(theScrollView.bounds.size.height * theScale));

NSArray *layers = [NSArray arrayWithObjects:imageLayer1, imageLayer2, imageLayer3, imageLayer4, nil];
UIGraphicsBeginImageContext(visibleArea.size);
for (UIImageView *layer in layers) {
    CALayer *coreLayer = layer.layer;
    coreLayer.bounds = CGRectMake(layer.frame.origin.x - visibleArea.origin.x, layer.frame.origin.y - visibleArea.origin.y, layer.frame.size.width, layer.frame.size.height);
    [coreLayer renderInContext:UIGraphicsGetCurrentContext()];
}
UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

This takes the screenshot in absolute coordinates. That is, if you have a 2048*2048 image in the scrollview and you can see about a quarter of it, then regardless of the resolution of your screen it would take a screenshot of 512*512. If you want to take a screenshot at your screen resolution (say, 320*480) then you have to adjust the image as follows, directly after the above code:

UIGraphicsBeginImageContext(theScrollView.frame.size);
[screenshot drawInRect:CGRectMake(0, 0, theScrollView.frame.size.width, theScrollView.frame.size.height)];
UIImage *smallScreenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
查看更多
梦该遗忘
6楼-- · 2019-01-01 00:49

for me answer https://stackoverflow.com/a/3539944/4164443 didn't work. I had a task of implementing this on iOS 8.

Actually this method works for iPhone, but iPad (both simu and real device) is another case. It just renders visible part and the rest of image is just blank.

I tried with drawViewHierarchyInRect - no luck. Depending on afterScreenUpdates being true or false I got stretched part of image or again only part of image.

So the only way I found to achieve correct screenshoting is to add scrollview to another temp view and render it.

Sample code is below (scrollview is outlet in my VC)

func getImageOfScrollView()->UIImage{
    var image = UIImage();

    UIGraphicsBeginImageContextWithOptions(self.scrollView.contentSize, false, UIScreen.mainScreen().scale)

    // save initial values
    let savedContentOffset = self.scrollView.contentOffset;
    let savedFrame = self.scrollView.frame;
    let savedBackgroundColor = self.scrollView.backgroundColor

    // reset offset to top left point
    self.scrollView.contentOffset = CGPointZero;
    // set frame to content size
    self.scrollView.frame = CGRectMake(0, 0, self.scrollView.contentSize.width, self.scrollView.contentSize.height);
    // remove background
    self.scrollView.backgroundColor = UIColor.clearColor()

    // make temp view with scroll view content size
    // a workaround for issue when image on ipad was drawn incorrectly
    let tempView = UIView(frame: CGRectMake(0, 0, self.scrollView.contentSize.width, self.scrollView.contentSize.height))

    // save superview
    let tempSuperView = self.scrollView.superview
    // remove scrollView from old superview
    self.scrollView.removeFromSuperview()
    // and add to tempView
    tempView.addSubview(self.scrollView)

    // render view
    // drawViewHierarchyInRect not working correctly
    tempView.layer.renderInContext(UIGraphicsGetCurrentContext())
    // and get image
    image = UIGraphicsGetImageFromCurrentImageContext();

    // and return everything back
    tempView.subviews[0].removeFromSuperview()
    tempSuperView?.addSubview(self.scrollView)

    // restore saved settings
    self.scrollView.contentOffset = savedContentOffset;
    self.scrollView.frame = savedFrame;
    self.scrollView.backgroundColor = savedBackgroundColor

    UIGraphicsEndImageContext();

    return image
}
查看更多
看淡一切
7楼-- · 2019-01-01 00:50

A refined Swift 4.1 version, based on @RyanG 's answer:

fileprivate extension UIScrollView {
    func screenshot() -> UIImage? {
        // begin image context
        UIGraphicsBeginImageContextWithOptions(contentSize, false, 0.0)
        // save the orginal offset & frame 
        let savedContentOffset = contentOffset
        let savedFrame = frame
        // end ctx, restore offset & frame before returning
        defer {
            UIGraphicsEndImageContext()
            contentOffset = savedContentOffset
            frame = savedFrame
        }
        // change the offset & frame so as to include all content
        contentOffset = .zero
        frame = CGRect(x: 0, y: 0, width: contentSize.width, height: contentSize.height)
        guard let ctx = UIGraphicsGetCurrentContext() else {
            return nil
        }
        layer.render(in: ctx)
        let image = UIGraphicsGetImageFromCurrentImageContext()

        return image
    }
}
查看更多
登录 后发表回答