3

我尝试使用 HWSUIImage中的代码从 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)
            }
        }
    }
}
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 上解决这个问题?

4

2 回答 2

10

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

因此,我使用view 修饰符修改了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)
        }
    }
}
于 2021-11-03T04:07:59.650 回答
1

我还注意到 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)嵌入到添加NavigationView了视图修饰符的 a 时才有效.statusBar(hidden: true)。请参阅下面的示例代码以重现结果:

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)
            })
        }
    }
}
于 2021-10-15T02:56:58.697 回答