SwiftUI将View转换为Image时背景与贴纸尺寸位置错乱问题求解
问题根因排查
- 第一个核心问题:截图时传入的尺寸错误
你调用takeScreenshot时传入的size是原图self.image!.size,但界面上实际显示的SnapshotView是经过屏幕适配缩放后的尺寸,你存储的贴纸位置、缩放参数、特效位置都是基于屏幕上显示的视图尺寸计算得到的,传入原图尺寸生成新的SnapshotView时,GeometryReader拿到的宽高和界面显示时完全不一致,所有基于尺寸计算的布局都会错乱。 - 第二个问题:UIHostingController安全区域影响
你封装的截图扩展中,没有忽略安全区域,UIHostingController默认会适配安全边距,导致生成的视图整体偏移,也会影响布局位置。 - 第三个问题:新生成的SnapshotView和界面上显示的视图上下文不一致
你在按钮点击时重新初始化了一个SnapshotView,而不是直接对当前界面上已经渲染完成、布局正确的SnapshotView截图,新实例的布局参数和实际显示的视图存在差异。
修复方案
1. 优先直接对当前显示的视图截图
如果不需要导出高于屏幕分辨率的图片,直接对当前界面上已经渲染好的SnapshotView实例截图,不要重新初始化新实例,给界面上的SnapshotView加上唯一标识,拿到它对应的UIView后直接调用renderedImage即可。
2. 修复截图扩展的安全区域问题
修改takeScreenshot方法,给传入的View加上忽略安全区域的修饰符:
extension View { func takeScreenshot(origin: CGPoint, size: CGSize) -> UIImage { let window = UIWindow(frame: CGRect(origin: origin, size: size)) // 忽略安全区域,避免布局偏移 let hosting = UIHostingController(rootView: self.edgesIgnoringSafeArea(.all)) hosting.view.frame = window.frame hosting.view.backgroundColor = .clear window.addSubview(hosting.view) window.makeKeyAndVisible() return hosting.view.renderedImage } }
3. 正确传入截图尺寸
如果需要导出原图分辨率的图片,需要先计算界面显示的视图尺寸和原图尺寸的缩放比例,把所有贴纸、特效的位置、缩放参数都乘以这个比例之后再传入新的SnapshotView,同时调用截图方法时,传入的size应该和你新SnapshotView布局用到的尺寸一致,不要混用屏幕尺寸和原图尺寸。
常规导出屏幕适配尺寸的调用代码修改示例:
Button(action: { // 拿到当前界面上SnapshotView的实际显示尺寸,不是原图尺寸 let displaySize = geo.size let viewSnapshot = SnapshotView(image: self.$image, isActive: self.$isActive, isEffectAvailable: self.$isEffectAvailable, sliderValueBlur: self.$sliderValueBlur, effectData: self._effectData, eraseModel: self._eraseModel, stickerData: self._stickerData) // 传入实际显示的尺寸,不要传image.size let saveImage = viewSnapshot.takeScreenshot(origin: .zero, size: displaySize) UIImageWriteToSavedPhotosAlbum(saveImage, nil, nil, nil) }, label: { Text("SAVE") .foregroundColor(.white) })
内容的提问来源于stack exchange,提问作者Riswan Gani Padilah
相关产品推荐
相关产品推荐

