11

The API for using the camera in swift seems a but different and I am having a hard time focusing the camera on a point. When the user taps the screen I want the camera to focus on that point

This is my code:

 func focusCamera(point:CGPoint)
    {
        var screenRect:CGRect = bounds
        var focusX = Float(point.x/screenRect.width)
        var focusY = Float(point.y/screenRect.height)

        _currentDevice.lockForConfiguration(nil)
        _currentDevice.setFocusModeLockedWithLensPosition(focusX)
        {
            time in
            self._currentDevice.unlockForConfiguration()
        }

        _currentDevice.setFocusModeLockedWithLensPosition(focusY)
        {
                time in
                self._currentDevice.unlockForConfiguration()
        }
    }

But it doesnt seem to work.

Any suggestions are more than welcome!

Aggressor
  • 13,323
  • 24
  • 103
  • 182

7 Answers7

19

Updated answer from @ryantxr for Swift 3:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    let screenSize = videoView.bounds.size
    if let touchPoint = touches.first {
        let x = touchPoint.location(in: videoView).y / screenSize.height
        let y = 1.0 - touchPoint.location(in: videoView).x / screenSize.width
        let focusPoint = CGPoint(x: x, y: y)
        
        if let device = captureDevice {
            do {
                try device.lockForConfiguration()
                
                device.focusPointOfInterest = focusPoint
                //device.focusMode = .continuousAutoFocus
                device.focusMode = .autoFocus
                //device.focusMode = .locked
                device.exposurePointOfInterest = focusPoint
                device.exposureMode = AVCaptureExposureMode.continuousAutoExposure
                device.unlockForConfiguration()
            }
            catch {
                // just ignore
            }
        }
    }
}
OxyFlax
  • 451
  • 1
  • 5
  • 16
  • 1
    You can also use `let devicePoint: CGPoint = (self.previewView.layer as! AVCaptureVideoPreviewLayer).captureDevicePointOfInterestForPoint(gestureRecognizer.locationInView(gestureRecognizer.view))` instead of manually calculating the coordinates for device. – Milo Wielondek May 17 '18 at 15:34
7

Better solution, as it works correctly for all videoGravity modes, also when the preview layer aspect ratio is different from the device ratio:

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

    guard let touchPoint = touches.first else { return }

    // precondition: the videoView contains the previewLayer, and the frames of the two are being kept equal
    let touchPointInPreviewLayer = touchPoint.location(in: videoView)
    let focusPoint = previewLayer.captureDevicePointOfInterest(for: touchPointInPreviewLayer)

    // etc
}
fabb
  • 11,660
  • 13
  • 67
  • 111
4

Updated answer from @Code for Swift 2.

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let screenSize = videoView.bounds.size
    if let touchPoint = touches.first {
        let x = touchPoint.locationInView(videoView).y / screenSize.height
        let y = 1.0 - touchPoint.locationInView(videoView).x / screenSize.width
        let focusPoint = CGPoint(x: x, y: y)

        if let device = captureDevice {
            do {
                try device.lockForConfiguration()

                device.focusPointOfInterest = focusPoint
                //device.focusMode = .ContinuousAutoFocus
                device.focusMode = .AutoFocus
                //device.focusMode = .Locked
                device.exposurePointOfInterest = focusPoint
                device.exposureMode = AVCaptureExposureMode.ContinuousAutoExposure
                device.unlockForConfiguration()
            }
            catch {
                // just ignore
            }
        }
    }
}
ryantxr
  • 4,119
  • 1
  • 11
  • 25
  • Perfect answer! Note the X and Y coordinates, they are the *most important part* of this example. The according to the apple docs: "[The focusPointOfInterest] represents a CGPoint where {0,0} corresponds to the top left of the picture area, and {1,1} corresponds to the bottom right in landscape mode with the home button on the right" – SeanRobinson159 Jun 16 '16 at 15:00
  • The documentation also says that we should use the point conversion methods in `AVCaptureVideoPreviewLayer` captureDevicePointOfInterest. This answer does not work for me correctly, probably because it ignores device orientation. I'd rather use `captureDevicePointOfInterest`. – fabb Jan 05 '17 at 14:17
  • Also this answer does not work correctly when the `videoView` does not have the same aspect ratio as the raw device image output (e.g. when using `videoGravity`). – fabb Jan 05 '17 at 14:25
