Getting a screenshot of a UIScrollView, including offscreen parts

前端 未结 12 2120
遥遥无期
遥遥无期 2020-11-22 17:31

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

-(void)takeScreenshot {  
  CGRect contextRect  =          


        
相关标签:
12条回答
  • 2020-11-22 17:40

    Here is code that works ...

    - (IBAction) renderScrollViewToImage
    {
        UIImage* image = nil;
    
        UIGraphicsBeginImageContext(_scrollView.contentSize);
        {
            CGPoint savedContentOffset = _scrollView.contentOffset;
            CGRect savedFrame = _scrollView.frame;
    
            _scrollView.contentOffset = CGPointZero;
            _scrollView.frame = CGRectMake(0, 0, _scrollView.contentSize.width, _scrollView.contentSize.height);
    
            [_scrollView.layer renderInContext: UIGraphicsGetCurrentContext()];     
            image = UIGraphicsGetImageFromCurrentImageContext();
    
            _scrollView.contentOffset = savedContentOffset;
            _scrollView.frame = savedFrame;
        }
        UIGraphicsEndImageContext();
    
        if (image != nil) {
            [UIImagePNGRepresentation(image) writeToFile: @"/tmp/test.png" atomically: YES];
            system("open /tmp/test.png");
        }
    }
    

    The last few lines simply write the image to /tmp/test.png and then opens it in Preview.app. This obviously only works on in the Simulator :-)

    Complete project in the ScrollViewScreenShot Github Repository

    0 讨论(0)
  • 2020-11-22 17:43

    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
    }
    
    0 讨论(0)
  • 2020-11-22 17:48

    I took this solution from @Roopesh Mittal's answer and made it safer/cleaner.

    Swift 5 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
        }
    }
    
    0 讨论(0)
  • 2020-11-22 17:51

    In iOS 13 I have ran into issue that this line won't work

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

    to fix the issue, I am removing scrollview from parent and then attaching in after taking the screenshot.

    0 讨论(0)
  • 2020-11-22 17:51

    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();
    
    0 讨论(0)
  • 2020-11-22 17:55

    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

    0 讨论(0)
提交回复
热议问题