diff --git a/README.md b/README.md index 62ffd04..53afac5 100755 --- a/README.md +++ b/README.md @@ -88,13 +88,27 @@ cameraManager.stopVideoRecording({ (videoURL, recordError) -> Void in }) ``` -To zoom in manually: +To zoom manually: ```swift +// Zoom in let zoomScale = CGFloat(2.0) cameraManager.zoom(zoomScale) + +// Zoom out (uses ultra-wide camera on supported devices) +let zoomScale = CGFloat(1.0) +cameraManager.zoom(zoomScale) ``` +The zoom range is automatically determined by the device's camera capabilities using `minAvailableVideoZoomFactor` and `maxAvailableVideoZoomFactor`. + +**Ultra-Wide Camera Support:** +The library automatically detects and uses multi-camera virtual devices (iPhone 11+) that include ultra-wide lenses: +- iPhone 11 Pro/12 Pro/13 Pro/14 Pro: `.builtInTripleCamera` (ultra-wide + wide + telephoto) +- iPhone 11/13/14: `.builtInDualWideCamera` (ultra-wide + wide) + +On these devices, setting zoom to 1.0 automatically uses the ultra-wide lens. iOS handles the camera switching seamlessly as you zoom in and out. + ### Properties You can set input device to front or back camera. `(Default: .Back)` diff --git a/Sources/CameraManager.swift b/Sources/CameraManager.swift index a568cea..3e226cc 100644 --- a/Sources/CameraManager.swift +++ b/Sources/CameraManager.swift @@ -260,7 +260,7 @@ open class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate, UIGest _updateCameraDevice(cameraDevice) _updateIlluminationMode(flashMode) _setupMaxZoomScale() - _zoom(0) + _zoom(1) _orientationChanged() } } @@ -292,7 +292,7 @@ open class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate, UIGest _setupOutputMode(cameraOutputMode, oldCameraOutputMode: oldValue) } _setupMaxZoomScale() - _zoom(0) + _zoom(1) } } } @@ -348,7 +348,26 @@ open class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate, UIGest }() fileprivate lazy var backCameraDevice: AVCaptureDevice? = { - AVCaptureDevice.videoDevices.filter { $0.position == .back }.first + // Try to get multi-camera virtual devices first (supports ultra-wide) + var deviceTypes: [AVCaptureDevice.DeviceType] = [.builtInWideAngleCamera] + + if #available(iOS 13.0, *) { + // Prefer virtual devices that support multiple cameras including ultra-wide + deviceTypes = [ + .builtInTripleCamera, // iPhone 11 Pro, 12 Pro, 13 Pro, 14 Pro, etc. + .builtInDualWideCamera, // iPhone 11, 13, 14 (wide + ultra-wide) + .builtInDualCamera, // iPhone 7 Plus, 8 Plus, X, XS (wide + telephoto) + .builtInWideAngleCamera // Fallback for single camera devices + ] + } + + let discoverySession = AVCaptureDevice.DiscoverySession( + deviceTypes: deviceTypes, + mediaType: .video, + position: .back + ) + + return discoverySession.devices.first }() fileprivate lazy var mic: AVCaptureDevice? = { @@ -366,6 +385,7 @@ open class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate, UIGest fileprivate var zoomScale = CGFloat(1.0) fileprivate var beginZoomScale = CGFloat(1.0) fileprivate var maxZoomScale = CGFloat(1.0) + fileprivate var minZoomScale = CGFloat(1.0) fileprivate func _tempFilePath() -> URL { let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("tempMovie\(Date().timeIntervalSince1970)").appendingPathExtension("mp4") @@ -740,7 +760,7 @@ open class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate, UIGest /** Starts recording a video with or without voice as in the session preset. */ - open func startRecordingVideo() { + open func startRecordingVideo(toURL url: URL? = nil) { guard cameraOutputMode != .stillImage else { _show(NSLocalizedString("Capture session output still image", comment: ""), message: NSLocalizedString("I can only take pictures", comment: "")) return @@ -772,7 +792,7 @@ open class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate, UIGest _updateIlluminationMode(flashMode) - videoOutput.startRecording(to: _tempFilePath(), recordingDelegate: self) + videoOutput.startRecording(to: url ?? _tempFilePath(), recordingDelegate: self) } /** @@ -971,7 +991,7 @@ open class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate, UIGest let captureDevice = device try captureDevice?.lockForConfiguration() - zoomScale = max(1.0, min(beginZoomScale * scale, maxZoomScale)) + zoomScale = max(minZoomScale, min(beginZoomScale * scale, maxZoomScale)) captureDevice?.videoZoomFactor = zoomScale @@ -1541,15 +1561,41 @@ open class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate, UIGest fileprivate func _setupMaxZoomScale() { var maxZoom = CGFloat(1.0) + var minZoom = CGFloat(1.0) + var defaultZoom = CGFloat(1.0) beginZoomScale = CGFloat(1.0) - + if cameraDevice == .back, let backCameraDevice = backCameraDevice { maxZoom = backCameraDevice.activeFormat.videoMaxZoomFactor + minZoom = backCameraDevice.minAvailableVideoZoomFactor + + // For multi-camera devices, start at the first switchover point (main wide-angle camera) + // This represents "1x" zoom in the Camera app + if #available(iOS 13.0, *), + let switchOverFactors = backCameraDevice.virtualDeviceSwitchOverVideoZoomFactors as? [CGFloat], + let firstSwitchOver = switchOverFactors.first { + defaultZoom = firstSwitchOver + } else { + defaultZoom = minZoom + } } else if cameraDevice == .front, let frontCameraDevice = frontCameraDevice { maxZoom = frontCameraDevice.activeFormat.videoMaxZoomFactor + minZoom = frontCameraDevice.minAvailableVideoZoomFactor + + // For multi-camera front devices, start at the first switchover point + if #available(iOS 13.0, *), + let switchOverFactors = frontCameraDevice.virtualDeviceSwitchOverVideoZoomFactors as? [CGFloat], + let firstSwitchOver = switchOverFactors.first { + defaultZoom = firstSwitchOver + } else { + defaultZoom = minZoom + } } - + maxZoomScale = maxZoom + minZoomScale = minZoom + zoomScale = defaultZoom + beginZoomScale = defaultZoom } fileprivate func _checkIfCameraIsAvailable() -> CameraState {