2

Swift 2.0

if let device = captureDevice {
  do { 
    try device.lockForConfiguration()
    device.focusPointOfInterest = focusPoint
    device.focusMode = AVCaptureFocusMode.ContinuousAutoFocus
    device.exposurePointOfInterest = focusPoint
    device.exposureMode = AVCaptureExposureMode.ContinuousAutoExposure
  } catch let error as NSError {
    print(error.localizedDescription)
  }
}
Tunaki
  • 132,869
  • 46
  • 340
  • 423
2

I used AVFoundation library and a wrapper that Camera Manager. I am sharing to code sample. You can adapt your project. By the way, you cannot focus the camera using front-camera. It doesn't support it. You can only adjust exposure for front-camera. By rare-camera, you can do it.

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {

    let screenSize = cameraView.bounds.size
    let frameSize:CGSize = view.frame.size
    if let touchPoint = touches.first {

        var location:CGPoint = touchPoint.locationInView(cameraView)

        if cameraManager.cameraDevice == .Front {
            print("Front camera is used")

            location.x = frameSize.width - location.x;
        }
        else {
            print("Back camera is used")
        }

        let x = location.x / frameSize.width
        let y = 1.0 - (location.x / frameSize.width)

        let focusPoint = CGPoint(x: x, y: y)

        print("POINT : X: \(x), Y: \(y)")


        let captureDevice = (AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo) as! [AVCaptureDevice]).filter{$0.position == .Back}.first

        if let device = captureDevice {
            do {
                try device.lockForConfiguration()

                let support:Bool = device.focusPointOfInterestSupported

                if support  {

                    print("focusPointOfInterestSupported: \(support)")

                    device.focusPointOfInterest = focusPoint

                    // device.focusMode = .ContinuousAutoFocus
                    device.focusMode = .AutoFocus
                    // device.focusMode = .Locked

                    device.unlockForConfiguration()

                    print("Focus point was set successfully")
                }
                else{
                    print("focusPointOfInterestSupported is not supported: \(support)")
                }
            }
            catch {
                // just ignore
                print("Focus point error")
            }
        }
    }
}
Meet Doshi
  • 4,241
  • 10
  • 40
  • 81
gkhanacer
  • 575
  • 7
  • 25
1

Turns out its very simple:

_currentDevice.lockForConfiguration(nil)
_currentDevice.focusPointOfInterest = tap.locationInView(self)
_currentDevice.unlockForConfiguration()
Aggressor
  • 13,323
  • 24
  • 103
  • 182
  • 3
    Could you please provide more information... I'm beginner and I don't know where put this code – Michael Czolko Apr 08 '15 at 22:14
  • `let gesture = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))` and `myView.addGesture(gesture)`. With that, you create `handleTap` that looks like `func handleTap(tap: UITapGestureRecognizer)` and then the code above goes in there. – Rob Aug 26 '18 at 16:28
1

This question maybe is a duplicate of iOS tap to focus, where I posted this (hopefully explicit) solution that worked for me:

With a videoView: UIView displaying the video, and cameraDevice: AVCaptureDevice, the following seems to work for me:

override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
    var touchPoint = touches.first as! UITouch
    var screenSize = videoView.bounds.size
    var focusPoint = CGPoint(x: touchPoint.locationInView(videoView).y / screenSize.height, y: 1.0 - touchPoint.locationInView(videoView.x / screenSize.width)

    if let device = cameraDevice {
        if(device.lockForConfiguration(nil)) {
            device.focusPointOfInterest = focusPoint
            device.focusMode = AVCaptureFocusMode.ContinuousAutoExposure
            device.exposurePointOfInterest = focusPoint
            device.exposureMode = AVCaptureExposureMode.ContinuousAutoExposure
            device.unlockForConfiguration()
        }
    }
}

Note that I had to swap the x and y coordinates, and remap the x coord from 1 to 0 instead of 0 to 1 — not sure why that should be the case but it seems to be necessary to get it to work right (though it's a little tricky to test it too).

Community
  • 1
  • 1
Cody
  • 721
  • 7
  • 15