Adding objects over camera view in app (Swift 3) not under camera view?

Postmaster picture Postmaster · Mar 3, 2017 · Viewed 7.3k times · Source

I have an overlay over the camera view made up of red circles, stored in the Assets.xcasset ImgOverlay placeholders, and the cameraview (preview) appears behind - or underneath - the overlay. That's fine. As it should be. When I run the app on the iPhone, the overlay appears as it should. See Img.1 But I also have a blue circle being drawn, that is Underneath the Camera View, and only appears if the camera view is missing. That is, Turned off, or run on a simulator. See Img.2 below.

Image 1. red circles over camera view, in iPhone

Image 1. red circles over camera view, in iPhone

Image 2. red circles including drawn blue circle in Simulator Image 2. red circles including drawn blue circle in Simulator
The code I have so far is here. I'm doing something wrong, but can't see it. I need the blue circle(s) visible in the iPhone, not just in the Simulator. I'm trying to draw all the circles, so I can abandon the red circles which are in a image.png type file. I prefer to draw the circles for better accuracy across any device, and display them instead of the red circles, and then save the composite image, of the circles and the camera view. I haven't yet managed to combine the images when saved either, but getting the blue circle visible on the iPhone is the first step ... So it's almost fully working. I can't see what I'm doing wrong?

ViewController.swift

import UIKit
import AVFoundation
import Foundation

class ViewController: UIViewController {

@IBOutlet weak var navigationBar: UINavigationBar!
@IBOutlet weak var imgOverlay: UIImageView!
@IBOutlet weak var btnCapture: UIButton!

@IBOutlet weak var shapeLayer: UIView!

let captureSession = AVCaptureSession()
let stillImageOutput = AVCaptureStillImageOutput()
var previewLayer : AVCaptureVideoPreviewLayer?

//var shapeLayer : CALayer?

// If we find a device we'll store it here for later use
var captureDevice : AVCaptureDevice?

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    //=======================

    let midX = self.view.bounds.midX
    let midY = self.view.bounds.midY

    let circlePath = UIBezierPath(arcCenter: CGPoint(x: midX,y: midY), radius: CGFloat(20), startAngle: CGFloat(0), endAngle:CGFloat(M_PI * 2), clockwise: true)

    let shapeLayer = CAShapeLayer()

    shapeLayer.path = circlePath.cgPath
    //change the fill color
    shapeLayer.fillColor = UIColor.clear.cgColor
    //you can change the stroke color
    shapeLayer.strokeColor = UIColor.blue.cgColor
    //you can change the line width
    shapeLayer.lineWidth = 2.5

    view.layer.addSublayer(shapeLayer)
    print("Shape layer drawn")
    //=====================
    captureSession.sessionPreset = AVCaptureSessionPresetHigh

    if let devices = AVCaptureDevice.devices() as? [AVCaptureDevice] {
        // Loop through all the capture devices on this phone
        for device in devices {
            // Make sure this particular device supports video
            if (device.hasMediaType(AVMediaTypeVideo)) {
                // Finally check the position and confirm we've got the back camera
                if(device.position == AVCaptureDevicePosition.back) {
                    captureDevice = device
                    if captureDevice != nil {
                        print("Capture device found")
                        beginSession()
                    }
                }
            }
        }
    }
}

@IBAction func actionCameraCapture(_ sender: AnyObject) {

    print("Camera button pressed")
    saveToCamera()
}

func beginSession() {

    do {
        try captureSession.addInput(AVCaptureDeviceInput(device: captureDevice))
        stillImageOutput.outputSettings = [AVVideoCodecKey:AVVideoCodecJPEG]

        if captureSession.canAddOutput(stillImageOutput) {
            captureSession.addOutput(stillImageOutput)
        }

    }
    catch {
        print("error: \(error.localizedDescription)")
    }

    guard let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) else {
        print("no preview layer")
        return
    }
    // this is what displays the camera view. But - it's on TOP of the drawn view, and under the overview. ??
    self.view.layer.addSublayer(previewLayer)
    previewLayer.frame = self.view.layer.frame



    captureSession.startRunning()
    print("Capture session running")


    self.view.addSubview(navigationBar)
    self.view.addSubview(imgOverlay)
    self.view.addSubview(btnCapture)
        }

func saveToCamera() {

    if let videoConnection = stillImageOutput.connection(withMediaType: AVMediaTypeVideo) {
          stillImageOutput.captureStillImageAsynchronously(from: videoConnection, completionHandler: { (CMSampleBuffer, Error) in

            if let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(CMSampleBuffer) {
                if let cameraImage = UIImage(data: imageData) {


                   UIImageWriteToSavedPhotosAlbum(cameraImage, nil, nil, nil)

                }
            }
        })
    }
}



override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

}

Answer

DonMag picture DonMag · Mar 3, 2017

You're very close...

You have:

@IBOutlet weak var shapeLayer: UIView!

but then you also create a CAShapeLayer named shapeLayer:

let shapeLayer = CAShapeLayer()

which you add as a Sublayer of your "main" view. You then add everything else on top of your main view, covering the shapeLayer.

In viewDidLoad(), change your blue-circle-drawing section to this:

    let midX = self.view.bounds.midX
    let midY = self.view.bounds.midY

    let circlePath = UIBezierPath(arcCenter: CGPoint(x: midX,y: midY), radius: CGFloat(20), startAngle: CGFloat(0), endAngle:CGFloat(M_PI * 2), clockwise: true)

    let shapeLayerPath = CAShapeLayer()

    shapeLayerPath.path = circlePath.cgPath
    //change the fill color
    shapeLayerPath.fillColor = UIColor.clear.cgColor
    //you can change the stroke color
    shapeLayerPath.strokeColor = UIColor.blue.cgColor
    //you can change the line width
    shapeLayerPath.lineWidth = 2.5

    // add the blue-circle layer to the shapeLayer ImageView
    shapeLayer.layer.addSublayer(shapeLayerPath)

    print("Shape layer drawn")
    //=====================

Then, at the end of beginSession()...

    self.view.addSubview(navigationBar)
    self.view.addSubview(imgOverlay)
    self.view.addSubview(btnCapture)

    // shapeLayer ImageView is already a subview created in IB
    // but this will bring it to the front
    self.view.addSubview(shapeLayer)

    // note: since these elements are added as @IBOutlet in IB,
    // these could be:

    // self.view.bringSubview(toFront: navigationBar)
    // self.view.bringSubview(toFront: imgOverlay)
    // self.view.bringSubview(toFront: btnCapture)
    // self.view.bringSubview(toFront: shapeLayer)

See what you get :)

Edit: Follow-up question moved to Combining Images in CameraView with Overlay. (Swift 3)?