Ios 获取UIScrollView的屏幕截图,包括屏幕外部分
我有一个Ios 获取UIScrollView的屏幕截图,包括屏幕外部分,ios,iphone,ipad,core-graphics,Ios,Iphone,Ipad,Core Graphics,我有一个UIScrollViewDecent,它实现了一个截图方法,如下所示: -(void)takeScreenshot { CGRect contextRect = CGRectMake(0, 0, 768, 1004); UIGraphicsBeginImageContext(contextRect.size); [self.layer renderInContext:UIGraphicsGetCurrentContext()]; UIImage *viewI
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
的快照,包括屏幕上未显示的区域?或者我需要向下滚动视图,拍摄第二张照片并将它们缝合在一起吗?我知道的不多,但我可以猜,如果我们为横向设置contextRect
的大小,可能会很好:
CGRect contextRect = CGRectMake(0, 0, 1004, 768*2);
因为此contextRect将决定UIGraphicsBeginImageContext的大小,所以我希望双倍的高度可以解决您的问题以下是有效的代码
- (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/test.png,然后在Preview.app中打开它。这显然只在模拟器中起作用:-)
在中完成项目这里有另一种方法,它考虑了缩放级别。我有一个包含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();
这将以绝对坐标拍摄屏幕截图。也就是说,如果你在scrollview中有一个2048*2048的图像,你可以看到它的四分之一,那么不管你的屏幕分辨率如何,它的屏幕截图都是512*512。如果您想以屏幕分辨率(例如,320*480)拍摄屏幕快照,则必须在上述代码之后直接按如下方式调整图像:
UIGraphicsBeginImageContext(theScrollView.frame.size);
[screenshot drawInRect:CGRectMake(0, 0, theScrollView.frame.size.width, theScrollView.frame.size.height)];
UIImage *smallScreenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
对我来说,这个方法不起作用
我必须在iOS 8及以上版本上实现这一点,并在iPhone上进行了测试。接受的答案只是呈现滚动视图的可见部分,而图像的其余部分保持空白
我尝试使用drawViewHierarchyInRect
修复此问题-运气不好。取决于afterScreenUpdates
是否为true
或false
我得到了图像的拉伸部分或仅部分内容
我发现,要对UIScrollView
的全部内容进行正确的快照,唯一的方法是将其添加到另一个临时视图中,然后进行渲染
下面是示例代码(scrollview
是我的VC中的出口)
如果您不想将滚动视图扩展到整个屏幕之外(而且它无论如何也无法与autolayout一起工作),有一个更好的方法 您可以将核心图形转换与滚动视图的
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
}
}
//
//ScrollViewSnapshotter.swift
//滚动视图快照器
//
//由Moshe Berman于2016年4月10日创作。
//版权所有©2016 Moshe Berman。版权所有。
//
导入UIKit
类ScrollViewSnapshotter:NSObject{
func PDFWithScrollView(scrollview:UIScrollView)->NSData{
/**
*第一步:我们需要的第一件事是页面的默认原点和大小。
*因为边界总是从(0,0)开始,滚动视图的边界给我们
*可见区域的正确大小,我们可以使用它。
*
*在美国,标准打印页面为8.5英寸乘11英寸,
*但是在生成PDF时,保持页面大小与
*滚动视图的可见区域。我们可以让我们的打印机软件(如
*与OS X上的预览应用程序或iOS上的打印机应用程序一样,进行缩放。
*
*如果我们想扩展自己,我们可以将每一个都放大
*乘以72得到每个维度的点数。
*我们将不得不改变我们如何生成下面的页面,所以
*为了简单起见,我们将坚持每屏内容一页。
*/
让pageDimensions=scrollview.bounds
/**
*第二步:现在我们需要知道我们需要多少页面来适应我们的内容。
*为了得到这个结果,我们将滚动视图的尺寸除以大小
*每一页的任意方向。
*我们还需要将页面进行汇总,这样页面就不会被剪裁。
*/
让pageSize=pageDimensions.size
让totalSize=scrollview.contentSize
让numberOfPageSthatFit水平=Int(ceil(totalSize.width/pageSize.width))
让numberOfPageSthatFitVertical=Int(ceil(totalSize.height/pageSize.height))
/**
*步骤3:设置核心图形PDF上下文。
*
*首先,我们为PDF数据创建一个备份存储,然后
*将其和页面尺寸传递给核心图形。
*
*我们可以在这里传递一些文档信息,主要包括PDF元数据,
*包括作者姓名、创建者姓名(我们的软件)和密码
*查看PDF文件时需要。
*
*还请注意,我们可以使用UIGraphicsBeginPDFContextToFile()代替,
*它将PDF写入指定路径。我还没有玩过它,所以
*我不知道数据是一次性写入的,还是在每个页面关闭时写入的。
*/
让outputData=NSMutableData()
UIGraphicsBeginPDFContextToData(输出数据,页面维度,无)
/**
*第四步:记住一些状态以备将来使用。
*然后我们需要清除内容插图,以便
*核心图形层和我们的内容偏移匹配。
*我们不需要重置内容偏移量,因为
*在下面的循环中隐式发生。
*/
让savedContentOffset=scrollview.contentOffset
让savedContentSet=scrollview.contentInset
scrollview.contentInset=UIEdgeInsetsZero
/**
*步骤6:现在我们循环浏览页面并为每个页面生成数据。
*/
//
// 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
}
}
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!
}
}
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
}
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
}
}
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
}
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
}
}
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
}
extension UIView {
func capture() -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale)
drawHierarchy(in: self.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}}
UIImage *tableViewScreenShot = [yourTableView takeSnapshotOfFullContent];
let tableViewScreenShot: UIImage = yourTableView.takeSnapshotOfFullContent()