SwiftUI 视图的快照被部分截断

Geo*_*e_E 8 uiimage ios swift swiftui ios15

我尝试UIImage使用 HWS 中的代码从 SwiftUI 视图创建快照:如何将 SwiftUI 视图转换为图像

我得到以下结果,这显然是不正确的,因为图像被截断了。

结果

代码:

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)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

看起来作为快照的原始视图比应有的位置要低,但我不确定。我该如何解决?


编辑

我们发现这个问题不会出现在 iOS 14 上,只会出现在 iOS 15 上。所以问题是……如何在 iOS 15 上解决这个问题?

小智 24

我最近也注意到这个问题。我在不同的模拟器(例如 iPhone 8 和 iPhone 13 Pro)上进行了测试,并意识到偏移量似乎始终是状态栏高度的一半。所以我怀疑当你调用 时drawHierarchy(in:afterScreenUpdates:),内部 SwiftUI 总是考虑安全区域插入。

因此,我使用视图修饰符修改了扩展snapshot()中的函数,并且它起作用了:ViewedgesIgnoringSafeArea(_:)

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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 很好的解决方案,它对我有用!我也保留了 @claes 解决方案的 UIGraphicsImageRendererFormat 部分。.edgesIgnoringSafeArea([.top]) 视图修饰符就达到了目的。 (2认同)

小智 5

避免额外空白所需的主要事情是额外的安全区域插入应该减去原始的安全区域插入

这是我当前的实现,可渲染不在屏幕上的视图。使用运行 iOS 16.2 Beta 的 iPhone 7、iOS 15.7 和 iPhone 13 以及运行 iOS 16.2 Beta 的 iPad mini 6 进行测试。

extension View {
    func asImage() -> UIImage {
        let controller = UIHostingController(rootView: self.edgesIgnoringSafeArea(.all))
        
        let scenes = UIApplication.shared.connectedScenes 
        let windowScene = scenes.first as? UIWindowScene 
        let window = windowScene?.windows.first
        
        window?.rootViewController?.view.addSubview(controller.view)
        
        controller.view.frame = CGRect(x: 0, y: CGFloat(Int.max), width: 1, height: 1)
        controller.additionalSafeAreaInsets = UIEdgeInsets(top: -controller.view.safeAreaInsets.top, left: -controller.view.safeAreaInsets.left, bottom: -controller.view.safeAreaInsets.bottom, right: -controller.view.safeAreaInsets.right)
        
        let targetSize = controller.view.intrinsicContentSize      
        controller.view.bounds = CGRect(origin: .zero, size: targetSize)
        controller.view.sizeToFit()
        
        let image = controller.view.asImage()
        controller.view.removeFromSuperview()
        
        return image
    }
}
extension UIView {
    func asImage() -> UIImage  {
   
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)