SwiftUI视图保存为图片时内容缩小且出现异常黑背景问题
问题描述
视图在iPhone上显示时无黑色背景,但保存为图片后内容被缩小,上方出现黑色背景。已固定视图宽高,但问题仍存在。
显示效果
- iPhone上的显示效果:

- 导出为图片后的效果:

完整代码
import Foundation import Photos import SwiftUI import UIKit struct MyImageView: View { @State private var image: UIImage? @State var viewWidth = 0.0 @State var viewHeight = 0.0 @ObservedObject var imageSaver = ImageSaver() var imageUrl = "https://pic.netbian.com/uploads/allimg/160709/215802-1468072682c2c8.jpg" func calculateSize() { let maxWidth = UIScreen.main.bounds.width let maxHeight = UIScreen.main.bounds.height - 100 if maxHeight/maxWidth >= image!.size.height/image!.size.width { viewWidth = maxWidth viewHeight = maxWidth * image!.size.height/image!.size.width } else { viewHeight = maxHeight viewWidth = maxHeight/(image!.size.height/image!.size.width) } } private func loadImage() { URLSession.shared.dataTask(with: URL(string: imageUrl)!) { data, _, error in guard let data = data, error == nil else { print("Error loading image: \(error?.localizedDescription ?? "Unknown error")") return } if let uiImage = UIImage(data: data) { DispatchQueue.main.async { self.image = uiImage calculateSize() } } else { print("Failed to create image from data") } }.resume() } var myView: some View { ZStack { if self.image != nil { Image(uiImage: self.image!) .resizable() .frame(width: viewWidth, height: viewHeight) } Text("hello girl").font(.title).foregroundColor(.red) }.frame(width: viewWidth, height: viewHeight) .background(Color.black) .onAppear { loadImage() } } var body: some View { VStack { myView Button("save image") { myView.snapshot { image in if let image = image { imageSaver.saveSnapshot(compressedImage: image) } else { print("Failed to capture snapshot image.") } } } } } } extension View { func snapshot(completion: @escaping (UIImage?) -> Void) { let controller = UIHostingController(rootView: self) let view = controller.view view?.translatesAutoresizingMaskIntoConstraints = false let targetSize = controller.view.intrinsicContentSize view?.bounds = CGRect(origin: .zero, size: targetSize) view?.backgroundColor = .black view?.layoutIfNeeded() let renderer = UIGraphicsImageRenderer(size: targetSize) DispatchQueue.main.asyncAfter(deadline: .now() + 1) { let image: UIImage = renderer.image { _ in controller.view.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true) } controller.view.removeFromSuperview() completion(image) } } } class ImageSaver: NSObject, ObservableObject { @Published var isPresented = false @Published var title: String = "" @Published var message: String = "" func saveSnapshot(compressedImage: UIImage) { PHPhotoLibrary.requestAuthorization(for: .addOnly) { status in switch status { case .authorized: UIImageWriteToSavedPhotosAlbum( compressedImage, self, #selector(self.saveError), nil ) case .denied: self.title = NSLocalizedString("access_denied", comment: "") self.message = NSLocalizedString("snapshot_saver_1", comment: "") self.isPresented = true print("access denied") case .limited: self.title = NSLocalizedString("snapshot_saver_2", comment: "") self.message = NSLocalizedString("snapshot_saver_1", comment: "") self.isPresented = true print("limited access only") case .notDetermined: print("access not determined") case .restricted: print("access restricted") @unknown default: break } } } @objc func saveError(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) { isPresented = true if error != nil { title = "failed" message = "save image failed" } else { title = "success" message = "save image success" } } } #Preview { MyImageView() }
解决方案
问题根源有两点:
myView的onAppear在快照时不会触发,导致图片未加载、宽高参数为初始值0,快照用错误尺寸生成黑边和缩小内容。- 快照方法依赖
intrinsicContentSize获取尺寸,加上延迟1秒的做法不可靠,容易出现布局不匹配。
修复步骤
1. 迁移加载逻辑,脱离onAppear依赖
把图片加载和尺寸计算从myView移到主视图的onAppear,确保快照前已完成数据准备:
var myView: some View { ZStack { if self.image != nil { Image(uiImage: self.image!) .resizable() .frame(width: viewWidth, height: viewHeight) } Text("hello girl").font(.title).foregroundColor(.red) } .frame(width: viewWidth, height: viewHeight) .background(Color.black) } var body: some View { VStack { myView Button("save image") { // 后续修改内容见步骤3 } } .onAppear { loadImage() // 移到此处 } }
2. 优化快照方法,使用已知尺寸
直接传入已计算好的宽高作为快照尺寸,去掉不可靠的延迟:
extension View { func snapshot(size: CGSize, completion: @escaping (UIImage?) -> Void) { let controller = UIHostingController(rootView: self) let view = controller.view view?.frame = CGRect(origin: .zero, size: size) view?.backgroundColor = .black // 强制完成布局 view?.layoutIfNeeded() let renderer = UIGraphicsImageRenderer(size: size) let image = renderer.image { context in view?.drawHierarchy(in: view!.bounds, afterScreenUpdates: true) } controller.view.removeFromSuperview() completion(image) } }
3. 调用快照时传入正确尺寸
修改按钮点击事件,确保尺寸就绪后再生成快照:
@State private var isImageLoaded = false // 更新loadImage方法 private func loadImage() { guard !isImageLoaded else { return } URLSession.shared.dataTask(with: URL(string: imageUrl)!) { data, _, error in guard let data = data, error == nil else { print("Error loading image: \(error?.localizedDescription ?? "Unknown error")") return } if let uiImage = UIImage(data: data) { DispatchQueue.main.async { self.image = uiImage calculateSize() self.isImageLoaded = true } } else { print("Failed to create image from data") } }.resume() } // 更新按钮 Button("save image") { let size = CGSize(width: viewWidth, height: viewHeight) myView.snapshot(size: size) { image in if let image = image { imageSaver.saveSnapshot(compressedImage: image) } else { print("Failed to capture snapshot image.") } } } .disabled(!isImageLoaded)
修改后,快照尺寸与显示尺寸完全匹配,不会再出现黑边和内容缩小的问题。
内容的提问来源于stack exchange,提问作者jifei
相关产品推荐
相关产品推荐

