Pinch to zoom camera

Swift 3.0 || 4.0


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 👍🏼


I have experienced the same issues with the camera implementation. To solve this you need to know about two things.

  • The max and min zoom has to be within a value or else it results in the camera zooming in way too much.
  • As with the actual image not saving the zoomed in image, it is a common bug a lot of solutions online do not cover. This is actually because you are only changing the view's zoom and not the actual 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.