SwiftUI中UIViewRepresentable实现的图片缩放视图加载后空白及控制台异常日志问题
Hey there! Let's dive into why your ViewerFrameView is showing a blank screen and throwing those weird error logs.
What's Causing the Problem?
From the error messages and your code, the root issue is zero-value zoom scale calculations that break UIScrollView's internal assertions and matrix transformations:
- The
Must be called with non-zero scaleerror happens because when your image finishes loading, theImageScrollViewhasn't finished laying out yet. Itsbounds.sizeis still(0,0), so when you calculatexScale = boundsSize.width / imageSize.width, you end up with 0. - The
CGAffineTransformInvert: singular matrixerror is a side effect of that zero scale—you can't invert a matrix with a zero scaling factor, which UIScrollView tries to do under the hood.
Also, your ZoomableScrollView initializes scrollView = ImageScrollView() upfront with a zero frame. By the time the image loads, the scroll view might not have received its actual layout bounds from SwiftUI yet.
Fixes to Get Your Image Showing & Scrolling
Let's fix this step by step:
1. Add Safety Checks to Zoom Scale Calculation
Update setCurrentMaxandMinZoomScale to skip invalid calculations when sizes are zero, and set safe default scales instead:
func setCurrentMaxandMinZoomScale() { let boundsSize = self.bounds.size guard let imageSize = imageZoomView.image?.size, imageSize.width > 0, imageSize.height > 0, boundsSize.width > 0, boundsSize.height > 0 else { // Fallback to safe defaults if sizes are invalid self.minimumZoomScale = 1.0 self.maximumZoomScale = 3.0 return } let xScale = boundsSize.width / imageSize.width let yScale = boundsSize.height / imageSize.height let minScale = min(xScale, yScale) var maxScale: CGFloat = 1.0 if minScale < 0.1 { maxScale = 0.3 } else if minScale >= 0.1 && minScale < 0.5 { maxScale = 0.7 } else { maxScale = max(1.0, minScale) } self.minimumZoomScale = minScale self.maximumZoomScale = maxScale }
2. Explicitly Set ImageZoomView's Frame
When you assign the image to imageZoomView, make sure its frame matches the image's size so bounds calculations are correct:
func set(image: UIImage) { imageZoomView?.removeFromSuperview() imageZoomView = UIImageView(image: image) // Force the image view to match the image's dimensions imageZoomView.frame = CGRect(origin: .zero, size: image.size) self.addSubview(imageZoomView) configurateFor(imageSize: image.size) }
3. Recalculate Scales After Layout
Update layoutSubviews to recheck zoom scales every time the scroll view's bounds change (like after SwiftUI finishes laying it out):
override func layoutSubviews() { super.layoutSubviews() // Refresh scales now that we have valid bounds setCurrentMaxandMinZoomScale() centerImage() }
4. Fix ZoomableScrollView's Initialization
Don't create the scroll view upfront—make it in makeUIView instead, and only update the image if it actually changes:
struct ZoomableScrollView: UIViewRepresentable { @Binding var image: UIImage func makeUIView(context: Context) -> ImageScrollView { let scrollView = ImageScrollView() // Set initial image if it's already available if !image.size.equalTo(.zero) { scrollView.set(image: image) } return scrollView } func updateUIView(_ uiView: ImageScrollView, context: Context) { // Avoid redundant updates if the image hasn't changed if uiView.imageZoomView?.image != image { uiView.set(image: image) } } }
5. Guard Against Empty Images in ViewerFrameView
Only update the local image state when the view model's image is valid:
.onChange(of: viewModel.image, perform: { newValue in guard !newValue.size.equalTo(.zero) else { return } image = newValue print(newValue.size) })
Test It Out
After making these changes, your image should load correctly without the error logs. The scroll view will wait until it has valid bounds before calculating zoom scales, and all matrix operations will use valid non-zero values.
内容的提问来源于stack exchange,提问作者Iskander Nizamov

