iosswiftswiftuisnapshot

Weird reaction of the image on snapshot creation (SwiftUI)


I'm working on an iOS app, and I have a problem with the scannedImage in my SwiftUI view. I'm using kontiki's solution to capture a snapshot of the view, and it works fine. Here's the screen of how does it look like. However, after the snapshot is taken, the scannedImage changes its frame size, it's resizing to the bottom of the view, and this behavior only happens once. Also it's only happening at the bottom, as you can see in the second link, it's successfully cropping it if I'm positing my signature on left and right places and pressing green. I'm not sure why this is happening, I've tried to catch the time when scannedImage size is changing, but no luck. Hope you can help with it. Here are the relevant parts of my code:

// Main part that capturing the snapshot
private func captureSnapshot(rect: CGRect) -> UIImage? {
    var result: UIImage?
    if let scene = UIApplication.shared.connectedScenes.first(
        where: { $0.activationState == .foregroundActive }
    ) as? UIWindowScene {
        result = scene.windows[0].rootViewController?.view.asImage(rect: rect)
    }
    return result
}

extension UIView {
    func asImage(rect: CGRect) -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: rect)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}

// View itself 
    var body: some View {
        VStack {
            
            GeometryReader { geometry in

                let magnificationGesture = MagnificationGesture()
                    .onChanged{ gesture in
                        scaleAnchor = .center
                        scale = lastScale * gesture
                    }
                    .onEnded { _ in
                        fixOffsetAndScale(geometry: geometry)
                    }
                
                let dragGesture = DragGesture()
                    .onChanged { gesture in
                        var newOffset = lastOffset
                        newOffset.width += gesture.translation.width
                        newOffset.height += gesture.translation.height
                        offset = newOffset
                    }
                    .onEnded { _ in
                        fixOffsetAndScale(geometry: geometry)
                    }
                                    
                Image(uiImage: scannedImage)
                    .resizable()
                    .scaledToFit()
                    .border(.gray, width: 2)
                    .scaleEffect(scale, anchor: scaleAnchor)
                    .offset(offset)
                    .gesture(dragGesture)
                    .gesture(magnificationGesture)
                    .overlay (
                        VStack {
                            ZStack {
                                ZStack(alignment: .bottomTrailing) {
                                    
                                    Rectangle()
                                        .stroke(style: StrokeStyle(lineWidth: 2, dash: [5]))
                                        .fill(.blue)
                                        .opacity(opacity)
                                    
                                    Rectangle()
                                        .fill(.clear)
                                    
                                    Image(uiImage: signImage)
                                        .resizable()
                                        .scaledToFit()
                                    
                                    VStack {
                                        HStack {
                                            Spacer()
                                            Circle()
                                                .fill(Color.green)
                                                .frame(width: 25, height: 25)
                                                .onTapGesture {
                                                    
                                                    opacity = 0.0
                                                    
                                                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: {
                                                        
                                                        scannedImage = captureSnapshot(rect: geometry.frame(in: .global))!
                                                                                                                
                                                        opacity = 1.0
                                                    })
                                                }
                                        }
                                        Spacer()
                                    }
                                    .opacity(opacity)
                                    .zIndex(2)

                                }
                                .frame(width: width, height: height)
                                
                            }
                            
                        }
                            .frame(maxWidth: width, maxHeight: height, alignment: .center)
                            .position(location)
                            .gesture(
                                simpleDrag.simultaneously(with: fingerDrag)
                            )
                    )
                
                Spacer()
//                    .frame(width: geometry.size.width, height: geometry.size.height)
            }
            
        }
        .onChange(of: scannedImage.size.height, perform: { newValue in
            print("height changed, but why?!")
        })
        .background(Color.black.opacity(0.3).ignoresSafeArea(.all))

    }

My guess is that the problem is with captureSnapshot, It's getting more, then It's supposed to. Hope you can help with it, thank you.

EDIT: The Spacer() is not the problem.


Solution

  • There are two likely reasons why scannedImage is changing size:

    1. You are displaying scannedImage inside a GeometryReader and using the frame of the GeometryReader to capture a snapshot. This snapshot is then used to replace the scannedImage. But the frame of the GeometryReader also includes a Spacer. Try moving the Spacer to outside the GeometryReader.
    2. The image is being scaledToFit and you are also applying a scaleFactor to it. But the snapshot is of the scaled image. So I would not even expect it to be the same size. It will only stay the same size if it exactly fits the frame to start with and if you do not apply any more scaling (that is, if scale is set to 1).