【问题标题】:Take a Screenshot of the entire contents of a UICollectionView截取 UICollectionView 的全部内容
【发布时间】:2023-03-04 18:57:01
【问题描述】:

我正在开发的应用程序使用集合视图单元格向用户显示数据。我希望用户能够共享单元格中包含的数据,但通常有太多单元格无法尝试重新调整大小并适合单个 iPhone 屏幕大小的窗口并获取屏幕截图。

所以我遇到的问题是尝试在屏幕上和屏幕外的集合视图中获取所有单元格的图像。我知道屏幕外的单元格实际上并不存在,但我会对一种伪造图像并绘制数据的方法感兴趣(如果这可能很快)。

简而言之,有没有一种方法可以通过 Swift 以编程方式从集合视图及其包含的单元格创建图像,无论是在屏幕上还是在屏幕外?

【问题讨论】:

  • 查看this answer。不确定,但我认为您可以将UIGraphicsBeginImageContext()size 参数设置为集合视图的边界。
  • 如果集合视图很大,您在尝试从中创建图像时可能会遇到内存问题。
  • 澄清一下,您是否尝试获取集合视图的整个内容区域的快照,即使该内容区域的一部分在我们的集合视图范围之外的屏幕之外?
  • 是的,他在下面答案的评论中说。 ;)
  • 是的,我会改写上面的

标签: swift uiimage uicollectionview


【解决方案1】:

更新

如果内存不是问题:

mutating func screenshot(scale: CGFloat) -> UIImage {
    let currentSize = frame.size
    let currentOffset = contentOffset // temp store current offset

    frame.size = contentSize
    setContentOffset(CGPointZero, animated: false)        

    // it might need a delay here to allow loading data.

    let rect = CGRect(x: 0, y: 0, width: self.bounds.size.width, height: self.bounds.size.height)
    UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.mainScreen().scale)
    self.drawViewHierarchyInRect(rect, afterScreenUpdates: true)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    frame.size = currentSize
    setContentOffset(currentOffset, animated: false)

    return resizeUIImage(image, scale: scale)        
}

这对我有用:

github link -> 包含最新代码

getScreenshotRects 创建要滚动到的偏移量和要捕获的帧。 (命名并不完美)

takeScreenshotAtPoint 滚动到该点,设置延迟以允许重绘,截取屏幕截图并通过完成处理程序返回。

stitchImages 创建一个与内容大小相同的矩形并在其中绘制所有图像。

makeScreenshotsUIImage 的嵌套数组上使用didSet 和一个计数器来创建所有图像,同时等待完成。完成后,它会触发自己的完成处理程序。

基本部分:

  • 滚动收藏视图-> 作品
  • 重绘延迟截屏 -> 有效
  • 裁剪重叠的图像 -> 显然不需要
  • 拼接所有图片 -> 作品
  • 基础数学 -> 有效
  • 当这一切发生时,可能会冻结屏幕或隐藏(这不在我的回答中)

代码:

protocol ScrollViewImager {

    var bounds : CGRect { get }

    var contentSize : CGSize { get }

    var contentOffset : CGPoint { get }

    func setContentOffset(contentOffset: CGPoint, animated: Bool)

    func drawViewHierarchyInRect(rect: CGRect, afterScreenUpdates: Bool) -> Bool
}

extension ScrollViewImager {

    func screenshot(completion: (screenshot: UIImage) -> Void) {

        let pointsAndFrames = getScreenshotRects()
        let points = pointsAndFrames.points
        let frames = pointsAndFrames.frames

        makeScreenshots(points, frames: frames) { (screenshots) -> Void in
            let stitched = self.stitchImages(images: screenshots, finalSize: self.contentSize)
            completion(screenshot: stitched!)
        }

    }

