Skip to content

Commit 103dad2

Browse files
authored
Merge pull request #20 from ionic-team/feat/RMET-5025/ios-unification
feat(ios): ios plugin implementation
2 parents 86badce + 1c8fe8f commit 103dad2

3 files changed

Lines changed: 159 additions & 11 deletions

File tree

CapacitorCamera.podspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ Pod::Spec.new do |s|
1313
s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}', 'camera/ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
1414
s.ios.deployment_target = '15.0'
1515
s.dependency 'Capacitor'
16+
s.dependency 'IONCameraLib', spec='~> 1.0.0'
1617
s.swift_version = '5.1'
1718
end

Package.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ let package = Package(
1010
targets: ["CameraPlugin"])
1111
],
1212
dependencies: [
13-
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0")
13+
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0"),
14+
.package(url: "https://github.com/ionic-team/ion-ios-camera.git", branch: "main") // TODO: update to a stable release when available
1415
],
1516
targets: [
1617
.target(
1718
name: "CameraPlugin",
1819
dependencies: [
1920
.product(name: "Capacitor", package: "capacitor-swift-pm"),
20-
.product(name: "Cordova", package: "capacitor-swift-pm")
21+
.product(name: "Cordova", package: "capacitor-swift-pm"),
22+
.product(name: "IONCameraLib", package: "ion-ios-camera")
2123
],
2224
path: "ios/Sources/CameraPlugin"),
2325
.testTarget(

ios/Sources/CameraPlugin/CameraPlugin.swift

Lines changed: 154 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Foundation
2+
import IONCameraLib
23
import Capacitor
34
import Photos
45
import PhotosUI
@@ -8,6 +9,12 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin {
89
public let identifier = "CAPCameraPlugin"
910
public let jsName = "Camera"
1011
public let pluginMethods: [CAPPluginMethod] = [
12+
CAPPluginMethod(name: "takePhoto", returnType: CAPPluginReturnPromise),
13+
CAPPluginMethod(name: "chooseFromGallery", returnType: CAPPluginReturnPromise),
14+
CAPPluginMethod(name: "editURIPhoto", returnType: CAPPluginReturnPromise),
15+
CAPPluginMethod(name: "editPhoto", returnType: CAPPluginReturnPromise),
16+
CAPPluginMethod(name: "recordVideo", returnType: CAPPluginReturnPromise),
17+
CAPPluginMethod(name: "playVideo", returnType: CAPPluginReturnPromise),
1118
CAPPluginMethod(name: "getPhoto", returnType: CAPPluginReturnPromise),
1219
CAPPluginMethod(name: "pickImages", returnType: CAPPluginReturnPromise),
1320
CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise),
@@ -20,8 +27,83 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin {
2027
private let defaultSource = CameraSource.prompt
2128
private let defaultDirection = CameraDirection.rear
2229
private var multiple = false
30+
31+
private lazy var cameraManager = IONCAMRFactory.createCameraManagerWrapper(withDelegate: self, and: self.bridge?.viewController ?? UIViewController())
32+
private lazy var galleryManager = IONCAMRFactory.createGalleryManagerWrapper(withDelegate: self, and: self.bridge?.viewController ?? UIViewController())
33+
private lazy var editManager = IONCAMRFactory.createEditManagerWrapper(withDelegate: self, and: self.bridge?.viewController ?? UIViewController())
34+
private lazy var videoManager = IONCAMRFactory.createVideoManagerWrapper(withDelegate: self, and: self.bridge?.viewController ?? UIViewController())
2335

2436
private var imageCounter = 0
37+
38+
private func decodeParameters<T: Decodable>(from call: CAPPluginCall) -> T? {
39+
guard let dict = call.options as? [String: Any],
40+
let data = try? JSONSerialization.data(withJSONObject: dict)
41+
else { return nil }
42+
return try? JSONDecoder().decode(T.self, from: data)
43+
}
44+
45+
private func sendError(_ error: IONCAMRError) {
46+
DispatchQueue.main.async {
47+
self.call?.reject(error.localizedDescription, "OS-PLUG-CAMR-" + String(format: "%04d", error.errorCode))
48+
}
49+
}
50+
51+
private func handleCall<T: Decodable>(_ call: CAPPluginCall, error: IONCAMRError, action: @escaping (T) -> Void) {
52+
self.call = call
53+
guard let options: T = decodeParameters(from: call) else {
54+
sendError(error)
55+
return
56+
}
57+
DispatchQueue.main.async {
58+
action(options)
59+
}
60+
}
61+
62+
@objc func takePhoto(_ call: CAPPluginCall) {
63+
handleCall(call, error: .takePictureArguments) { (options: IONCAMRTakePhotoOptions) in
64+
self.cameraManager.takePhoto(with: options)
65+
}
66+
}
67+
68+
@objc func chooseFromGallery(_ call: CAPPluginCall) {
69+
handleCall(call, error: .chooseMultimediaIssue) { (options: IONCAMRGalleryOptions) in
70+
self.galleryManager.chooseFromGallery(with: options)
71+
}
72+
}
73+
74+
@objc func editURIPhoto(_ call: CAPPluginCall) {
75+
handleCall(call, error: .editPictureIssue) { (options: IONCAMRPhotoEditOptions) in
76+
self.editManager.editPhoto(with: options)
77+
}
78+
}
79+
80+
@objc func editPhoto(_ call: CAPPluginCall) {
81+
handleCall(call, error: .editPictureIssue) { (options: IONCAMRPhotoEditOptions) in
82+
self.editManager.editPhoto(with: options)
83+
}
84+
}
85+
86+
@objc func recordVideo(_ call: CAPPluginCall) {
87+
handleCall(call, error: .captureVideoIssue) { (options: IONCAMRRecordVideoOptions) in
88+
self.cameraManager.recordVideo(with: options)
89+
}
90+
}
91+
92+
@objc func playVideo(_ call: CAPPluginCall) {
93+
handleCall(call, error: .playVideoIssue) { (options: IONCAMRPlayVideoOptions) in
94+
Task {
95+
do {
96+
try await self.videoManager.playVideo(options.url)
97+
call.resolve()
98+
} catch let error as IONCAMRError {
99+
self.callback(error: error)
100+
} catch {
101+
self.callback(error: .playVideoIssue)
102+
}
103+
}
104+
}
105+
}
106+
25107

26108
@objc override public func checkPermissions(_ call: CAPPluginCall) {
27109
var result: [String: Any] = [:]
@@ -126,6 +208,7 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin {
126208
}
127209
}
128210

211+
@available(*, deprecated, message: "Use takePhoto or chooseFromGallery instead")
129212
@objc func getPhoto(_ call: CAPPluginCall) {
130213
self.multiple = false
131214
self.call = call
@@ -150,15 +233,6 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin {
150233
}
151234
}
152235

153-
@objc func pickImages(_ call: CAPPluginCall) {
154-
self.multiple = true
155-
self.call = call
156-
self.settings = cameraSettings(from: call)
157-
DispatchQueue.main.async {
158-
self.showPhotos()
159-
}
160-
}
161-
162236
private func checkUsageDescriptions() -> String? {
163237
if let dict = Bundle.main.infoDictionary {
164238
for key in CameraPropertyListKeys.allCases where dict[key.rawValue] == nil {
@@ -168,6 +242,16 @@ public class CameraPlugin: CAPPlugin, CAPBridgedPlugin {
168242
return nil
169243
}
170244

245+
@available(*, deprecated, message: "Use chooseFromGallery instead")
246+
@objc func pickImages(_ call: CAPPluginCall) {
247+
self.multiple = true
248+
self.call = call
249+
self.settings = cameraSettings(from: call)
250+
DispatchQueue.main.async {
251+
self.showPhotos()
252+
}
253+
}
254+
171255
private func cameraSettings(from call: CAPPluginCall) -> CameraSettings {
172256
var settings = CameraSettings()
173257
settings.jpegQuality = min(abs(CGFloat(call.getFloat("quality") ?? 100.0)) / 100.0, 1.0)
@@ -551,3 +635,64 @@ private extension CameraPlugin {
551635
return result
552636
}
553637
}
638+
639+
extension CameraPlugin: IONCAMRCallbackDelegate {
640+
641+
public func callback(error: IONCAMRError) {
642+
sendError(error)
643+
}
644+
645+
public func callback(result: IONCAMRMediaResult) {
646+
resolve(result)
647+
}
648+
649+
public func callback(result: [IONCAMRMediaResult]) {
650+
resolve(["results": result])
651+
}
652+
653+
private func resolve<T: Encodable>(_ value: T) {
654+
do {
655+
let data = try JSONEncoder().encode(value)
656+
var json = try JSONSerialization.jsonObject(with: data)
657+
658+
// Add webPath to results
659+
if var dict = json as? [String: Any] {
660+
// Handle single result
661+
if let uri = dict["uri"] as? String,
662+
let webPath = resolveWebPath(from: uri) {
663+
dict["webPath"] = webPath
664+
}
665+
666+
// Handle array of results
667+
if var results = dict["results"] as? [[String: Any]] {
668+
results = results.map { item in
669+
var newItem = item
670+
if let uri = item["uri"] as? String,
671+
let webPath = resolveWebPath(from: uri) {
672+
newItem["webPath"] = webPath
673+
}
674+
return newItem
675+
}
676+
dict["results"] = results
677+
}
678+
679+
json = dict
680+
}
681+
682+
DispatchQueue.main.async {
683+
self.call?.resolve(json as? [String: Any] ?? [:])
684+
}
685+
} catch {
686+
sendError(.invalidEncodeResultMedia)
687+
}
688+
}
689+
690+
private func resolveWebPath(from uri: String) -> String? {
691+
guard !uri.isEmpty,
692+
let fileURL = URL(string: uri),
693+
let webURL = bridge?.portablePath(fromLocalURL: fileURL) else {
694+
return nil
695+
}
696+
return webURL.absoluteString
697+
}
698+
}

0 commit comments

Comments
 (0)