0

This is the view when showing on an iPad Mini. On an iPhone the camera image seems correct, it fills the screen. How can I tell the camera to fill the entire area? My code follows the images.

Indeed, the project works ok on smaller devices, only when I move to iPad it shows this boundary. Maybe also on iPhone 7, but I don't have one. Running on the simulator makes no difference, because it is the camera view that is being confined. I have included all the code because it's kind of intimately tied together.

enter image description here

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?

//=====================
let screenWidth = UIScreen.main.bounds.size.width
let screenHeight = UIScreen.main.bounds.size.height
var aspectRatio: CGFloat = 1.0


var viewFinderHeight: CGFloat = 0.0
var viewFinderWidth: CGFloat = 0.0
var viewFinderMarginLeft: CGFloat = 0.0
var viewFinderMarginTop: CGFloat = 0.0
//======================


// 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.

    //On iPad Mini this returns 760 x 1024 = correct
    //On the iPhone SE, this returns 320x568 = correct
    print("Width: \(screenWidth)")
    print("Height: \(screenHeight)")

    //=======================

    captureSession.sessionPreset = AVCaptureSessionPresetHigh

    if #available(iOS 10.0, *) {
        if let devices = AVCaptureDevice.defaultDevice(withDeviceType: .builtInWideAngleCamera, mediaType: AVMediaTypeVideo, position: .back) {

            print("Device name: \(devices.localizedName)")

        }
    } else {
        // Fallback on earlier versions
    }

    if let devices = AVCaptureDevice.devices() as? [AVCaptureDevice] {
        // Loop through all the capture devices on this phone
        for device in devices {

            print("Device name: \(device.localizedName)")

            // 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
    //self.previewLayer?.frame = self.view.bounds
    //=======================================


    imgOverlay.frame = self.view.frame
    imgOverlay.image = self.drawCirclesOnImage(fromImage: nil, targetSize: imgOverlay.bounds.size)

    self.view.bringSubview(toFront: navigationBar)
    self.view.bringSubview(toFront: imgOverlay)
    self.view.bringSubview(toFront: btnCapture)
    // don't use shapeLayer anymore...
    //      self.view.bringSubview(toFront: shapeLayer)


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

}


func getImageWithColor(color: UIColor, size: CGSize) -> UIImage {
    let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: size.width, height: size.height))
    UIGraphicsBeginImageContextWithOptions(size, false, 0)
    color.setFill()
    UIRectFill(rect)
    let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()
    return image
}

func drawCirclesOnImage(fromImage: UIImage? = nil, targetSize: CGSize? = CGSize.zero) -> UIImage? {

    if fromImage == nil && targetSize == CGSize.zero {
        return nil
    }

    var tmpimg: UIImage?

    if targetSize == CGSize.zero {

        tmpimg = fromImage

    } else {

        tmpimg = getImageWithColor(color: UIColor.clear, size: targetSize!)

    }

    guard let img = tmpimg else {
        return nil
    }

    let imageSize = img.size
    let scale: CGFloat = 0
    UIGraphicsBeginImageContextWithOptions(imageSize, false, scale)

    img.draw(at: CGPoint.zero)

    let w = imageSize.width

    //print("Width: \(w)")

    let midX = imageSize.width / 2
    let midY = imageSize.height / 2

    // red circles - radius in %
    let circleRads = [ 0.07, 0.13, 0.17, 0.22, 0.29, 0.36, 0.40, 0.48, 0.60, 0.75 ]

    // center "dot" - radius is 1.5%
    var circlePath = UIBezierPath(arcCenter: CGPoint(x: midX,y: midY), radius: CGFloat(w * 0.015), startAngle: CGFloat(0), endAngle:CGFloat(M_PI * 2), clockwise: true)

    UIColor.red.setFill()
    circlePath.stroke()
    circlePath.fill()

    // blue circle is between first and second red circles
    circlePath = UIBezierPath(arcCenter: CGPoint(x: midX,y: midY), radius: w * CGFloat((circleRads[0] + circleRads[1]) / 2.0), startAngle: CGFloat(0), endAngle:CGFloat(M_PI * 2), clockwise: true)

    UIColor.blue.setStroke()
    circlePath.lineWidth = 2.5
    circlePath.stroke()

    UIColor.red.setStroke()

    for pct in circleRads {

        let rad = w * CGFloat(pct)

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

        circlePath.lineWidth = 2.5
        circlePath.stroke()

    }

    let newImage = UIGraphicsGetImageFromCurrentImageContext()

    UIGraphicsEndImageContext()

    return newImage
}

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

                    if let nImage = self.drawCirclesOnImage(fromImage: cameraImage, targetSize: CGSize.zero) {
                        UIImageWriteToSavedPhotosAlbum(nImage, nil, nil, nil)
                    }

                }
            }
        })
    }
}



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

}

Postmaster
  • 110
  • 3
  • 13

2 Answers2

2

Try this, its working fine with my this application. :)

 override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
            super.viewWillTransition(to: size, with: coordinator)
            // Update camera orientation
            let videoOrientation: AVCaptureVideoOrientation
            switch UIDevice.current.orientation {
            case .portrait:
                videoOrientation = .portrait
            case .portraitUpsideDown:
                videoOrientation = .portraitUpsideDown
            case .landscapeLeft:
                videoOrientation = .landscapeRight
            case .landscapeRight:
                videoOrientation = .landscapeLeft
            default:
                videoOrientation = .portrait
            }
            cameraView.layer.connection.videoOrientation = videoOrientation
        }
Ashish Sharma
  • 663
  • 5
  • 15
  • This appears to control orientation - how do I call it from my code though?and, It doesn't appear to solve the problem of the camera view being the wrong size for the display. – Postmaster Mar 06 '17 at 08:01
  • unless I'm missing something - the sample application on github is empty? – Postmaster Mar 06 '17 at 08:08
  • Hey @user7653427 thanks for telling me that the project is empty, i've updated it now. :) – Ashish Sharma Mar 06 '17 at 11:15
  • @user7653427 And also, the function viewWillTransition() has a parameter size , which will be the size of your view , for that you can read more at [This Reference](https://developer.apple.com/reference/uikit/uicontentcontainer/1621466-viewwilltransition) – Ashish Sharma Mar 06 '17 at 11:23
0

I found the answer. The video preview image is now the right size on both the iPhone SE, and the iPad Mini. It should be right on others now as well.

Change the code in the original ViewController.swift, beginning line 107 ending line 127, to this code. I've left the commented out section in place for clarity.

The key line is the previewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill;

Thanks for the kind help from others.

         //==============================
    /*
    guard let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) else {
        print("no preview layer")
        return
    }
    */
    previewLayer = AVCaptureVideoPreviewLayer(session: captureSession);
    previewLayer?.frame = view.layer.bounds
    previewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill;
    view.layer.addSublayer(previewLayer!);


    // this is what displays the camera view.
    //=======================================
    self.view.layer.addSublayer(previewLayer!)
    previewLayer?.frame = self.view.layer.bounds
    //self.previewLayer?.frame = self.view.bounds


    //=======================================
Postmaster
  • 110
  • 3
  • 13
  • a bit more to do yet. It's still saving the cropped image, not the new full screen image. That will be in the save to camera bit. – Postmaster Mar 06 '17 at 09:29