iosswiftuikitsnapshot

How to take a large snapshot in iOS


In my case, I need to output a relatively large floor plan, and I simplified the code as follows. In the case of a relatively large size, the correct image cannot be output

Sample Code

let image1Size = CGSize(width: 400, height: 3000)
let size1 = CGSize(width: 430, height: 682)
    
let testView = UIView(frame: CGRect(origin: .zero, size: image1Size))
testView.backgroundColor = .red
    
let testImage = UIGraphicsImageRenderer(size: image1Size).image { context in
    testView.drawHierarchy(in: CGRect(origin: .zero, size: image1Size), afterScreenUpdates: true)
}
    
let test2View = UIView(frame: CGRect(origin: .zero, size: size1))
test2View.backgroundColor = .red
    
let test2Image = UIGraphicsImageRenderer(size: size1).image { context in
    test2View.drawHierarchy(in: CGRect(origin: .zero, size: size1), afterScreenUpdates: true)
}

result: error image expect image

I expect to be able to output the correct image at a relatively large size, or any other large image output possible


Solution

  • The size of the rect in:

    func drawHierarchy(in rect: CGRect, afterScreenUpdates afterUpdates: Bool) -> Bool
    

    is limited to 8192 x 8192 pixels.

    That means on a 3x device, the max rect size is 2730, 2730 (2730 * 3 == 8190)

    On a 2x device, the max rect size is 4096, 4096 (4096 * 2 == 8192)

    You can create a UIImage that is larger than that, and render several views into it... but each view's size is limited.

    You can, however, render a view's layer at a larger size:

    let viewSize: CGSize = .init(width: 400.0, height: 3000.0)
    
    var testView = UIView(frame: CGRect(origin: .zero, size: viewSize))
    testView.backgroundColor = .red
    let v1 = UILabel()
    v1.text = "Top-Left"
    v1.backgroundColor = .yellow
    let v2 = UILabel()
    v2.text = "Bottom-Right"
    v2.backgroundColor = .yellow
    
    v1.sizeToFit()
    v2.sizeToFit()
    
    v1.frame.origin = .init(x: 20.0, y: 20.0)
    v2.frame.origin = .init(x: viewSize.width - (v2.frame.width + 20.0), y: viewSize.height - (v2.frame.height + 20.0))
    testView.addSubview(v1)
    testView.addSubview(v2)
    
    let image = UIGraphicsImageRenderer(bounds: testView.bounds).image { context in
        testView.layer.render(in: context.cgContext)
    }
    

    I don't know why you can do that, but it (appears) to work.

    Note: UIGraphicsImageRenderer generates the image at point size. If your goal is to take a 400 x 3000 point UIView and generate a 400 x 3000 pixel image, use UIGraphicsImageRendererFormat and set .scale = 1:

    let fmt = UIGraphicsImageRendererFormat()
    fmt.scale = 1
    let image = UIGraphicsImageRenderer(bounds: testView.bounds, format: fmt).image { context in
        testView.layer.render(in: context.cgContext)
    }