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

59

我有一个UIScrollView的子类,实现了一个名为takeScreenshot的方法,代码如下:

-(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的整个快照,包括不在屏幕上的区域?或者我需要向下滚动视图,拍第二张照片再将它们拼接在一起?

16个回答

2
如果您不想将滚动视图扩展到整个屏幕之外(而且它也无法与自动布局一起使用),那么有更好的方法。
您可以使用核心图形转换(core graphics transforms)与滚动视图的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
}
}

这是我写的一篇博客文章,解释了生成PDF的过程。

生成PDF的过程与截图图像非常相似,只是需要制作一个与滚动视图大小相匹配的大画布,然后分块抓取内容,而不是页面。


2

Swift 5 版本

    extension UIScrollView {
    
    func takeScrollViewScreenShot() -> UIImage? {
        
        UIGraphicsBeginImageContext(self.contentSize)
        
        let savedContentOffset = self.contentOffset
        let savedFrame = self.frame
        
        self.contentOffset = CGPoint.zero
        self.layer.frame = CGRect(x: 0, y: 0, width: self.contentSize.width, height: self.contentSize.height)
        
        self.layer.render(in: UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        
        self.contentOffset = savedContentOffset
        self.frame = savedFrame
        UIGraphicsEndImageContext()
        return image
    }
}

这应该是2023年的新答案!我不必删除scrollview或任何其他东西。这里我要添加的一个附加项以提高质量是将func中的第一行更改为UIGraphicsBeginImageContextWithOptions(this.contentSize, false, 0) - RyanG

1
通常我不会推荐使用库,但是... 使用SnapshotKit。它非常好用,代码看起来也不错。使用它很简单:
Objective-C:
UIImage *tableViewScreenShot = [yourTableView takeSnapshotOfFullContent];

Swift:

let tableViewScreenShot: UIImage = yourTableView.takeSnapshotOfFullContent()

是的,SnapshotKit非常好用!我尝试了“async_takeSnapshotOfFullContent()”来进行scrollview截图。`private func async_takeSnapshotOfFullContent() { self.aScrollView.asyncTakeSnapshotOfFullContent { (image) in UIImageWriteToSavedPhotosAlbum(image!, nil, nil, nil) } }@IBAction func buttonTap(_ sender: Any) { async_takeSnapshotOfFullContent() }` - Michael42

1
我发现了以下代码,并且它对我有效。请尝试这个代码。
extension UIView {
func capture() -> UIImage {
    UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale)
    drawHierarchy(in: self.bounds, afterScreenUpdates: true)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return image
}}

0

在我看来,已接受的解决方案可以通过更新scrollView.layer.frame而不是scrollView.frame来修复,正如此处所指出的那样。虽然我并不确定我真正理解这为什么有效!


-2

我不太清楚,但是我猜如果我们针对横屏设置 contextRect 的大小,它可能会很好地运行:

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

因为这个 contextRect 将决定 UIGraphicsBeginImageContext 的大小,所以我希望将高度加倍可以解决你的问题。


1
看起来它只是在可见区域的末尾截断。增加高度也没有帮助。 - Tim Sullivan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接