I\'m trying to make a pinch to zoom camera but I\'m encountering two problems. First is that it allows the user to zoom way too much in and way to much out, secondly when I take
I have experienced the same issues with the camera implementation. To solve this you need to know about two things.
AVCaptureDevice
's zoom.To change the two things you need something like this:
func pinch(pinch: UIPinchGestureRecognizer) {
var device: AVCaptureDevice = self.videoDevice
var vZoomFactor = ((gestureRecognizer as! UIPinchGestureRecognizer).scale)
var error:NSError!
do{
try device.lockForConfiguration()
defer {device.unlockForConfiguration()}
if (vZoomFactor <= device.activeFormat.videoMaxZoomFactor){
device.videoZoomFactor = vZoomFactor
}else{
NSLog("Unable to set videoZoom: (max %f, asked %f)", device.activeFormat.videoMaxZoomFactor, vZoomFactor);
}
}catch error as NSError{
NSLog("Unable to set videoZoom: %@", error.localizedDescription);
}catch _{
}
}
As you can see I use a class variable for the video device (videoDevice
) to keep track of the capture device I am using for visual component. I restrict the zoom to a particular range and change the zoom property on the device and not the view itself!
You can avoid saving prevZoomFactor
by simply resetting UIPinchGestureRecognizer.scale
to 1 like this:
@IBAction func pinchAction(_ sender: UIPinchGestureRecognizer) {
guard let device = currentCaptureDevice else {return}
var zoom = device.videoZoomFactor * sender.scale
sender.scale = 1.0
var error:NSError!
do{
try device.lockForConfiguration()
defer {device.unlockForConfiguration()}
if zoom >= device.minAvailableVideoZoomFactor && zoom <= device.maxAvailableVideoZoomFactor {
device.videoZoomFactor = zoom
}else{
NSLog("Unable to set videoZoom: (max %f, asked %f)", device.activeFormat.videoMaxZoomFactor, zoom);
}
}catch error as NSError{
NSLog("Unable to set videoZoom: %@", error.localizedDescription);
}catch _{
}
}
This was recommended by Apple at a WWDC event I attended way back when gesture recognizers first came out.
If you need a manual zoomTo(2.0) function, you can use this
// Create listener for Pinch to Zoom
let pinchRecognizer = UIPinchGestureRecognizer(target: self, action:#selector(FSCameraView.pinchToZoom(_:)))
pinchRecognizer.delegate = self
self.previewViewContainer.addGestureRecognizer(pinchRecognizer)
// set the zoom to a zoomed in mode from start
setZoom(CGFloat(2.0)
// and the functions
func pinchToZoom(sender:UIPinchGestureRecognizer) {
var vZoomFactor = ((sender as! UIPinchGestureRecognizer).scale)
setZoom(vZoomFactor)
}
func setZoom(zoomFactor:CGFloat) {
var device: AVCaptureDevice = self.device!
var error:NSError!
do{
try device.lockForConfiguration()
defer {device.unlockForConfiguration()}
if (zoomFactor <= device.activeFormat.videoMaxZoomFactor) {
let desiredZoomFactor:CGFloat = zoomFactor + atan2(sender.velocity, 5.0);
device.videoZoomFactor = max(1.0, min(desiredZoomFactor, device.activeFormat.videoMaxZoomFactor));
}
else {
NSLog("Unable to set videoZoom: (max %f, asked %f)", device.activeFormat.videoMaxZoomFactor, zoomFactor);
}
}
catch error as NSError{
NSLog("Unable to set videoZoom: %@", error.localizedDescription);
}
catch _{
}
}
var device: AVCaptureDevice = self.backCamera
var vZoomFactor = sender.scale
var error:NSError!
do{
try device.lockForConfiguration()
defer {device.unlockForConfiguration()}
if (vZoomFactor <= device.activeFormat.videoMaxZoomFactor) {
let desiredZoomFactor:CGFloat = vZoomFactor + atan2(sender.velocity, 5.0);
device.videoZoomFactor = max(1.0, min(desiredZoomFactor, device.activeFormat.videoMaxZoomFactor));
}
else {
NSLog("Unable to set videoZoom: (max %f, asked %f)", device.activeFormat.videoMaxZoomFactor, vZoomFactor);
}
}
catch error as NSError{
NSLog("Unable to set videoZoom: %@", error.localizedDescription);
}
catch _{
}
To expand on Ritvik Upadhyaya's answer, you would also need to save the previous zoom factor to calculate the new one, you wouldn't want the zooming to reset to normal every time you lift up your fingers and try zooming again.
// To track the zoom factor
var prevZoomFactor: CGFloat = 1
func pinch(pinch: UIPinchGestureRecognizer) {
var device: AVCaptureDevice = self.videoDevice
// Here we multiply vZoomFactor with the previous zoom factor if it exist.
// Else just multiply by 1
var vZoomFactor = pinch.scale * prevZoomFactor
// If the pinching has ended, update prevZoomFactor.
// Note that we set the limit at 1, because zoom factor cannot be less than 1 or the setting device.videoZoomFactor will crash
if sender.state == .ended {
prevZoomFactor = zoomFactor >= 1 ? zoomFactor : 1
}
do {
try device.lockForConfiguration()
defer {device.unlockForConfiguration()}
if (vZoomFactor <= device.activeFormat.videoMaxZoomFactor) {
device.videoZoomFactor = vZoomFactor
} else {
print("Unable to set videoZoom: (max \(device.activeFormat.videoMaxZoomFactor), asked \(vZoomFactor))")
}
} catch {
print("\(error.localizedDescription)")
}
}
1. Define zoom levels.
let minimumZoom: CGFloat = 1.0
let maximumZoom: CGFloat = 3.0
var lastZoomFactor: CGFloat = 1.0
2. Add Pinch Gesture on CameraView.
let pinchRecognizer = UIPinchGestureRecognizer(target: self, action:#selector(pinch(_:)))
self.viewCamera.addGestureRecognizer(pinchRecognizer)
3. Pinch-action method with the logic of zoomin
and zoom out
func pinch(_ pinch: UIPinchGestureRecognizer) {
guard let device = videoDeviceInput.device else { return }
// Return zoom value between the minimum and maximum zoom values
func minMaxZoom(_ factor: CGFloat) -> CGFloat {
return min(min(max(factor, minimumZoom), maximumZoom), device.activeFormat.videoMaxZoomFactor)
}
func update(scale factor: CGFloat) {
do {
try device.lockForConfiguration()
defer { device.unlockForConfiguration() }
device.videoZoomFactor = factor
} catch {
print("\(error.localizedDescription)")
}
}
let newScaleFactor = minMaxZoom(pinch.scale * lastZoomFactor)
switch pinch.state {
case .began: fallthrough
case .changed: update(scale: newScaleFactor)
case .ended:
lastZoomFactor = minMaxZoom(newScaleFactor)
update(scale: lastZoomFactor)
default: break
}
}
Thanks. Happy coding