Get part of the image taken with default camera

38 Views Asked by At

I tried in SwiftUI to create a custom overlay over default camera. I want to get the image only from that square and not the entire image that the camera gets.
I managed to get the image in the square, but it doesn't get the proper position and size and I can't figure it out why.

Here is before taking the capture: Photo before taking the picture
Here is after taking the picture - you can see that looks like the photo maybe moved? : Photo after taking the picture
Here is the final picture after saving it - also different position and size: Final photo

Here is the entire class for camera:

    import AVFoundation
    import SwiftUI
    import UIKit
    
    struct CameraView: UIViewControllerRepresentable {
        private let completionHandler: (UIImage?) -> Void
    
        init(completionHandler: @escaping (UIImage?) -> Void) {
            self.completionHandler = completionHandler
        }
    
        func makeUIViewController(context: UIViewControllerRepresentableContext<CameraView>) -> UIViewController {
            let cameraView = UIImagePickerController()
            cameraView.sourceType = .camera
            cameraView.delegate = context.coordinator
            cameraView.cameraOverlayView = self.guideForCameraOverlay()
    
            return cameraView
        }
    
        func guideForCameraOverlay() -> UIView {
            let guide = UIView(frame: UIScreen.main.fullScreenSquare())
            guide.backgroundColor = UIColor.clear
            guide.layer.borderWidth = 4
            guide.layer.borderColor = UIColor.red.cgColor
            guide.isUserInteractionEnabled = false
            return guide
        }
    
        func updateUIViewController(_: UIViewController, context _: UIViewControllerRepresentableContext<CameraView>) {}
    
        func makeCoordinator() -> Coordinator {
            Coordinator(completionHandler: self.completionHandler, cameraView: self)
        }
    
        class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
            var completionHandler: (UIImage?) -> Void
            var cameraView: CameraView
    
            init(completionHandler: @escaping (UIImage?) -> Void, cameraView: CameraView) {
                self.completionHandler = completionHandler
                self.cameraView = cameraView
            }
    
            func imagePickerController(_: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
                let image = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
                let croppedImage = self.cropToBounds(image: image)
                self.completionHandler(croppedImage)
            }
    
            func imagePickerControllerDidCancel(_: UIImagePickerController) {
                self.completionHandler(nil)
            }
    
            func cropToBounds(image: UIImage) -> UIImage {
                let contextImage = UIImage(cgImage: image.cgImage!)
                let contextSize: CGSize = contextImage.size
                let widthRatio = contextSize.height / UIScreen.main.bounds.size.height
                let heightRatio = contextSize.width / UIScreen.main.bounds.size.width
    
                let width = self.cameraView.guideForCameraOverlay().frame.size.width * widthRatio
                let height = self.cameraView.guideForCameraOverlay().frame.size.height * heightRatio
                let x = (contextSize.width / 2) - width / 2
                let y = (contextSize.height / 2) - height / 2
                let rect = CGRect(x: x, y: y, width: width, height: height)
    
                let imageRef: CGImage = contextImage.cgImage!.cropping(to: rect)!
                let image = UIImage(cgImage: imageRef, scale: 0, orientation: image.imageOrientation)
                return image
            }
        }
    }
    
    extension UIScreen {
        func fullScreenSquare() -> CGRect {
            var hw: CGFloat = 0
            var isLandscape = false
            if UIScreen.main.bounds.size.width < UIScreen.main.bounds.size.height {
                hw = UIScreen.main.bounds.size.width
            } else {
                isLandscape = true
                hw = UIScreen.main.bounds.size.height
            }
    
            var x: CGFloat = 0
            var y: CGFloat = 0
            if isLandscape {
                x = (UIScreen.main.bounds.size.width / 2) - (hw / 2)
            } else {
                y = (UIScreen.main.bounds.size.height / 2) - (hw / 2)
            }
            return CGRect(x: x, y: y, width: hw, height: hw / 2)
        }
    
        func isLandscape() -> Bool {
            UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height
        }
    }

struct ContentView: View {
    @State var cameraImage: UIImage?

    var body: some View {
        VStack {
            CameraView(completionHandler: { result in
                guard let image = result else {
                    return
                }
                self.cameraImage = image
            }).frame(maxWidth: .infinity, maxHeight: .infinity)
                .edgesIgnoringSafeArea(.all)
            
            if let scannedImage = cameraImage {
                Image(uiImage: scannedImage)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
            }
        }
    }
}
0

There are 0 best solutions below