@capgo/camera-preview 7.4.0-beta.3 → 7.4.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +66 -23
  2. package/android/.gradle/8.14.2/checksums/checksums.lock +0 -0
  3. package/android/.gradle/8.14.2/checksums/md5-checksums.bin +0 -0
  4. package/android/.gradle/8.14.2/checksums/sha1-checksums.bin +0 -0
  5. package/android/.gradle/8.14.2/executionHistory/executionHistory.bin +0 -0
  6. package/android/.gradle/8.14.2/executionHistory/executionHistory.lock +0 -0
  7. package/android/.gradle/8.14.2/fileHashes/fileHashes.bin +0 -0
  8. package/android/.gradle/8.14.2/fileHashes/fileHashes.lock +0 -0
  9. package/android/.gradle/8.14.2/fileHashes/resourceHashesCache.bin +0 -0
  10. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  11. package/android/.gradle/file-system.probe +0 -0
  12. package/android/src/main/AndroidManifest.xml +1 -1
  13. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraPreview.java +54 -27
  14. package/android/src/main/java/com/ahm/capacitor/camera/preview/CameraXView.java +132 -19
  15. package/android/src/main/java/com/ahm/capacitor/camera/preview/GridOverlayView.java +80 -0
  16. package/android/src/main/java/com/ahm/capacitor/camera/preview/model/CameraSessionConfiguration.java +19 -5
  17. package/dist/docs.json +100 -3
  18. package/dist/esm/definitions.d.ts +44 -2
  19. package/dist/esm/definitions.js.map +1 -1
  20. package/dist/esm/web.d.ts +16 -2
  21. package/dist/esm/web.js +177 -78
  22. package/dist/esm/web.js.map +1 -1
  23. package/dist/plugin.cjs.js +177 -78
  24. package/dist/plugin.cjs.js.map +1 -1
  25. package/dist/plugin.js +177 -78
  26. package/dist/plugin.js.map +1 -1
  27. package/ios/Sources/CapgoCameraPreview/CameraController.swift +78 -20
  28. package/ios/Sources/CapgoCameraPreview/GridOverlayView.swift +65 -0
  29. package/ios/Sources/CapgoCameraPreview/Plugin.swift +155 -37
  30. package/package.json +1 -1
