如何从 UIView 创建特定大小的图像

How to create an image of specific size from UIView

我正在开发 iOS 应用程序,该应用程序应该允许用户创建 Instagram 快拍照片并将它们导出到 Instagram。基本上像 Unfold、Stellar、Chroma Stories 这样的应用程序...我已经准备好 UI,用户可以在其中 select 从准备好的模板中添加自己的带有滤镜、标签等的照片。

我的问题是 - 导出创建的 UIView 到更大的图像的最佳方式是什么? 我的意思是如何获得最佳质量、清晰的标签像素等? 因为带有子视图(添加的照片、标签...)的模板视图占据了设备屏幕的 +- 一半。但我需要更大尺寸的导出图像。 目前我使用:

 func makeImageFromView() -> UIImage {
    let format = UIGraphicsImageRendererFormat()
    let size = CGSize(width: 1080 / format.scale, height: 1920 / format.scale)

    let renderer = UIGraphicsImageRenderer(size: size, format: format)
    let image = renderer.image { (ctx) in
        templateView.drawHierarchy(in: CGRect(origin: .zero, size: size), afterScreenUpdates: true)
    }
    return image
}

生成的图像大小为 1080 x 1920,但标签不清晰。 在将其制作成图像之前,我是否需要以某种方式缩放照片和字体大小? 谢谢!

所以实际上是的,在捕获图像之前我需要缩放整个视图及其子视图。这是我的发现(可能是显而易见的事情,但我花了一段时间才意识到这一点——我会很高兴有任何改进)

渲染相同尺寸的图像

当你想将UIView截取为图像时,你可以简单地使用这个函数。结果图像将与视图具有相同的大小(根据实际设备缩放 2x / 3x)

 func makeImageFrom(_ desiredView: MyView) -> UIImage {
    let size = CGSize(width: desiredView.bounds.width, height: desiredView.bounds.height)
    let renderer = UIGraphicsImageRenderer(size: size)
    let image = renderer.image { (ctx) in
        desiredView.drawHierarchy(in: CGRect(origin: .zero, size: size), afterScreenUpdates: true)
    }
    return image
}

不同尺寸的渲染图像

但是当您想要导出的图像的特定尺寸时该怎么办? 因此,根据我的用例,我想渲染最终尺寸 (1080 x 1920) 的图像,但我想捕获的视图尺寸较小(在我的例子中为 275 x 487)。如果什么都不做就做这样的渲染,画质肯定是有损失的。

如果你想避免这种情况并保留清晰的标签和其他子视图,你需要尝试理想地将视图缩放到所需的大小。在我的例子中,从275 x 487 到 1080 x 1920。

func makeImageFrom(_ desiredView: MyView) -> UIImage {
    let format = UIGraphicsImageRendererFormat()
    // We need to divide desired size with renderer scale, otherwise you get output size larger @2x or @3x
    let size = CGSize(width: 1080 / format.scale, height: 1920 / format.scale)

    let renderer = UIGraphicsImageRenderer(size: size, format: format)
    let image = renderer.image { (ctx) in
    // remake constraints or change size of desiredView to 1080 x 1920
    // handle it's subviews (update font size etc.)
    // ...
    desiredView.drawHierarchy(in: CGRect(origin: .zero, size: size), afterScreenUpdates: true)
    // undo the size changes
    // ...
    }
    return image
}

我的做法

但是因为我不想弄乱显示给用户的视图大小,所以我采取了不同的方式并使用了不显示给用户的第二个视图。这意味着就在我想要捕获图像之前,我准备了 "duplicated" 具有相同内容但更大尺寸的视图。我没有将它添加到视图控制器的视图层次结构中,所以它不可见。

重要提示!

您确实需要处理子视图。这意味着,你必须增加字体大小,更新移动子视图的位置(例如它们的中心)等! 这里只是几行来说明这一点:

        // 1. Create bigger view
        let hdView = MyView()
        hdView.frame = CGRect(x: 0, y: 0, width: 1080, height: 1920)

        // 2. Load content according to the original view (desiredView)
        // set text, images...

        // 3. Scale subviews
        // Find out what scale we need
        let scaleMultiplier: CGFloat = 1080 / desiredView.bounds.width // 1080 / 275 = 3.927 ...

        // Scale everything, for examples label's font size
        [label1, label2].forEach { [=12=].font =  UIFont.systemFont(ofSize: [=12=].font.pointSize * scaleMultiplier, weight: .bold) }
        // or subview's center
        subview.center = subview.center.applying(.init(scaleX: scaleMultiplier, y: scaleMultiplier))

        // 4. Render image from hdView
        let hdImage = makeImageFrom(hdView)

与实际使用的质量差异——放大到标签: