Search code examples
swiftuiviewcameraavfoundationavcam

AVCam: Building a Camera App - Draw a grid on top of PreviewView


I've downloaded AVCam app sample from Apple. I added a custom view "GridView" on top of PreviewView, but i would like to size my GridView in order draw my grid on top of the captured image.

How can i get the bound of the capture image?

thanks

enter image description here


Solution

  • You can do something like this:

    1- Create a gird

    
    import Foundation
    import UIKit
    
    class GridView: UIView
    {
        private var path = UIBezierPath()
        fileprivate var gridWidthMultiple: CGFloat
        {
            return 4
        }
        fileprivate var gridHeightMultiple : CGFloat
        {
            return 4
        }
    
        fileprivate var gridWidth: CGFloat
        {
            return bounds.width/CGFloat(gridWidthMultiple)
        }
    
        fileprivate var gridHeight: CGFloat
        {
            return bounds.height/CGFloat(gridHeightMultiple)
        }
    
        fileprivate var gridCenter: CGPoint {
            return CGPoint(x: bounds.midX, y: bounds.midY)
        }
    
        fileprivate func drawGrid()
        {
            path = UIBezierPath()
            path.lineWidth = 2.0
    
            for index in 1...Int(gridWidthMultiple) - 1
            {
                let start = CGPoint(x: CGFloat(index) * gridWidth, y: 0)
                let end = CGPoint(x: CGFloat(index) * gridWidth, y:bounds.height)
                path.move(to: start)
                path.addLine(to: end)
            }
    
            for index in 1...Int(gridHeightMultiple) - 1 {
                let start = CGPoint(x: 0, y: CGFloat(index) * gridHeight)
                let end = CGPoint(x: bounds.width, y: CGFloat(index) * gridHeight)
                path.move(to: start)
                path.addLine(to: end)
            }
    
            //Close the path.
            path.close()
    
        }
    
        override func draw(_ rect: CGRect)
        {
            drawGrid()
    
            // Specify a border (stroke) color.
            UIColor.systemYellow.setStroke()
            path.stroke()
        }
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            backgroundColor = .clear
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
    
    

    2- In ViewController

    define a grid var:

    var gridView: GridView!
    
    func addGrid() {
        gridView = GridView(frame: previewView.bounds)
        previewView.addSubview(gridView)
    }
          
    

    Add call addGrid in viewdidLoad or anywhere that suits you.

    Then we need to update the frame of the gridView based on the displayed camera live view.

    func updateGrid() {
        DispatchQueue.main.async {
            let p1 = self.previewView.videoPreviewLayer.layerPointConverted(fromCaptureDevicePoint: .zero)
            let p2 = self.previewView.videoPreviewLayer.layerPointConverted(fromCaptureDevicePoint: CGPoint(x: 1, y: 1))
                
            self.gridView.frame = CGRect(x: 0, y: p1.y, width: p1.x, height: p2.y - p1.y)
                
        }
    }
    

    You call call updateGrid wherever best suits in your case. In here I called it in viewWillAppear after the session starts running:

    override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            
            sessionQueue.async {
                switch self.setupResult {
                case .success:
                    // Only setup observers and start the session if setup succeeded.
                    self.addObservers()
                    self.session.startRunning()
                    self.isSessionRunning = self.session.isRunning
                    
                    self.updateGrid()
                    
                    
                case .notAuthorized:
                    DispatchQueue.main.async {
                        let changePrivacySetting = "AVCam doesn't have permission to use the camera, please change privacy settings"
                        let message = NSLocalizedString(changePrivacySetting, comment: "Alert message when the user has denied access to the camera")
                        let alertController = UIAlertController(title: "AVCam", message: message, preferredStyle: .alert)
                        
                        alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"),
                                                                style: .cancel,
                                                                handler: nil))
                        
                        alertController.addAction(UIAlertAction(title: NSLocalizedString("Settings", comment: "Alert button to open Settings"),
                                                                style: .`default`,
                                                                handler: { _ in
                                                                    UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!,
                                                                                              options: [:],
                                                                                              completionHandler: nil)
                        }))
                        
                        self.present(alertController, animated: true, completion: nil)
                    }
                    
                case .configurationFailed:
                    DispatchQueue.main.async {
                        let alertMsg = "Alert message when something goes wrong during capture session configuration"
                        let message = NSLocalizedString("Unable to capture media", comment: alertMsg)
                        let alertController = UIAlertController(title: "AVCam", message: message, preferredStyle: .alert)
                        
                        alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"),
                                                                style: .cancel,
                                                                handler: nil))
                        
                        self.present(alertController, animated: true, completion: nil)
                    }
                }
            }
        }
        
    

    Output

    enter image description here