代码之家  ›  专栏  ›  技术社区  ›  Tim Sullivan

获取UIScrollView的屏幕截图,包括屏幕外部分

  •  48
  • Tim Sullivan  · 技术社区  · 14 年前

    我有一个 UIScrollView Decent实现了如下截图方法:

    -(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.
    }
    

    这基本上移动到滚动视图的顶部,并获取可见区域的屏幕截图。当iPad是面向肖像的时候,它工作得很好,但是当它是横向的时候,图像的底部会被切断(因为可见区域的高度只有748,而不是1004)。

    有没有可能拍到 UIScrollView公司 ,包括不在屏幕上的区域?或者我需要向下滚动视图,拍第二张照片并将它们缝合在一起?

    10 回复  |  直到 8 年前
        1
  •  112
  •   Stefan Arentz    14 年前

    这是一个有效的代码。。。

    - (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");
        }
    }
    

    最后几行只是将图像写入/tmp/测试.png然后把它打开预览.app. 这显然只适用于模拟器:-)

    完成项目 ScrollViewScreenShot Github Repository

        2
  •  27
  •   Sam Spencer    5 年前

    currently accepted answer from Stefan Arentz 没用。

    我必须在iOS8和更高版本上实现这一点,并在iPhone上进行了测试。接受的答案只是呈现滚动视图的可见部分,而图像的其余部分保持空白。

    我试着用 drawViewHierarchyInRect -运气不好。取决于 afterScreenUpdates true false 我得到了图像的一部分或只是部分内容。

    UIScrollView 的全部内容是将其添加到另一个临时视图中,然后进行渲染。

    scrollview 是我的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
    }
    
        3
  •  10
  •   Roopesh Mittal    8 年前

    extension UIView {
        func screenshot() -> UIImage {
    
                if(self is UIScrollView) {
                    let scrollView = self as! UIScrollView
    
                    let savedContentOffset = scrollView.contentOffset
                    let savedFrame = scrollView.frame
    
                    UIGraphicsBeginImageContext(scrollView.contentSize)
                    scrollView.contentOffset = .zero
                    self.frame = CGRect(x: 0, y: 0, width: scrollView.contentSize.width, height: scrollView.contentSize.height)
                    self.layer.render(in: UIGraphicsGetCurrentContext()!)
                    let image = UIGraphicsGetImageFromCurrentImageContext()
                    UIGraphicsEndImageContext();
    
                    scrollView.contentOffset = savedContentOffset
                    scrollView.frame = savedFrame
    
                    return image!
                }
    
                UIGraphicsBeginImageContext(self.bounds.size)
                self.layer.render(in: UIGraphicsGetCurrentContext()!)
                let image = UIGraphicsGetImageFromCurrentImageContext()
                UIGraphicsEndImageContext()
                return image!
    
            }
    }
    
        4
  •  9
  •   RyanG    5 年前

    我从 @Roopesh Mittal's answer

    Swift 5兼容

    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
  •  8
  •   Sam Spencer    5 年前

    完善的Swift 4.x/5.0版本,基于 @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
        }
    }
    
        6
  •  7
  •   IceFloe    4 年前

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

    为了解决这个问题,我将从父级删除scrollview,然后在截图后附加。

    完整代码:

    func screenshot() -> UIImage? {
            let savedContentOffset = contentOffset
            let savedFrame = frame
            defer {
                contentOffset = savedContentOffset
                frame = savedFrame
            }
    
            contentOffset = .zero
            frame = CGRect(x: 0, y: 0, width: contentSize.width, height: contentSize.height)
    
            let image = UIGraphicsImageRenderer(bounds: CGRect(origin: .zero, size: contentSize)).image { renderer in
                let context = renderer.cgContext
                layer.render(in: context)
            }
    
            return image
        }
    

    func getScreenshot() -> UIImage? {
         scrollView.removeFromSuperview()
         let image = scrollView.screenshot()
         addScrollView()
         return image
    }
    
        7
  •  4
  •   Krishna Kirana    7 年前

    SWIFT 3版本:

    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
    }
    

    这对我有用

        8
  •  2
  •   Adam    13 年前

    下面是另一种方法,它考虑了缩放级别。我有一个包含4个不同UIImageView层的scrollview,我想截取它们的当前状态:

    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();
    

    UIGraphicsBeginImageContext(theScrollView.frame.size);
    [screenshot drawInRect:CGRectMake(0, 0, theScrollView.frame.size.width, theScrollView.frame.size.height)];
    UIImage *smallScreenshot = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
        9
  •  2
  •   Moshe    9 年前

    如果您不想将滚动视图扩展到整个屏幕之外(而且自动布局也不起作用),有一个更好的方法。

    contentOffset

    //
    //  ScrollViewSnapshotter.swift
    //  ScrollViewSnapshotter
    //
    //  Created by Moshe Berman on 4/10/16.
    //  Copyright © 2016 Moshe Berman. All rights reserved.
    //
    
    import UIKit
    
    class ScrollViewSnapshotter: NSObject {
    
    
    func PDFWithScrollView(scrollview: UIScrollView) -> NSData {
    
        /**
         *  Step 1: The first thing we need is the default origin and size of our pages.
         *          Since bounds always start at (0, 0) and the scroll view's bounds give us
         *          the correct size for the visible area, we can just use that.
         *
         *          In the United States, a standard printed page is 8.5 inches by 11 inches,
         *          but when generating a PDF it's simpler to keep the page size matching the
         *          visible area of the scroll view. We can let our printer software (such
         *          as the Preview app on OS X or the Printer app on iOS) do the scaling.
         *
         *          If we wanted to scale ourselves, we could multiply each of those
         *          numbers by 72, to get the number of points for each dimension.
         *          We would have to change how we generated the the pages below, so
         *          for simplicity, we're going to stick to one page per screenful of content.
         */
    
        let pageDimensions = scrollview.bounds
    
        /**
         *  Step 2: Now we need to know how many pages we will need to fit our content.
         *          To get this, we divide our scroll views dimensions by the size
         *          of each page, in either direction.
         *          We also need to round up, so that the pages don't get clipped.
         */
    
        let pageSize = pageDimensions.size
        let totalSize = scrollview.contentSize
    
        let numberOfPagesThatFitHorizontally = Int(ceil(totalSize.width / pageSize.width))
        let numberOfPagesThatFitVertically = Int(ceil(totalSize.height / pageSize.height))
    
        /**
         *  Step 3: Set up a Core Graphics PDF context.
         *
         *          First we create a backing store for the PDF data, then
         *          pass it and the page dimensions to Core Graphics.
         *
         *          We could pass in some document information here, which mostly cover PDF metadata,
         *          including author name, creator name (our software) and a password to
         *          require when viewing the PDF file.
         *
         *          Also note that we can use UIGraphicsBeginPDFContextToFile() instead,
         *          which writes the PDF to a specified path. I haven't played with it, so
         *          I don't know if the data is written all at once, or as each page is closed.
         */
    
        let outputData = NSMutableData()
    
        UIGraphicsBeginPDFContextToData(outputData, pageDimensions, nil)
    
        /**
         *  Step 4: Remember some state for later.
         *          Then we need to clear the content insets, so that our
         *          core graphics layer and our content offset match up.
         *          We don't need to reset the content offset, because that
         *          happens implicitly, in the loop below.
         */
    
        let savedContentOffset = scrollview.contentOffset
        let savedContentInset = scrollview.contentInset
    
        scrollview.contentInset = UIEdgeInsetsZero
    
        /**
         *  Step 6: Now we loop through the pages and generate the data for each page.
         */
    
        if let context = UIGraphicsGetCurrentContext()
        {
            for indexHorizontal in 0 ..< numberOfPagesThatFitHorizontally
            {
                for indexVertical in 0 ..< numberOfPagesThatFitVertically
                {
    
                    /**
                     *  Step 6a: Start a new page.
                     *
                     *          This automatically closes the previous page.
                     *          There's a similar method UIGraphicsBeginPDFPageWithInfo,
                     *          which allows you to configure the rectangle of the page and
                     *          other metadata.
                     */
    
                    UIGraphicsBeginPDFPage()
    
                    /**
                     *  Step 6b:The trick here is to move the visible portion of the
                     *          scroll view *and* adjust the core graphics context
                     *          appropriately.
                     *
                     *          Consider that the viewport of the core graphics context
                     *          is attached to the top of the scroll view's content view
                     *          and we need to push it in the opposite direction as we scroll.
                     *          Further, anything not inside of the visible area of the scroll
                     *          view is clipped, so scrolling will move the core graphics viewport
                     *          out of the rendered area, producing empty pages.
                     *
                     *          To counter this, we scroll the next screenful into view, and adjust
                     *          the core graphics context. Note that core graphics uses a coordinate
                     *          system which has the y coordinate decreasing as we go from top to bottom.
                     *          This is the opposite of UIKit (although it matches AppKit on OS X.)
                     */
    
                    let offsetHorizontal = CGFloat(indexHorizontal) * pageSize.width
                    let offsetVertical = CGFloat(indexVertical) * pageSize.height
    
                    scrollview.contentOffset = CGPointMake(offsetHorizontal, offsetVertical)
                    CGContextTranslateCTM(context, -offsetHorizontal, -offsetVertical) // NOTE: Negative offsets
    
                    /**
                     *  Step 6c: Now we are ready to render the page.
                     *
                     *  There are faster ways to snapshot a view, but this
                     *  is the most straightforward way to render a layer
                     *  into a context.
                     */
    
                    scrollview.layer.renderInContext(context)
                }
            }
        }
    
        /**
         *  Step 7: End the document context.
         */
    
        UIGraphicsEndPDFContext()
    
        /**
         *  Step 8: Restore the scroll view.
         */
    
        scrollview.contentInset = savedContentInset
        scrollview.contentOffset = savedContentOffset
    
        /**
         *  Step 9: Return the data.
         *          You can write it to a file, or display it the user,
         *          or even pass it to iOS for sharing.
         */
    
        return outputData
    }
    }
    

    这里有一个 blog post 我写信解释这个过程。

        10
  •  2
  •   clopex    7 年前

    SWIFT 3版本感谢@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
    }
    
        11
  •  0
  •   Murugan M    4 年前

    我发现下面的代码和它为我工作。试试这个。。

    extension UIView {
    func capture() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale)
        drawHierarchy(in: self.bounds, afterScreenUpdates: true)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }}
    
        12
  •  0
  •   Murmeltier    4 年前

    通常我不建议使用图书馆,但是。。。使用快照工具包。它就像一个魅力和代码看起来也不错。使用它是直截了当的:

    UIImage *tableViewScreenShot = [yourTableView takeSnapshotOfFullContent];
    

    银行代码:

    let tableViewScreenShot: UIImage = yourTableView.takeSnapshotOfFullContent()
    
        13
  •  -2
  •   vodkhang    14 年前

    我知道的不多,但我可以猜如果我们把 contextRect 像这样的景观,它可能工作得很好:

      CGRect contextRect  = CGRectMake(0, 0, 1004, 768*2);
    

    UIGraphicsBeginImageContext