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.
There are two likely reasons why scannedImage
is changing size:
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
.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).