@@ -27,6 +27,7 @@ class CameraController: NSObject {
27
27
  var fileVideoOutput: AVCaptureMovieFileOutput?
28
28
 
29
29
  var previewLayer: AVCaptureVideoPreviewLayer?
30
+ var gridOverlayView: GridOverlayView?
30
31
 
31
32
  var flashMode = AVCaptureDevice.FlashMode.off
32
33
  var photoCaptureCompletionBlock: ((UIImage?, Error?) -> Void)?
@@ -51,7 +52,7 @@ class CameraController: NSObject {
51
52
  }
52
53
 
53
54
  extension CameraController {
54
- func prepare(cameraPosition: String, deviceId: String? = nil, disableAudio: Bool, cameraMode: Bool, completionHandler: @escaping (Error?) -> Void) {
55
+ func prepare(cameraPosition: String, deviceId: String? = nil, disableAudio: Bool, cameraMode: Bool, aspectRatio: String? = nil, completionHandler: @escaping (Error?) -> Void) {
55
56
  func createCaptureSession() {
56
57
  self.captureSession = AVCaptureSession()
57
58
  }
@@ -204,11 +205,54 @@ extension CameraController {
204
205
  func configurePhotoOutput(cameraMode: Bool) throws {
205
206
  guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing }
206
207
 
207
- // TODO: check if that really useful
208
- if !cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.photo) {
209
- captureSession.sessionPreset = .photo
210
- } else if cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.high) {
211
- captureSession.sessionPreset = .high
208
+ // Configure session preset based on aspect ratio and other settings
209
+ var targetPreset: AVCaptureSession.Preset = .photo // Default preset
210
+
211
+ if let aspectRatio = aspectRatio {
212
+ switch aspectRatio {
213
+ case "16:9":
214
+ // Use HD presets for 16:9 aspect ratio
215
+ if self.highResolutionOutput && captureSession.canSetSessionPreset(.hd1920x1080) {
216
+ targetPreset = .hd1920x1080
217
+ } else if captureSession.canSetSessionPreset(.hd1280x720) {
218
+ targetPreset = .hd1280x720
219
+ } else {
220
+ targetPreset = .high
221
+ }
222
+ case "4:3":
223
+ // Use photo preset for 4:3 aspect ratio (traditional photo format)
224
+ if self.highResolutionOutput && captureSession.canSetSessionPreset(.photo) {
225
+ targetPreset = .photo
226
+ } else {
227
+ targetPreset = .medium
228
+ }
229
+ default:
230
+ // Default behavior for unrecognized aspect ratios
231
+ if !cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.photo) {
232
+ targetPreset = .photo
233
+ } else if cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.high) {
234
+ targetPreset = .high
235
+ }
236
+ }
237
+ } else {
238
+ // Original logic when no aspect ratio is specified
239
+ if !cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.photo) {
240
+ targetPreset = .photo
241
+ } else if cameraMode && self.highResolutionOutput && captureSession.canSetSessionPreset(.high) {
242
+ targetPreset = .high
243
+ }
244
+ }
245
+
246
+ // Apply the determined preset
247
+ if captureSession.canSetSessionPreset(targetPreset) {
248
+ captureSession.sessionPreset = targetPreset
249
+ print("[CameraPreview] Set session preset to \(targetPreset) for aspect ratio: \(aspectRatio ?? "default")")
250
+ } else {
251
+ // Fallback to a basic preset if the target preset is not supported
252
+ print("[CameraPreview] Target preset \(targetPreset) not supported, falling back to .medium")
253
+ if captureSession.canSetSessionPreset(.medium) {
254
+ captureSession.sessionPreset = .medium
255
+ }
212
256
  }
213
257
 
214
258
  self.photoOutput = AVCapturePhotoOutput()
@@ -276,6 +320,19 @@ extension CameraController {
276
320
  updateVideoOrientation()
277
321
  }
278
322
 
323
+ func addGridOverlay(to view: UIView, gridMode: String) {
324
+ removeGridOverlay()
325
+
326
+ gridOverlayView = GridOverlayView(frame: view.bounds)
327
+ gridOverlayView?.gridMode = gridMode
328
+ view.addSubview(gridOverlayView!)
329
+ }
330
+
331
+ func removeGridOverlay() {
332
+ gridOverlayView?.removeFromSuperview()
333
+ gridOverlayView = nil
334
+ }
335
+
279
336
  func setupGestures(target: UIView, enableZoom: Bool) {
280
337
  setupTapGesture(target: target, selector: #selector(handleTap(_:)), delegate: self)
281
338
  if enableZoom {
@@ -433,18 +490,15 @@ extension CameraController {
433
490
  completion(nil, NSError(domain: "Camera", code: 0, userInfo: [NSLocalizedDescriptionKey: "Photo output is not available"]))
434
491
  return
435
492
  }
436
-
493
+
437
494
  let settings = AVCapturePhotoSettings()
438
- if photoOutput.isHighResolutionCaptureEnabled {
439
- settings.isHighResolutionCaptureEnabled = true
440
- }
441
495
 
442
496
  self.photoCaptureCompletionBlock = { (image, error) in
443
497
  if let error = error {
444
498
  completion(nil, error)
445
499
  return
446
500
  }
447
-
501
+
448
502
  guard let image = image else {
449
503
  completion(nil, NSError(domain: "Camera", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to capture image"]))
450
504
  return
@@ -453,7 +507,7 @@ extension CameraController {
453
507
  if let location = gpsLocation {
454
508
  self.addGPSMetadata(to: image, location: location)
455
509
  }
456
-
510
+
457
511
  if let width = width, let height = height {
458
512
  let resizedImage = self.resizeImage(image: image, to: CGSize(width: width, height: height))
459
513
  completion(resizedImage, nil)
@@ -461,7 +515,7 @@ extension CameraController {
461
515
  completion(image, nil)
462
516
  }
463
517
  }
464
-
518
+
465
519
  photoOutput.capturePhoto(with: settings, delegate: self)
466
520
  }
467
521
 
@@ -469,21 +523,25 @@ extension CameraController {
469
523
  guard let jpegData = image.jpegData(compressionQuality: 1.0),
470
524
  let source = CGImageSourceCreateWithData(jpegData as CFData, nil),
471
525
  let uti = CGImageSourceGetType(source) else { return }
472
-
473
- let metadata = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [String: Any] ?? [:]
474
-
526
+
527
+ var metadata = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [String: Any] ?? [:]
528
+
529
+ let formatter = DateFormatter()
530
+ formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
531
+ formatter.timeZone = TimeZone(abbreviation: "UTC")
532
+
475
533
  let gpsDict: [String: Any] = [
476
534
  kCGImagePropertyGPSLatitude as String: abs(location.coordinate.latitude),
477
535
  kCGImagePropertyGPSLatitudeRef as String: location.coordinate.latitude >= 0 ? "N" : "S",
478
536
  kCGImagePropertyGPSLongitude as String: abs(location.coordinate.longitude),
479
537
  kCGImagePropertyGPSLongitudeRef as String: location.coordinate.longitude >= 0 ? "E" : "W",
480
- kCGImagePropertyGPSTimeStamp as String: location.timestamp.ISO8601Format(),
538
+ kCGImagePropertyGPSTimeStamp as String: formatter.string(from: location.timestamp),
481
539
  kCGImagePropertyGPSAltitude as String: location.altitude,
482
540
  kCGImagePropertyGPSAltitudeRef as String: location.altitude >= 0 ? 0 : 1
483
541
  ]
484
-
542
+
485
543
  metadata[kCGImagePropertyGPSDictionary as String] = gpsDict
486
-
544
+
487
545
  let destData = NSMutableData()
488
546
  guard let destination = CGImageDestinationCreateWithData(destData, uti, 1, nil) else { return }
489
547
  CGImageDestinationAddImageFromSource(destination, source, 0, metadata as CFDictionary)
@@ -737,7 +795,7 @@ extension CameraController {
737
795
 
738
796
  return device.uniqueID
739
797
  }
740
-
798
+
741
799
  func getCurrentLensInfo() throws -> (focalLength: Float, deviceType: String, baseZoomRatio: Float) {
742
800
  var currentCamera: AVCaptureDevice?
743
801
  switch currentCameraPosition {
@@ -0,0 +1,65 @@
1
+ import UIKit
2
+
3
+ class GridOverlayView: UIView {
4
+
5
+ var gridMode: String = "none" {
6
+ didSet {
7
+ isHidden = gridMode == "none"
8
+ setNeedsDisplay()
9
+ }
10
+ }
11
+
12
+ override init(frame: CGRect) {
13
+ super.init(frame: frame)
14
+ setup()
15
+ }
16
+
17
+ required init?(coder: NSCoder) {
18
+ super.init(coder: coder)
19
+ setup()
20
+ }
21
+
22
+ private func setup() {
23
+ backgroundColor = UIColor.clear
24
+ isUserInteractionEnabled = false
25
+ isHidden = true
26
+ }
27
+
28
+ override func draw(_ rect: CGRect) {
29
+ guard let context = UIGraphicsGetCurrentContext() else { return }
30
+
31
+ if gridMode == "none" {
32
+ return
33
+ }
34
+
35
+ context.setStrokeColor(UIColor.white.withAlphaComponent(0.5).cgColor)
36
+ context.setLineWidth(1.0)
37
+
38
+ if gridMode == "3x3" {
39
+ drawGrid(context: context, rect: rect, divisions: 3)
40
+ } else if gridMode == "4x4" {
41
+ drawGrid(context: context, rect: rect, divisions: 4)
42
+ }
43
+ }
44
+
45
+ private func drawGrid(context: CGContext, rect: CGRect, divisions: Int) {
46
+ let stepX = rect.width / CGFloat(divisions)
47
+ let stepY = rect.height / CGFloat(divisions)
48
+
49
+ // Draw vertical lines
50
+ for i in 1..<divisions {
51
+ let x = CGFloat(i) * stepX
52
+ context.move(to: CGPoint(x: x, y: 0))
53
+ context.addLine(to: CGPoint(x: x, y: rect.height))
54
+ }
55
+
56
+ // Draw horizontal lines
57
+ for i in 1..<divisions {
58
+ let y = CGFloat(i) * stepY
59
+ context.move(to: CGPoint(x: 0, y: y))
60
+ context.addLine(to: CGPoint(x: rect.width, y: y))
61
+ }
62
+
63
+ context.strokePath()
64
+ }
65
+ }
@@ -5,6 +5,7 @@ import Photos
5
5
  import CoreImage
6
6
  import CoreLocation
7
7
 
8
+
8
9
  extension UIWindow {
9
10
  static var isLandscape: Bool {
10
11
  if #available(iOS 13.0, *) {
@@ -35,7 +36,7 @@ extension UIWindow {
35
36
  * here: https://capacitor.ionicframework.com/docs/plugins/ios
36
37
  */
37
38
  @objc(CameraPreview)
38
- public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
39
+ public class CameraPreview: CAPPlugin, CAPBridgedPlugin, CLLocationManagerDelegate {
39
40
  public let identifier = "CameraPreviewPlugin"
40
41
  public let jsName = "CameraPreview"
41
42
  public let pluginMethods: [CAPPluginMethod] = [
@@ -57,7 +58,9 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
57
58
  CAPPluginMethod(name: "setZoom", returnType: CAPPluginReturnPromise),
58
59
  CAPPluginMethod(name: "getFlashMode", returnType: CAPPluginReturnPromise),
59
60
  CAPPluginMethod(name: "setDeviceId", returnType: CAPPluginReturnPromise),
60
- CAPPluginMethod(name: "getDeviceId", returnType: CAPPluginReturnPromise)
61
+ CAPPluginMethod(name: "getDeviceId", returnType: CAPPluginReturnPromise),
62
+ CAPPluginMethod(name: "setAspectRatio", returnType: CAPPluginReturnPromise),
63
+ CAPPluginMethod(name: "getAspectRatio", returnType: CAPPluginReturnPromise)
61
64
  ]
62
65
  // Camera state tracking
63
66
  private var isInitializing: Bool = false
@@ -79,33 +82,41 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
79
82
  var disableAudio: Bool = false
80
83
  var locationManager: CLLocationManager?
81
84
  var currentLocation: CLLocation?
85
+ private var aspectRatio: String?
82
86
 
83
87
  // MARK: - Transparency Methods
84
-
88
+
89
+
85
90
  private func makeWebViewTransparent() {
86
91
  guard let webView = self.webView else { return }
87
-
92
+
93
+
88
94
  // Define a recursive function to traverse the view hierarchy
89
95
  func makeSubviewsTransparent(_ view: UIView) {
90
96
  // Set the background color to clear
91
97
  view.backgroundColor = .clear
92
-
98
+
99
+
93
100
  // Recurse for all subviews
94
101
  for subview in view.subviews {
95
102
  makeSubviewsTransparent(subview)
96
103
  }
97
104
  }
98
-
105
+
106
+
99
107
  // Set the main webView to be transparent
100
108
  webView.isOpaque = false
101
109
  webView.backgroundColor = .clear
102
-
110
+
111
+
103
112
  // Recursively make all subviews transparent
104
113
  makeSubviewsTransparent(webView)
105
-
114
+
115
+
106
116
  // Also ensure the webview's container is transparent
107
117
  webView.superview?.backgroundColor = .clear
108
-
118
+
119
+
109
120
  // Force a layout pass to apply changes
110
121
  DispatchQueue.main.async {
111
122
  webView.setNeedsLayout()
@@ -150,13 +161,38 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
150
161
  }
151
162
 
152
163
  cameraController.updateVideoOrientation()
153
-
164
+
165
+ cameraController.updateVideoOrientation()
166
+
167
+ // Update grid overlay frame if it exists
168
+ if let gridOverlay = self.cameraController.gridOverlayView {
169
+ gridOverlay.frame = previewView.bounds
170
+ }
171
+
154
172
  // Ensure webview remains transparent after rotation
155
173
  if self.isInitialized {
156
174
  self.makeWebViewTransparent()
157
175
  }
158
176
  }
159
177
 
178
+ @objc func setAspectRatio(_ call: CAPPluginCall) {
179
+ guard self.isInitialized else {
180
+ call.reject("camera not started")
181
+ return
182
+ }
183
+ self.aspectRatio = call.getString("aspectRatio")
184
+ self.updateCameraFrame()
185
+ call.resolve()
186
+ }
187
+
188
+ @objc func getAspectRatio(_ call: CAPPluginCall) {
189
+ guard self.isInitialized else {
190
+ call.reject("camera not started")
191
+ return
192
+ }
193
+ call.resolve(["aspectRatio": self.aspectRatio ?? "fill"])
194
+ }
195
+
160
196
  @objc func appDidBecomeActive() {
161
197
  if self.isInitialized {
162
198
  DispatchQueue.main.async {
@@ -164,7 +200,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
164
200
  }
165
201
  }
166
202
  }
167
-
203
+
204
+
168
205
  @objc func appWillEnterForeground() {
169
206
  if self.isInitialized {
170
207
  DispatchQueue.main.async {
@@ -292,6 +329,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
292
329
  self.storeToFile = call.getBool("storeToFile") ?? false
293
330
  self.enableZoom = call.getBool("enableZoom") ?? false
294
331
  self.disableAudio = call.getBool("disableAudio") ?? true
332
+ self.aspectRatio = call.getString("aspectRatio")
333
+ let gridMode = call.getString("gridMode") ?? "none"
295
334
 
296
335
  AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) in
297
336
  guard granted else {
@@ -303,18 +342,18 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
303
342
  if self.cameraController.captureSession?.isRunning ?? false {
304
343
  call.reject("camera already started")
305
344
  } else {
306
- self.cameraController.prepare(cameraPosition: self.cameraPosition, deviceId: deviceId, disableAudio: self.disableAudio, cameraMode: cameraMode) {error in
345
+ self.cameraController.prepare(cameraPosition: self.cameraPosition, deviceId: deviceId, disableAudio: self.disableAudio, cameraMode: cameraMode, aspectRatio: self.aspectRatio) {error in
307
346
  if let error = error {
308
347
  print(error)
309
348
  call.reject(error.localizedDescription)
310
349
  return
311
350
  }
312
- let height = self.paddingBottom != nil ? self.height! - self.paddingBottom!: self.height!
313
- self.previewView = UIView(frame: CGRect(x: self.posX ?? 0, y: self.posY ?? 0, width: self.width!, height: height))
351
+ self.updateCameraFrame()
314
352
 
315
353
  // Make webview transparent - comprehensive approach
316
354
  self.makeWebViewTransparent()
317
-
355
+
356
+
318
357
  self.webView?.superview?.addSubview(self.previewView)
319
358
  if self.toBack! {
320
359
  self.webView?.superview?.bringSubviewToFront(self.webView!)
@@ -324,17 +363,34 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
324
363
  let frontView = self.toBack! ? self.webView : self.previewView
325
364
  self.cameraController.setupGestures(target: frontView ?? self.previewView, enableZoom: self.enableZoom!)
326
365
 
366
+ // Add grid overlay if enabled
367
+ if gridMode != "none" {
368
+ self.cameraController.addGridOverlay(to: self.previewView, gridMode: gridMode)
369
+ }
370
+
371
+ // Add grid overlay if enabled
372
+ if gridMode != "none" {
373
+ self.cameraController.addGridOverlay(to: self.previewView, gridMode: gridMode)
374
+ }
375
+
327
376
  if self.rotateWhenOrientationChanged == true {
328
377
  NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
329
378
  }
330
-
379
+
380
+
331
381
  // Add observers for app state changes to maintain transparency
332
382
  NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
333
383
  NotificationCenter.default.addObserver(self, selector: #selector(CameraPreview.appWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
334
384
 
335
385
  self.isInitializing = false
336
386
  self.isInitialized = true
337
- call.resolve()
387
+
388
+ var returnedObject = JSObject()
389
+ returnedObject["width"] = self.previewView.frame.width as any JSValue
390
+ returnedObject["height"] = self.previewView.frame.height as any JSValue
391
+ returnedObject["x"] = self.previewView.frame.origin.x as any JSValue
392
+ returnedObject["y"] = self.previewView.frame.origin.y as any JSValue
393
+ call.resolve(returnedObject)
338
394
 
339
395
  }
340
396
  }
@@ -371,10 +427,12 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
371
427
  self.cameraController.previewLayer?.frame = self.previewView.bounds
372
428
  self.cameraController.previewLayer?.videoGravity = .resizeAspectFill
373
429
  self.previewView.isUserInteractionEnabled = true
374
-
430
+
431
+
375
432
  // Ensure webview remains transparent after flip
376
433
  self.makeWebViewTransparent()
377
-
434
+
435
+
378
436
  call.resolve()
379
437
  }
380
438
  } catch {
@@ -411,6 +469,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
411
469
  }
412
470
 
413
471
  // Always attempt to stop and clean up, regardless of captureSession state
472
+ self.cameraController.removeGridOverlay()
414
473
  if let previewView = self.previewView {
415
474
  previewView.removeFromSuperview()
416
475
  self.previewView = nil
@@ -420,7 +479,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
420
479
  self.isInitialized = false
421
480
  self.isInitializing = false
422
481
  self.cameraController.cleanup()
423
-
482
+
483
+
424
484
  // Remove notification observers
425
485
  NotificationCenter.default.removeObserver(self)
426
486
 
@@ -465,7 +525,7 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
465
525
  PHAssetChangeRequest.creationRequestForAsset(from: image!)
466
526
  }, completionHandler: { (success, error) in
467
527
  if !success {
468
- Logger.error("CameraPreview", "Error saving image to gallery", error)
528
+ print("CameraPreview: Error saving image to gallery: \(error?.localizedDescription ?? "Unknown error")")
469
529
  }
470
530
  })
471
531
  }
@@ -474,10 +534,12 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
474
534
  call.reject("Failed to get JPEG data from image")
475
535
  return
476
536
  }
477
-
537
+
538
+
478
539
  let exifData = self.getExifData(from: imageData)
479
540
  let base64Image = imageData.base64EncodedString()
480
-
541
+
542
+
481
543
  var result = JSObject()
482
544
  result["value"] = base64Image
483
545
  result["exif"] = exifData
@@ -492,12 +554,28 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
492
554
  let exifDict = imageProperties[kCGImagePropertyExifDictionary as String] as? [String: Any] else {
493
555
  return [:]
494
556
  }
495
-
557
+
558
+
496
559
  var exifData = JSObject()
497
560
  for (key, value) in exifDict {
498
- exifData[key] = value
561
+ // Convert value to JSValue-compatible type
562
+ if let stringValue = value as? String {
563
+ exifData[key] = stringValue
564
+ } else if let numberValue = value as? NSNumber {
565
+ exifData[key] = numberValue
566
+ } else if let boolValue = value as? Bool {
567
+ exifData[key] = boolValue
568
+ } else if let arrayValue = value as? [Any] {
569
+ exifData[key] = arrayValue
570
+ } else if let dictValue = value as? [String: Any] {
571
+ exifData[key] = JSObject(_immutableCocoaDictionary: NSMutableDictionary(dictionary: dictValue))
572
+ } else {
573
+ // Convert other types to string as fallback
574
+ exifData[key] = String(describing: value)
575
+ }
499
576
  }
500
-
577
+
578
+
501
579
  return exifData
502
580
  }
503
581
 
@@ -640,9 +718,11 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
640
718
  // Collect all devices by position
641
719
  for device in session.devices {
642
720
  var lenses: [[String: Any]] = []
643
-
721
+
722
+
644
723
  let constituentDevices = device.isVirtualDevice ? device.constituentDevices : [device]
645
-
724
+
725
+
646
726
  for lensDevice in constituentDevices {
647
727
  var deviceType: String
648
728
  switch lensDevice.deviceType {
@@ -662,7 +742,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
662
742
  } else if lensDevice.deviceType == .builtInTelephotoCamera {
663
743
  baseZoomRatio = 2.0 // A common value for telephoto lenses
664
744
  }
665
-
745
+
746
+
666
747
  let lensInfo: [String: Any] = [
667
748
  "label": lensDevice.localizedName,
668
749
  "deviceType": deviceType,
@@ -673,7 +754,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
673
754
  ]
674
755
  lenses.append(lensInfo)
675
756
  }
676
-
757
+
758
+
677
759
  let deviceData: [String: Any] = [
678
760
  "deviceId": device.uniqueID,
679
761
  "label": device.localizedName,
@@ -683,7 +765,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
683
765
  "maxZoom": Float(device.maxAvailableVideoZoomFactor),
684
766
  "isLogical": device.isVirtualDevice
685
767
  ]
686
-
768
+
769
+
687
770
  devices.append(deviceData)
688
771
  }
689
772
 
@@ -699,7 +782,8 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
699
782
  do {
700
783
  let zoomInfo = try self.cameraController.getZoom()
701
784
  let lensInfo = try self.cameraController.getCurrentLensInfo()
702
-
785
+
786
+
703
787
  var minZoom = zoomInfo.min
704
788
  var maxZoom = zoomInfo.max
705
789
  var currentZoom = zoomInfo.current
@@ -795,10 +879,12 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
795
879
  self.cameraController.previewLayer?.frame = self.previewView.bounds
796
880
  self.cameraController.previewLayer?.videoGravity = .resizeAspectFill
797
881
  self.previewView.isUserInteractionEnabled = true
798
-
882
+
883
+
799
884
  // Ensure webview remains transparent after device switch
800
885
  self.makeWebViewTransparent()
801
-
886
+
887
+
802
888
  call.resolve()
803
889
  }
804
890
  } catch {
@@ -825,14 +911,46 @@ public class CameraPreview: CAPPlugin, CAPBridgedPlugin {
825
911
  }
826
912
  }
827
913
 
828
- func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
914
+ public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
829
915
  self.currentLocation = locations.last
830
916
  self.locationManager?.stopUpdatingLocation()
831
917
  }
832
918
 
833
- func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
834
- Logger.error("CameraPreview", "Failed to get location", error)
919
+ public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
920
+ print("CameraPreview: Failed to get location: \(error.localizedDescription)")
835
921
  }
836
922
 
923
+ private func updateCameraFrame() {
924
+ guard let width = self.width, var height = self.height, let posX = self.posX, let posY = self.posY else { return }
925
+ let paddingBottom = self.paddingBottom ?? 0
926
+ height -= paddingBottom
927
+
928
+ var frame = CGRect(x: posX, y: posY, width: width, height: height)
837
929
 
930
+ if let aspectRatio = self.aspectRatio, aspectRatio != "fill" {
931
+ let ratioParts = aspectRatio.split(separator: ":").map { Double($0) ?? 1.0 }
932
+ let ratio = ratioParts[0] / ratioParts[1]
933
+ let viewWidth = Double(width)
934
+ let viewHeight = Double(height)
935
+
936
+ if viewWidth / ratio > viewHeight {
937
+ let newWidth = viewHeight * ratio
938
+ frame.origin.x += (viewWidth - newWidth) / 2
939
+ frame.size.width = newWidth
940
+ } else {
941
+ let newHeight = viewWidth / ratio
942
+ frame.origin.y += (viewHeight - newHeight) / 2
943
+ frame.size.height = newHeight
944
+ }
945
+ }
946
+
947
+ if self.previewView == nil {
948
+ self.previewView = UIView(frame: frame)
949
+ } else {
950
+ self.previewView.frame = frame
951
+ }
952
+
953
+ // Update the preview layer frame to match the preview view
954
+ self.cameraController.previewLayer?.frame = frame
955
+ }
838
956
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/camera-preview",
3
- "version": "7.4.0-beta.3",
3
+ "version": "7.4.0-beta.5",
4
4
  "description": "Camera preview",
5
5
  "license": "MIT",
6
6
  "repository": {