【问题标题】:Snapshot of SwiftUI view is partially cut offSwiftUI 视图的快照被部分截断
【发布时间】:2021-11-01 04:20:34
【问题描述】:

我尝试使用来自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 上解决这个问题?

【问题讨论】:

  • 那是因为你在那里使用ZStack!图像将自己置于所有视图之上!
  • @swiftPunk 我知道它位于所有视图之上。但是为什么快照从正确的位置偏移?红色矩形勾勒出它应该填充的位置。
  • 对我来说很好用(iPhone 12、iOS 14.5)。也许试试.resizable().aspectRatio(contentMode: .fit)
  • @George:对我来说也很好用!看:i.stack.imgur.com/xYrlR.png
  • 我刚刚向 Apple 发送反馈问题 ID:FB9666959

标签: ios swift swiftui uiimage ios15


【解决方案1】:

我最近也注意到了这个问题。我在不同的模拟器(例如,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)
        }
    }
}

【讨论】:

  • 很好的解决方案,它对我有用!我也保留了@claes 解决方案的 UIGraphicsImageRendererFormat 部分。 .edgesIgnoringSafeArea([.top]) 视图修饰符起到了作用。
【解决方案2】:

我还注意到 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)嵌入到添加了视图修饰符 .statusBar(hidden: true)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)
            })
        }
    }
}

【讨论】:

  • 我无法像为您那样工作。生成的图像仍然移动并被部分截断。我尝试使用模拟器 iOS 15.0 和运行 15.1 beta 4 的物理 iPad。两者似乎都与以前没有任何变化,除非我遗漏了您为使其正常工作所做的其他事情。
  • @Rob,感谢您的测试。嗯..奇怪。重新测试并在可能的应用程序中工作,但让我看看它是否与我的 SwiftUI 视图有关
  • @Rob,我发现了为什么它在我的应用程序中运行的问题。请参阅上面的编辑。
  • 我仍然无法重现工作循环。也许我的 swiftUI 视图更复杂。问题是这一切在 iOS 15.x 之前都可以正常工作。
  • @Rob,你试过上面的示例代码吗?如果是这样,它对你有用吗?如果没有,您能否提供一些示例代码来解决我可以查看的问题?
猜你喜欢
  • 2023-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-19
  • 2017-08-11
  • 1970-01-01
  • 2020-04-18
相关资源
最近更新 更多