    private func makeScreenshots(points:[[CGPoint]], frames : [[CGRect]],completion: (screenshots: [[UIImage]]) -> Void) {

        var counter : Int = 0

        var images : [[UIImage]] = [] {
            didSet {
                if counter < points.count {
                    makeScreenshotRow(points[counter], frames : frames[counter]) { (screenshot) -> Void in
                        counter += 1
                        images.append(screenshot)
                    }
                } else {
                    completion(screenshots: images)
                }
            }
        }

        makeScreenshotRow(points[counter], frames : frames[counter]) { (screenshot) -> Void in
            counter += 1
            images.append(screenshot)
        }

    }

    private func makeScreenshotRow(points:[CGPoint], frames : [CGRect],completion: (screenshots: [UIImage]) -> Void) {

        var counter : Int = 0

        var images : [UIImage] = [] {
            didSet {
                if counter < points.count {
                    takeScreenshotAtPoint(point: points[counter]) { (screenshot) -> Void in
                        counter += 1
                        images.append(screenshot)
                    }
                } else {
                    completion(screenshots: images)
                }
            }
        }

        takeScreenshotAtPoint(point: points[counter]) { (screenshot) -> Void in
            counter += 1
            images.append(screenshot)
        }

    }

    private func getScreenshotRects() -> (points:[[CGPoint]], frames:[[CGRect]]) {

        let vanillaBounds = CGRect(x: 0, y: 0, width: self.bounds.size.width, height: self.bounds.size.height)

        let xPartial = contentSize.width % bounds.size.width
        let yPartial = contentSize.height % bounds.size.height

        let xSlices = Int((contentSize.width - xPartial) / bounds.size.width)
        let ySlices = Int((contentSize.height - yPartial) / bounds.size.height)

        var currentOffset = CGPoint(x: 0, y: 0)

        var offsets : [[CGPoint]] = []
        var rects : [[CGRect]] = []

        var xSlicesWithPartial : Int = xSlices

        if xPartial > 0 {
            xSlicesWithPartial += 1
        }

        var ySlicesWithPartial : Int = ySlices

        if yPartial > 0 {
            ySlicesWithPartial += 1
        }

        for y in 0..<ySlicesWithPartial {

            var offsetRow : [CGPoint] = []
            var rectRow : [CGRect] = []
            currentOffset.x = 0

            for x in 0..<xSlicesWithPartial {

                if y == ySlices && x == xSlices {
                    let rect = CGRect(x: bounds.width - xPartial, y: bounds.height - yPartial, width: xPartial, height: yPartial)
                    rectRow.append(rect)

                } else if y == ySlices {
                    let rect = CGRect(x: 0, y: bounds.height - yPartial, width: bounds.width, height: yPartial)
                    rectRow.append(rect)

                } else if x == xSlices {
                    let rect = CGRect(x: bounds.width - xPartial, y: 0, width: xPartial, height: bounds.height)
                    rectRow.append(rect)

                } else {
                    rectRow.append(vanillaBounds)
                }

                offsetRow.append(currentOffset)

                if x == xSlices {
                    currentOffset.x = contentSize.width - bounds.size.width
                } else {
                    currentOffset.x = currentOffset.x + bounds.size.width
                }
            }
            if y == ySlices {
                currentOffset.y = contentSize.height - bounds.size.height
            } else {
                currentOffset.y = currentOffset.y + bounds.size.height
            }

            offsets.append(offsetRow)
            rects.append(rectRow)

        }

        return (points:offsets, frames:rects)

    }

