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)
}
}
}
}