SwiftUI 中无法对添加模糊(blur)效果视图截图的初步解决

概览

在 万物皆可截图:SwiftUI 中任意视图(包括List和ScrollView)截图的通用实现 这篇博文里,我们讨论了在 SwiftUI 中对任意视图截图的一般方法。

不过,经码友反应这些方法对添加模糊(blur)效果的视图好像不太灵了。这里,就让我们看看一些可能的应变(Workaround)之道。

在本篇博文中,您将学到如下内容:

  1. "模糊"截图"不模糊"
  2. SwiftUI 原生解决方案
  3. UIKit 中的解决方案
  4. 美中不足

我们废话少叙直奔主题,Let's go!!!:)


1. "模糊"截图"不模糊"

在 SwiftUI 中,我们可以轻松为任意视图添加模糊效果(blur):

swift 复制代码
struct BlurDemoView: View {
    var body: some View {
        VStack {
            Text("This is some text.")
                .padding()
                          
            Text("This is some blurry text.")
                .font(.largeTitle)
                .blur(radius: 2.0)
            
            Text("大熊猫侯佩😀")
                .font(.largeTitle)
                .padding()
                
        }
    }
}

如果用顶部链接指向博文里的截图方法对上面的 BlurDemoView 视图进行截图,将会是如下结果:

可以看到:原本模糊的效果在截图中变得荡然无存了。

检视 Xcode 中的视图渲染树可以发现,文本对应的模糊效果(CGDrawView)和文本视图本身是分开的,这无疑增加了对其渲染结果截取的难度。

2. SwiftUI 原生解决方案

如果我们要截取的内容比较简单,可以用 SwiftUI 原生的 ImageRenderer 来解决:

swift 复制代码
let renderer = ImageRenderer(content: content)

if let cgImage = renderer.cgImage {
    image = UIImage(cgImage: cgImage)
}

运行效果如下:

但是这样做的前提是,被截图内容不能有和 ImageRenderer "犯冲"(不兼容)的视图,比如 TextField:

3. UIKit 中的解决方案

为了能够更好的解决这一问题,我们需要将被截取的视图放到渲染继承层级中来做全盘考虑。

这意味着我们不得不重新回到 UIKit 的世界里一展拳脚:

swift 复制代码
extension View {
    func snapshot2(scale: CGFloat) -> UIImage {

        let controller = UIHostingController(rootView: self)
        let view = controller.view!
    
        let format = UIGraphicsImageRendererFormat()
        format.scale = scale
        // 可以开启或关闭背景透明
        //format.opaque = true
            
        var targetSize = controller.view.intrinsicContentSize
        // 根据设备顶部空隙修正渲染视图的 y 坐标
        let topEdgeInset = UIApplication.shared.windows.first { $0.isKeyWindow }!.safeAreaInsets.top
        view.bounds = CGRect(origin: .init(x: 0, y: topEdgeInset / 2), size: targetSize)
        view.backgroundColor = .clear
        
        let renderer = UIGraphicsImageRenderer(bounds: view.bounds, format: format)
        
        return renderer.image { rendererContext in
            view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
            
        }
    }
}

我们可以这样使用上面的方法:

swift 复制代码
image = content.snapshot2(scale: 3)

运行大概是下面这种效果:

4. 美中不足

虽然貌似以上方法解决了本文开头提出的问题,不过这只是权宜之计。在具体应用中还是需要做进一步的调整。

比如,目前的实现对于下面一些场景会变得"捉襟见肘":

  • 对于"超长"视图的截图会截取到大段空白内容;
  • 对于带换行的"模糊"文本会截取不全;
  • 截取结果需要调整其 y 坐标,否则也会截取不全;

我们猜测上面有些情况可能是 Apple 做了硬性限制(比如第一个):

而另一些情况可能是 UIKit 和 SwiftUI 搭配还不能做到 100% 的天衣无缝。

不管如何,对于所有这些问题如有机会我们会在后续博文中尝试给出一个更加完美的解决方案,也欢迎小伙伴们一起加入到讨论中来。

总结

在本篇博文中,我们介绍了在 SwiftUI 中无法对添加模糊(blur)效果视图截图问题的一些解决方法,并讨论了目前这些方法的一些不足之处。

感谢观赏,再会!8-)

相关推荐
传奇开心果编程8 小时前
【SwiftUI提高练中学】第9课 安全与隐私
swiftui·移动开发·swift·编程语言理论·开发范式·编程框架·声明式ui
CocoaKier9 小时前
苹果商店详情顶部头图已面向所有开发者开放!
ios·apple
茶底世界之下19 小时前
动态切换 nearest / linear 时,为什么不能只替换 sampler
ios·swift
应用市场1 天前
把旧 Pixel 变成相册备份中转站(上):Mac 到安卓的照片传输工具设计——流式上传、sha256 校验、adb forward 与 Bonjour
android·macos·adb·kotlin·swift
传奇开心果编程1 天前
【声明式UI开发实用技术学与练】第8课 状态提升与下放
学习·flutter·react native·ui·swiftui·composer
东坡肘子1 天前
Swift Server,又多了一个 Google -- 肘子的 Swift 周报 #156
人工智能·swiftui·swift
打工仔折腾 AI2 天前
从零写一个CAD 05:以鼠标为中心的滚轮缩放,招法能复用但顺序不能反
开发语言·后端·python·性能优化·计算机外设·swift·ai agent 实战
传奇开心果编程2 天前
【现代声明式UI学与练】第4课 列表渲染与 key——如何高效渲染列表、key 的作用、列表重排时的状态保持
学习·flutter·react native·ui·swiftui·android jetpack
茶底世界之下2 天前
视频预览切换为何会闪回旧帧:用 generation + mode identity 管住异步回调
ios·swift
茶底世界之下2 天前
类型擦除之后,Metal 滤镜组合为什么不能只执行一个 Filter?
ios·swift