    private func takeScreenshotAtPoint(point point_I: CGPoint, completion: (screenshot: UIImage) -> Void) {
        let rect = CGRect(x: 0, y: 0, width: self.bounds.size.width, height: self.bounds.size.height)
        let currentOffset = contentOffset
        setContentOffset(point_I, animated: false)

        delay(0.001) {

            UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.mainScreen().scale)
            self.drawViewHierarchyInRect(rect, afterScreenUpdates: true)
            let image = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()


            self.setContentOffset(currentOffset, animated: false)
            completion(screenshot: image)
        }
    }

    private func delay(delay:Double, closure:()->()) {
        dispatch_after(
            dispatch_time(
                DISPATCH_TIME_NOW,
                Int64(delay * Double(NSEC_PER_SEC))
            ),
            dispatch_get_main_queue(), closure)
    }


    private func crop(image image_I:UIImage, toRect rect:CGRect) -> UIImage? {

        guard let imageRef: CGImageRef = CGImageCreateWithImageInRect(image_I.CGImage, rect) else {
            return nil
        }
        return UIImage(CGImage:imageRef)
    }

    private func stitchImages(images images_I: [[UIImage]], finalSize : CGSize) -> UIImage? {

        let finalRect = CGRect(x: 0, y: 0, width: finalSize.width, height: finalSize.height)

        guard images_I.count > 0 else {
            return nil
        }

        UIGraphicsBeginImageContext(finalRect.size)

        var offsetY : CGFloat = 0

        for imageRow in images_I {

            var offsetX : CGFloat = 0

            for image in imageRow {

                let width = image.size.width
                let height = image.size.height


                let rect = CGRect(x: offsetX, y: offsetY, width: width, height: height)
                image.drawInRect(rect)

                offsetX += width

            }

            offsetX = 0

            if let firstimage = imageRow.first {
                offsetY += firstimage.size.height
            } // maybe add error handling here
        }

        let stitchedImages = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return stitchedImages
    }
}

extension UIScrollView : ScrollViewImager {

}

【讨论】:

  • 这很好用,它也适用于水平滚动视图——感谢您的帮助
  • @TheBeanstalk 添加了一种方法来简单地调整滚动视图的大小并截取屏幕截图。
  • 非常感谢您的代码。如果有人感兴趣,我将它分叉并更新为 Swift 4.2:github.com/ibakurov/ScrollViewImager
【解决方案2】:

使用 UIKit 图形函数将 UICollectionView 的位图数据绘制到 UIImage 中。然后,您将拥有一个 UIImage,您可以将其保存到磁盘或使用它做任何您需要的事情。像这样的东西应该可以工作:

// your collection view
@IBOutlet weak var myCollectionView: UICollectionView!

//...

let image: UIImage!

// draw your UICollectionView into a UIImage
UIGraphicsBeginImageContext(myCollectionView.frame.size) 

myCollectionView.layer.renderInContext(UIGraphicsGetCurrentContext()!)

image = UIGraphicsGetImageFromCurrentImageContext()  

UIGraphicsEndImageContext()

【讨论】:

  • 试过这个,但我得到一个空白图像。另外,我希望包括屏幕外单元格。
  • UICollectionView 不会一次将所有单元格加载到内存中,因此许多“屏幕外”单元格甚至不真正存在,直到用户平移它们被加载和按需绘制的点.因此,如果您想制作所有单元格的图像,则需要创建一个适配器,该适配器可以读取单元格数据中的@TheBeanstalk,为该单元格创建并绘制视图,然后输出图像。然后,当您需要构建合成图像时,您将遍历您的集合,通过为其生成图像的适配器运行每个数据项,然后将所有图像拼接成一个大图像
【解决方案3】:

为swift 4制作UICollectionView的截图

func makeScreenShotToShare()-> UIImage{
        UIGraphicsBeginImageContextWithOptions(CGSize.init(width: self.colHistory.contentSize.width, height: self.colHistory.contentSize.height + 84.0), false, 0)
        colHistory.scrollToItem(at: IndexPath.init(row: 0, section: 0), at: .top, animated: false)
        colHistory.layer.render(in: UIGraphicsGetCurrentContext()!)
        let row = colHistory.numberOfItems(inSection: 0)
        let numberofRowthatShowinscreen = self.colHistory.size.height / (self.arrHistoryData.count == 1 ? 130 : 220)
        let scrollCount = row / Int(numberofRowthatShowinscreen)

        for  i in 0..<scrollCount {
            colHistory.scrollToItem(at: IndexPath.init(row: (i+1)*Int(numberofRowthatShowinscreen), section: 0), at: .top, animated: false)
            colHistory.layer.render(in: UIGraphicsGetCurrentContext()!)
        }

        let image:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext();
        return image
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多