Search code examples
iosxcodecameraswiftuiuiimagepickercontroller

SwiftUI Position Overlay on Camera Preview


I'm trying to add a target-like overlay to the photo preview in an app that uses portrait on some devices and landscape on others. I can manually set coordinates but have not been able to get the correct coordinates of the frame of the camera preview.

This is a SwiftUI application, so I am doing the camera work in a UIViewControllerRepresentable. Here's what I have and obviously, the target circle is always wrong when the device and/or orientation change. I don't seem to be able to capture the frame of the modal view where the camera preview exists. I would settle to be able to specify the frame and location of the camera preview on the modal view.

struct CaptureImageView: View {
    @Binding var isShown: Bool
    @Binding var image: Image?
    @Binding var newUIImage: UIImage?
    @Binding var showSaveButton: Bool

    func makeCoordinator() -> Coordinator {
        return Coordinator(isShown: $isShown, image: $newUIImage, showSaveButton: $showSaveButton)
    }
}

extension CaptureImageView: UIViewControllerRepresentable {

    func makeUIViewController(context: UIViewControllerRepresentableContext<CaptureImageView>) -> UIImagePickerController {

        let vc = UIImagePickerController()

        if UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera) {
            vc.sourceType = .camera
            vc.allowsEditing = true
            vc.delegate = context.coordinator

            let screenSize: CGRect = UIScreen.main.bounds
            let screenWidth = screenSize.width
            let screenHeight = screenSize.height

            vc.cameraOverlayView = CircleView(frame: CGRect(x: (screenWidth / 2) - 50, y: (screenWidth / 2) + 25, width: 100, height: 100))

            return vc
        }
        return UIImagePickerController()
    }

    func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<CaptureImageView>) {
    }
}

Here is the idea - but the target always needs to be centered:

enter image description here

And the target file:

class CircleView: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = UIColor.clear
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func draw(_ rect: CGRect) {
        if let context = UIGraphicsGetCurrentContext() {
            context.setLineWidth(3.0)
            UIColor.red.set()

            let center = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
            let radius = (frame.size.width - 10) / 2

            context.addArc(center: center, radius: radius, startAngle: 0.0, endAngle: .pi * 2.0, clockwise: true)
            context.strokePath()
        }
    }
}

Any guidance would be appreciated. Xcode Version 11.3.1 (11C504)


Solution

  • While I would still like to find an answer for the way to get the frame of the camera - my goal could be mostly achieved by creating a circle view in a ZStack placing the camera view behind my circle view in SwiftUI like this:

    if showCaptureImageView {
        ZStack {
            CaptureImageView(isShown: $showCaptureImageView, image: $myImage, newUIImage: $newUIImage, showSaveButton: $showSaveButton)
            
            Circle()
                .stroke(Color.red, style: StrokeStyle(lineWidth: 10 ))
                .frame(width: 100, height: 100)
                .offset(CGSize(width: 0, height: -50.0))
        }
    }//if show