SwiftUI 视图的快照被部分截断
Snapshot of SwiftUI view is partially cut off
我尝试使用 HWS: How to convert a SwiftUI view to an image 中的代码从 SwiftUI 视图(快照)创建 UIImage
。
我得到以下结果,这显然是不正确的,因为图像被截断了。
代码:
struct ContentView: View {
@State private var savedImage: UIImage?
var textView: some View {
Text("Hello, SwiftUI")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.clipShape(Capsule())
}
var body: some View {
ZStack {
VStack(spacing: 100) {
textView
Button("Save to image") {
savedImage = textView.snapshot()
}
}
if let savedImage = savedImage {
Image(uiImage: savedImage)
.border(Color.red)
}
}
}
}
extension View {
func snapshot() -> 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)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
看起来作为快照的原始视图低于应有的位置,但我不确定。我该如何解决这个问题?
编辑
我们发现 iOS 14 没有出现这个问题,只有 iOS 15 才出现。所以问题是......如何解决 iOS 15 的问题?
我还在 iOS15 中注意到了这种恼人的行为,并认为我找到了解决方法,直到 iOS15 和 drawHierarchy(in: afterScreenUpdates:)
的问题得到解决。
这个扩展在我的应用程序中对我有效(在模拟器 iOS15.0 & iOS14.5 和 iPhone XS Max iOS15.0.1 上测试),你如果您需要更高分辨率的图像,可以将比例设置为高于 1:
extension View {
func snapshotiOS15() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let format = UIGraphicsImageRendererFormat()
format.scale = 1
format.opaque = true
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let window = UIWindow(frame: view!.bounds)
window.addSubview(controller.view)
window.makeKeyAndVisible()
let renderer = UIGraphicsImageRenderer(bounds: view!.bounds, format: format)
return renderer.image { rendererContext in
view?.layer.render(in: rendererContext.cgContext)
}
}
}
编辑
事实证明,只有当 view
(将保存为 UIImage
)嵌入到视图修饰符 [=16] 的 NavigationView
中时,上述解决方案才有效=] 添加。请参阅下面的示例代码以重现结果:
import SwiftUI
struct StartView: View {
var body: some View {
NavigationView {
TestView()
}.statusBar(hidden: true)
}
}
struct TestView: View {
var testView: some View {
Text("Hello, World!")
.font(.system(size: 42.0))
.foregroundColor(.white)
.frame(width: 200, height: 200, alignment: .center)
.background(Color.blue)
}
var body: some View {
VStack {
testView
Button(action: {
let imageiOS15 = testView.snapshotiOS15()
}, label: {
Text("Take snapshot")
.font(.headline)
})
}
}
}
我最近也注意到了这个问题。我在不同的模拟器(例如 iPhone 8 和 iPhone 13 Pro)上进行了测试,发现偏移似乎总是状态栏高度的一半。所以我怀疑当你调用 drawHierarchy(in:afterScreenUpdates:)
时,SwiftUI 内部总是考虑安全区域插入。
因此,我使用 edgesIgnoringSafeArea(_:)
视图修饰符修改了您的 View
扩展中的 snapshot()
函数,它起作用了:
extension View {
func snapshot() -> UIImage {
let controller = UIHostingController(rootView: self.edgesIgnoringSafeArea(.all))
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
我尝试使用 HWS: How to convert a SwiftUI view to an image 中的代码从 SwiftUI 视图(快照)创建 UIImage
。
我得到以下结果,这显然是不正确的,因为图像被截断了。
代码:
struct ContentView: View {
@State private var savedImage: UIImage?
var textView: some View {
Text("Hello, SwiftUI")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.clipShape(Capsule())
}
var body: some View {
ZStack {
VStack(spacing: 100) {
textView
Button("Save to image") {
savedImage = textView.snapshot()
}
}
if let savedImage = savedImage {
Image(uiImage: savedImage)
.border(Color.red)
}
}
}
}
extension View {
func snapshot() -> 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)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
看起来作为快照的原始视图低于应有的位置,但我不确定。我该如何解决这个问题?
编辑
我们发现 iOS 14 没有出现这个问题,只有 iOS 15 才出现。所以问题是......如何解决 iOS 15 的问题?
我还在 iOS15 中注意到了这种恼人的行为,并认为我找到了解决方法,直到 iOS15 和 drawHierarchy(in: afterScreenUpdates:)
的问题得到解决。
这个扩展在我的应用程序中对我有效(在模拟器 iOS15.0 & iOS14.5 和 iPhone XS Max iOS15.0.1 上测试),你如果您需要更高分辨率的图像,可以将比例设置为高于 1:
extension View {
func snapshotiOS15() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let format = UIGraphicsImageRendererFormat()
format.scale = 1
format.opaque = true
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let window = UIWindow(frame: view!.bounds)
window.addSubview(controller.view)
window.makeKeyAndVisible()
let renderer = UIGraphicsImageRenderer(bounds: view!.bounds, format: format)
return renderer.image { rendererContext in
view?.layer.render(in: rendererContext.cgContext)
}
}
}
编辑
事实证明,只有当 view
(将保存为 UIImage
)嵌入到视图修饰符 [=16] 的 NavigationView
中时,上述解决方案才有效=] 添加。请参阅下面的示例代码以重现结果:
import SwiftUI
struct StartView: View {
var body: some View {
NavigationView {
TestView()
}.statusBar(hidden: true)
}
}
struct TestView: View {
var testView: some View {
Text("Hello, World!")
.font(.system(size: 42.0))
.foregroundColor(.white)
.frame(width: 200, height: 200, alignment: .center)
.background(Color.blue)
}
var body: some View {
VStack {
testView
Button(action: {
let imageiOS15 = testView.snapshotiOS15()
}, label: {
Text("Take snapshot")
.font(.headline)
})
}
}
}
我最近也注意到了这个问题。我在不同的模拟器(例如 iPhone 8 和 iPhone 13 Pro)上进行了测试,发现偏移似乎总是状态栏高度的一半。所以我怀疑当你调用 drawHierarchy(in:afterScreenUpdates:)
时,SwiftUI 内部总是考虑安全区域插入。
因此,我使用 edgesIgnoringSafeArea(_:)
视图修饰符修改了您的 View
扩展中的 snapshot()
函数,它起作用了:
extension View {
func snapshot() -> UIImage {
let controller = UIHostingController(rootView: self.edgesIgnoringSafeArea(.all))
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}