在 SwiftUI 中,我想截取名为 ExtractedView 的子视图的屏幕截图。我正在使用 UIGraphicsImageRenderer 将视图渲染为图像。但是,当 ExtractedView 太长时,我会遇到错误消息。我该如何解决这个问题?
在 SwiftUI 中,我想截取子视图 ExtractedView 的屏幕截图。我使用 UIGraphicsImageRenderer 将视图渲染为图像。当ExtractedView太长时,它会输出一个空白图像。(但我需要一张长图片),并且收到以下错误消息。我该如何解决它
运行日志:
Render server returned error for view (0x102804310, _TtGC7SwiftUI14_UIHostingViewV5Kylin13ExtractedView_).
imageSize : (462.0, 2803.3333333333335)
身体
var body: some View {
VStack {
Button(action: {
renderToImage()
}, label: {
Text("Snapshot")
})
ExtractedView()
}
}
提取视图
struct ExtractedView: View {
var body: some View {
ScrollView {
let contentStr = "About 2000 word long piece of text"
Text(contentStr)
.frame(maxWidth: UIScreen.main.bounds.size.width, maxHeight: .infinity)
.padding()
}
}
}
渲染到图像:
@MainActor func renderToImage() -> UIImage? {
return ExtractedView()
.saveToImage()
}
保存到图像
extension View {
func saveToImage() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
let image = renderer.image { render in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
return image
}
}
我尝试使用ImageRenderer进行屏幕捕获,但它只支持有限的组件,并且不支持ScrollView或TextView,所以我放弃了
使用
ImageRenderer
,我获得了非常整洁的图像。
正如您已经提到的,
ImageRenderer
在很多方面都受到限制(ScrollView、WebView等),我的方法是为您想要在没有ScrollView
的情况下截图的内容创建一个计算属性,然后将内容传递给 ImageRenderer
。
ExtractedView
struct ExtractedView: View {
let contentStr = twokwords // I created some at https://ctxt.io/2/AABIE5oBFg
// 1. create a computed property for the content without the scroll view (text in this case)
private var myContentView: some View {
Text(contentStr)
}
var body: some View {
ScrollView {
// 2. Here, you can use the content as you previously did
myContentView
.frame(maxWidth: UIScreen.main.bounds.size.width, maxHeight: .infinity)
.padding()
}
}
// 3. This is just a helper to retrieve the content to screenshot
// You can remove `private` modifier on `myContentView` property and access it directly
func getViewToScreenshot() -> some View {
myContentView
}
}
struct ContentView: View {
@State private var screenshot: UIImage?
@Environment(\.displayScale) private var displayScale
var body: some View {
VStack {
if let screenshot {
Image(uiImage: screenshot)
.resizable()
.scaledToFit()
.frame(width: UIScreen.main.bounds.width)
.frame(maxHeight: .infinity)
.onTapGesture {
self.screenshot = nil
}
} else {
Button(action: {
if let img = renderToImage() {
self.screenshot = img
}
}, label: {
Text("Snapshot")
})
extractedView
}
}
}
private var extractedView: ExtractedView {
ExtractedView()
}
@MainActor func renderToImage() -> UIImage? {
// 1. Get the view you want and add any modifier you want (padding in my case)
let screenshot = extractedView.getViewToScreenshot().padding()
let renderer = ImageRenderer(content: screenshot)
// 2. make sure to use the correct display scale for the device
// Learn more at https://www.hackingwithswift.com/quick-start/swiftui/how-to-convert-a-swiftui-view-to-an-image
renderer.scale = displayScale
// 3. Set the proposedViewSize you want
var size = ProposedViewSize.infinity
size.width = 400 // 4. set the width you want for better result
renderer.proposedSize = size
if let uiImage = renderer.uiImage {
return uiImage
}
return nil
}
}
让我知道你的效果如何!