From dd8653caed734e7acbfcbfe9249813fe25e8af5d Mon Sep 17 00:00:00 2001 From: Andre Destro Date: Fri, 20 Mar 2026 18:29:04 +0000 Subject: [PATCH 1/4] feat: add IONCameraLib as pod and spm dependency --- CapacitorCamera.podspec | 1 + Package.swift | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CapacitorCamera.podspec b/CapacitorCamera.podspec index 83ea913..ce54ce8 100644 --- a/CapacitorCamera.podspec +++ b/CapacitorCamera.podspec @@ -13,5 +13,6 @@ Pod::Spec.new do |s| s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}', 'camera/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}' s.ios.deployment_target = '15.0' s.dependency 'Capacitor' + s.dependency 'IONCameraLib', spec='~> 1.0.0' s.swift_version = '5.1' end diff --git a/Package.swift b/Package.swift index 9dd603a..a38d7fd 100644 --- a/Package.swift +++ b/Package.swift @@ -10,14 +10,16 @@ let package = Package( targets: ["CameraPlugin"]) ], dependencies: [ - .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0") + .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0"), + .package(url: "https://github.com/ionic-team/ion-ios-camera.git", branch: "main") // TODO: update to a stable release when available ], targets: [ .target( name: "CameraPlugin", dependencies: [ .product(name: "Capacitor", package: "capacitor-swift-pm"), - .product(name: "Cordova", package: "capacitor-swift-pm") + .product(name: "Cordova", package: "capacitor-swift-pm"), + .product(name: "IONCameraLib", package: "ion-ios-camera") ], path: "ios/Sources/CameraPlugin"), .testTarget( From 734773bd69cab59473f2ef0bb00244e73a03bd9c Mon Sep 17 00:00:00 2001 From: Andre Destro Date: Sat, 21 Mar 2026 17:40:26 +0000 Subject: [PATCH 2/4] feat: handle lib result --- ios/Sources/CameraPlugin/CameraPlugin.swift | 79 +++++++++++++++++++-- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/ios/Sources/CameraPlugin/CameraPlugin.swift b/ios/Sources/CameraPlugin/CameraPlugin.swift index 251b8dd..1668635 100644 --- a/ios/Sources/CameraPlugin/CameraPlugin.swift +++ b/ios/Sources/CameraPlugin/CameraPlugin.swift @@ -1,4 +1,5 @@ import Foundation +import IONCameraLib import Capacitor import Photos import PhotosUI @@ -8,6 +9,12 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin { public let identifier = "CAPCameraPlugin" public let jsName = "Camera" public let pluginMethods: [CAPPluginMethod] = [ + CAPPluginMethod(name: "takePhoto", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "chooseFromGallery", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "editURIPhoto", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "editPhoto", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "recordVideo", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "playVideo", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "getPhoto", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "pickImages", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise), @@ -20,8 +27,26 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin { private let defaultSource = CameraSource.prompt private let defaultDirection = CameraDirection.rear private var multiple = false + + private lazy var cameraManager = IONCAMRFactory.createCameraManagerWrapper(withDelegate: self, and: self.bridge?.viewController) + private lazy var galleryManager = IONCAMRFactory.createGalleryManagerWrapper(withDelegate: self, and: self.bridge?.viewController) + private lazy var editManager = IONCAMRFactory.createEditManagerWrapper(withDelegate: self, and: self.bridge?.viewController) + private lazy var videoManager = IONCAMRFactory.createVideoManagerWrapper(withDelegate: self, and: self.bridge?.viewController) private var imageCounter = 0 + + private func decodeParameters(from call: CAPPluginCall) -> T? { + guard let dict = call.options as? [String: Any], + let data = try? JSONSerialization.data(withJSONObject: dict) + else { return nil } + return try? JSONDecoder().decode(T.self, from: data) + } + + private func sendError(_ error: IONCAMRError) { + DispatchQueue.main.async { + self.call?.reject(error.localizedDescription, "OS-PLUG-CAMR-" + String(format: "%04d", error.errorCode)) + } + } @objc override public func checkPermissions(_ call: CAPPluginCall) { var result: [String: Any] = [:] @@ -126,6 +151,7 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin { } } + @available(*, deprecated, message: "Use takePhoto or chooseFromGallery instead") @objc func getPhoto(_ call: CAPPluginCall) { self.multiple = false self.call = call @@ -150,15 +176,18 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin { } } - @objc func pickImages(_ call: CAPPluginCall) { - self.multiple = true - self.call = call - self.settings = cameraSettings(from: call) + @objc public func takePhoto(_ call: CAPPluginCall) { + guard let options: IONCAMRTakePhotoOptions = decodeParameters(from: call) else { + sendError(IONCAMRError.takePictureArguments) + return + } + + DispatchQueue.main.async { - self.showPhotos() + self.showCamera() } } - + private func checkUsageDescriptions() -> String? { if let dict = Bundle.main.infoDictionary { for key in CameraPropertyListKeys.allCases where dict[key.rawValue] == nil { @@ -168,6 +197,16 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin { return nil } + @available(*, deprecated, message: "Use chooseFromGallery instead") + @objc func pickImages(_ call: CAPPluginCall) { + self.multiple = true + self.call = call + self.settings = cameraSettings(from: call) + DispatchQueue.main.async { + self.showPhotos() + } + } + private func cameraSettings(from call: CAPPluginCall) -> CameraSettings { var settings = CameraSettings() settings.jpegQuality = min(abs(CGFloat(call.getFloat("quality") ?? 100.0)) / 100.0, 1.0) @@ -551,3 +590,31 @@ private extension CameraPlugin { return result } } + +extension CameraPlugin: IONCAMRCallbackDelegate { + + public func callback(error: IONCAMRError) { + sendError(error) + } + + public func callback(result: IONCAMRMediaResult) { + resolve(result) + } + + public func callback(result: [IONCAMRMediaResult]) { + resolve(["results": result]) + } + + private func resolve(_ value: T) { + do { + let data = try JSONEncoder().encode(value) + let json = try JSONSerialization.jsonObject(with: data) + + DispatchQueue.main.async { + self.call?.resolve(json as? [String: Any] ?? [:]) + } + } catch { + sendError(.invalidEncodeResultMedia) + } + } +} From c6be5a27d9c470e6a9f4e4e315544733441f40a4 Mon Sep 17 00:00:00 2001 From: Andre Destro Date: Mon, 23 Mar 2026 11:07:43 +0000 Subject: [PATCH 3/4] feat: implement ios lib calls --- ios/Sources/CameraPlugin/CameraPlugin.swift | 77 ++++++++++++++++----- 1 file changed, 61 insertions(+), 16 deletions(-) diff --git a/ios/Sources/CameraPlugin/CameraPlugin.swift b/ios/Sources/CameraPlugin/CameraPlugin.swift index 1668635..79179f3 100644 --- a/ios/Sources/CameraPlugin/CameraPlugin.swift +++ b/ios/Sources/CameraPlugin/CameraPlugin.swift @@ -28,10 +28,10 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin { private let defaultDirection = CameraDirection.rear private var multiple = false - private lazy var cameraManager = IONCAMRFactory.createCameraManagerWrapper(withDelegate: self, and: self.bridge?.viewController) - private lazy var galleryManager = IONCAMRFactory.createGalleryManagerWrapper(withDelegate: self, and: self.bridge?.viewController) - private lazy var editManager = IONCAMRFactory.createEditManagerWrapper(withDelegate: self, and: self.bridge?.viewController) - private lazy var videoManager = IONCAMRFactory.createVideoManagerWrapper(withDelegate: self, and: self.bridge?.viewController) + private lazy var cameraManager = IONCAMRFactory.createCameraManagerWrapper(withDelegate: self, and: self.bridge?.viewController ?? UIViewController()) + private lazy var galleryManager = IONCAMRFactory.createGalleryManagerWrapper(withDelegate: self, and: self.bridge?.viewController ?? UIViewController()) + private lazy var editManager = IONCAMRFactory.createEditManagerWrapper(withDelegate: self, and: self.bridge?.viewController ?? UIViewController()) + private lazy var videoManager = IONCAMRFactory.createVideoManagerWrapper(withDelegate: self, and: self.bridge?.viewController ?? UIViewController()) private var imageCounter = 0 @@ -48,6 +48,63 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin { } } + private func handleCall(_ call: CAPPluginCall, error: IONCAMRError, action: @escaping (T) -> Void) { + self.call = call + guard let options: T = decodeParameters(from: call) else { + sendError(error) + return + } + DispatchQueue.main.async { + action(options) + } + } + + @objc func takePhoto(_ call: CAPPluginCall) { + handleCall(call, error: .takePictureArguments) { (options: IONCAMRTakePhotoOptions) in + self.cameraManager.takePhoto(with: options) + } + } + + @objc func chooseFromGallery(_ call: CAPPluginCall) { + handleCall(call, error: .chooseMultimediaIssue) { (options: IONCAMRGalleryOptions) in + self.galleryManager.chooseFromGallery(with: options) + } + } + + @objc func editURIPhoto(_ call: CAPPluginCall) { + handleCall(call, error: .editPictureIssue) { (options: IONCAMRPhotoEditOptions) in + self.editManager.editPhoto(with: options) + } + } + + @objc func editPhoto(_ call: CAPPluginCall) { + handleCall(call, error: .editPictureIssue) { (options: IONCAMRPhotoEditOptions) in + self.editManager.editPhoto(with: options) + } + } + + @objc func recordVideo(_ call: CAPPluginCall) { + handleCall(call, error: .captureVideoIssue) { (options: IONCAMRRecordVideoOptions) in + self.cameraManager.recordVideo(with: options) + } + } + + @objc func playVideo(_ call: CAPPluginCall) { + handleCall(call, error: .playVideoIssue) { (options: IONCAMRPlayVideoOptions) in + Task { + do { + try await self.videoManager.playVideo(options.url) + call.resolve() + } catch let error as IONCAMRError { + self.callback(error: error) + } catch { + self.callback(error: .playVideoIssue) + } + } + } + } + + @objc override public func checkPermissions(_ call: CAPPluginCall) { var result: [String: Any] = [:] for permission in CameraPermissionType.allCases { @@ -176,18 +233,6 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin { } } - @objc public func takePhoto(_ call: CAPPluginCall) { - guard let options: IONCAMRTakePhotoOptions = decodeParameters(from: call) else { - sendError(IONCAMRError.takePictureArguments) - return - } - - - DispatchQueue.main.async { - self.showCamera() - } - } - private func checkUsageDescriptions() -> String? { if let dict = Bundle.main.infoDictionary { for key in CameraPropertyListKeys.allCases where dict[key.rawValue] == nil { From df463eebcf6aa25b5cc36c07deda070cef6e7c59 Mon Sep 17 00:00:00 2001 From: Andre Destro Date: Mon, 23 Mar 2026 13:47:20 +0000 Subject: [PATCH 4/4] feat: add webPath to media result --- ios/Sources/CameraPlugin/CameraPlugin.swift | 37 +++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/ios/Sources/CameraPlugin/CameraPlugin.swift b/ios/Sources/CameraPlugin/CameraPlugin.swift index 79179f3..497ec30 100644 --- a/ios/Sources/CameraPlugin/CameraPlugin.swift +++ b/ios/Sources/CameraPlugin/CameraPlugin.swift @@ -653,8 +653,32 @@ extension CameraPlugin: IONCAMRCallbackDelegate { private func resolve(_ value: T) { do { let data = try JSONEncoder().encode(value) - let json = try JSONSerialization.jsonObject(with: data) - + var json = try JSONSerialization.jsonObject(with: data) + + // Add webPath to results + if var dict = json as? [String: Any] { + // Handle single result + if let uri = dict["uri"] as? String, + let webPath = resolveWebPath(from: uri) { + dict["webPath"] = webPath + } + + // Handle array of results + if var results = dict["results"] as? [[String: Any]] { + results = results.map { item in + var newItem = item + if let uri = item["uri"] as? String, + let webPath = resolveWebPath(from: uri) { + newItem["webPath"] = webPath + } + return newItem + } + dict["results"] = results + } + + json = dict + } + DispatchQueue.main.async { self.call?.resolve(json as? [String: Any] ?? [:]) } @@ -662,4 +686,13 @@ extension CameraPlugin: IONCAMRCallbackDelegate { sendError(.invalidEncodeResultMedia) } } + + private func resolveWebPath(from uri: String) -> String? { + guard !uri.isEmpty, + let fileURL = URL(string: uri), + let webURL = bridge?.portablePath(fromLocalURL: fileURL) else { + return nil + } + return webURL.absoluteString + } }