diff --git a/ios/ReactNativeCameraKit/CKCameraViewComponentView.h b/ios/ReactNativeCameraKit/CKCameraViewComponentView.h index 28ade05a9..bed45a13f 100644 --- a/ios/ReactNativeCameraKit/CKCameraViewComponentView.h +++ b/ios/ReactNativeCameraKit/CKCameraViewComponentView.h @@ -1,3 +1,8 @@ +// +// CKCameraViewComponentView.h +// ReactNativeCameraKit +// + #ifdef RCT_NEW_ARCH_ENABLED #import @@ -8,6 +13,7 @@ NS_ASSUME_NONNULL_BEGIN @interface CKCameraViewComponentView : RCTViewComponentView +- (facebook::react::SharedViewEventEmitter)eventEmitter; @end NS_ASSUME_NONNULL_END diff --git a/ios/ReactNativeCameraKit/CKCameraViewComponentView.mm b/ios/ReactNativeCameraKit/CKCameraViewComponentView.mm index 4339b5df2..7d5570a50 100644 --- a/ios/ReactNativeCameraKit/CKCameraViewComponentView.mm +++ b/ios/ReactNativeCameraKit/CKCameraViewComponentView.mm @@ -1,14 +1,20 @@ +// +// CKCameraViewComponentView.h +// ReactNativeCameraKit +// + #ifdef RCT_NEW_ARCH_ENABLED #import "CKCameraViewComponentView.h" +#import "NewArchCameraEventEmitter.h" + #import #import #import #import #import -#import #import #import @@ -64,6 +70,10 @@ @implementation CKCameraViewComponentView { CKCameraView *_view; } +- (facebook::react::SharedViewEventEmitter)eventEmitter { + return _eventEmitter; +} + // Needed because of this: https://github.com/facebook/react-native/pull/37274 + (void)load { @@ -83,53 +93,11 @@ - (instancetype)initWithFrame:(CGRect)frame - (void)prepareView { - _view = [[CKCameraView alloc] init]; - - // just need to pass something, it won't really be used on fabric, but it's used to create events (it won't impact sending them) + _view = [[CKCameraView alloc] init]; _view.reactTag = @-1; - __weak __typeof__(self) weakSelf = self; - - [_view setOnReadCode:^(NSDictionary* event) { - __typeof__(self) strongSelf = weakSelf; + _view.eventEmitter = [[NewArchCameraEventEmitter alloc] initWithCameraViewComponentView:self]; - if (strongSelf != nullptr && strongSelf->_eventEmitter != nullptr) { - std::string codeStringValue = [event valueForKey:@"codeStringValue"] == nil ? "" : std::string([[event valueForKey:@"codeStringValue"] UTF8String]); - std::string codeFormat = [event valueForKey:@"codeFormat"] == nil ? "" : std::string([[event valueForKey:@"codeFormat"] UTF8String]); - std::dynamic_pointer_cast(strongSelf->_eventEmitter)->onReadCode({.codeStringValue = codeStringValue, .codeFormat = codeFormat}); - } - }]; - [_view setOnOrientationChange:^(NSDictionary* event) { - __typeof__(self) strongSelf = weakSelf; - - if (strongSelf != nullptr && strongSelf->_eventEmitter != nullptr) { - id orientation = [event valueForKey:@"orientation"] == nil ? 0 : [event valueForKey:@"orientation"]; - std::dynamic_pointer_cast(strongSelf->_eventEmitter)->onOrientationChange({.orientation = [orientation intValue]}); - } - }]; - [_view setOnZoom:^(NSDictionary* event) { - __typeof__(self) strongSelf = weakSelf; - - if (strongSelf != nullptr && strongSelf->_eventEmitter != nullptr) { - id zoom = [event valueForKey:@"zoom"] == nil ? 0 : [event valueForKey:@"zoom"]; - std::dynamic_pointer_cast(strongSelf->_eventEmitter)->onZoom({.zoom = [zoom doubleValue]}); - } - }]; - [_view setOnCaptureButtonPressIn:^(NSDictionary* event) { - __typeof__(self) strongSelf = weakSelf; - - if (strongSelf != nullptr && strongSelf->_eventEmitter != nullptr) { - std::dynamic_pointer_cast(strongSelf->_eventEmitter)->onCaptureButtonPressIn({}); - } - }]; - [_view setOnCaptureButtonPressOut:^(NSDictionary* event) { - __typeof__(self) strongSelf = weakSelf; - - if (strongSelf != nullptr && strongSelf->_eventEmitter != nullptr) { - std::dynamic_pointer_cast(strongSelf->_eventEmitter)->onCaptureButtonPressOut({}); - } - }]; - self.contentView = _view; } @@ -137,7 +105,7 @@ - (void)prepareView + (ComponentDescriptorProvider)componentDescriptorProvider { - return concreteComponentDescriptorProvider(); + return concreteComponentDescriptorProvider(); } - (void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetrics oldLayoutMetrics:(const facebook::react::LayoutMetrics &)oldLayoutMetrics @@ -218,17 +186,18 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & [changedProps addObject:@"resetFocusWhenMotionDetected"]; } id focusMode = CKConvertFollyDynamicToId(newProps.focusMode); - if (focusMode != nil) { + if (focusMode != nil && ![focusMode isEqual: @""]) { _view.focusMode = [focusMode isEqualToString:@"on"] ? CKFocusModeOn : CKFocusModeOff; [changedProps addObject:@"focusMode"]; } id zoomMode = CKConvertFollyDynamicToId(newProps.zoomMode); - if (zoomMode != nil) { + if (zoomMode != nil && ![zoomMode isEqual: @""]) { _view.zoomMode = [zoomMode isEqualToString:@"on"] ? CKZoomModeOn : CKZoomModeOff; [changedProps addObject:@"zoomMode"]; } id zoom = CKConvertFollyDynamicToId(newProps.zoom); if (zoom != nil) { + NSLog(@"Zoom received: %@", zoom); _view.zoom = zoom; [changedProps addObject:@"zoom"]; } @@ -244,7 +213,6 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & [changedProps addObject:@"barcodeFrameSize"]; } - [super updateProps:props oldProps:oldProps]; [_view didSetProps:changedProps]; } @@ -259,7 +227,7 @@ - (void)prepareForRecycle Class CKCameraCls(void) { - return CKCameraViewComponentView.class; + return CKCameraViewComponentView.class; } #endif // RCT_NEW_ARCH_ENABLED diff --git a/ios/ReactNativeCameraKit/CameraEventEmitter.swift b/ios/ReactNativeCameraKit/CameraEventEmitter.swift new file mode 100644 index 000000000..6987fca7d --- /dev/null +++ b/ios/ReactNativeCameraKit/CameraEventEmitter.swift @@ -0,0 +1,55 @@ +// +// CameraEventEmitter.swift +// ReactNativeCameraKit +// + +@objc public protocol CameraEventEmitter { + @objc func onReadCode(codeStringValue: String, codeFormat: String) + @objc func onOrientationChange(orientation: Int) + @objc func onZoom(zoom: Double) + + @objc func onCaptureButtonPressIn() + @objc func onCaptureButtonPressOut() +} + +class OldArchCameraEventEmitter: CameraEventEmitter { + var onReadCodeProp: RCTDirectEventBlock? + var onOrientationChangeProp: RCTDirectEventBlock? + var onZoomProp: RCTDirectEventBlock? + var onCaptureButtonPressInProp: RCTDirectEventBlock? + var onCaptureButtonPressOutProp: RCTDirectEventBlock? + + init(onReadCodeProp: RCTDirectEventBlock?, + onOrientationChangeProp: RCTDirectEventBlock?, + onZoomProp: RCTDirectEventBlock?, + onCaptureButtonPressInProp: RCTDirectEventBlock?, + onCaptureButtonPressOutProp: RCTDirectEventBlock?) { + self.onReadCodeProp = onReadCodeProp + self.onOrientationChangeProp = onOrientationChangeProp + self.onZoomProp = onZoomProp + self.onCaptureButtonPressInProp = onCaptureButtonPressInProp + self.onCaptureButtonPressOutProp = onCaptureButtonPressOutProp + } + + func onReadCode(codeStringValue: String, codeFormat: String) { + onReadCodeProp?(["codeStringValue": codeStringValue, "codeFormat": codeFormat]) + } + + func onOrientationChange(orientation: Int) { + onOrientationChangeProp?(["orientation": orientation]) + } + + func onZoom(zoom: Double) { + print("onZoom") + print(onZoomProp.debugDescription) + onZoomProp?(["zoom": zoom]) + } + + func onCaptureButtonPressIn() { + onCaptureButtonPressInProp?(nil) + } + + func onCaptureButtonPressOut() { + onCaptureButtonPressOutProp?(nil) + } +} diff --git a/ios/ReactNativeCameraKit/CameraProtocol.swift b/ios/ReactNativeCameraKit/CameraProtocol.swift index 35bcbec50..8fd8fda37 100644 --- a/ios/ReactNativeCameraKit/CameraProtocol.swift +++ b/ios/ReactNativeCameraKit/CameraProtocol.swift @@ -11,11 +11,11 @@ protocol CameraProtocol: AnyObject, FocusInterfaceViewDelegate { func setup(cameraType: CameraType, supportedBarcodeType: [CodeFormat]) func cameraRemovedFromSuperview() + func update(eventEmitter: CameraEventEmitter?) + func update(torchMode: TorchMode) func update(flashMode: FlashMode) func update(cameraType: CameraType) - func update(onOrientationChange: RCTDirectEventBlock?) - func update(onZoom: RCTDirectEventBlock?) func update(zoom: Double?) func update(maxZoom: Double?) func update(resizeMode: ResizeMode) diff --git a/ios/ReactNativeCameraKit/CameraView.swift b/ios/ReactNativeCameraKit/CameraView.swift index c579e9b1d..bc6155626 100644 --- a/ios/ReactNativeCameraKit/CameraView.swift +++ b/ios/ReactNativeCameraKit/CameraView.swift @@ -15,6 +15,13 @@ import AVKit public class CameraView: UIView { private let camera: CameraProtocol + // expose event native -> react + @objc public var eventEmitter: CameraEventEmitter? { + didSet { + camera.update(eventEmitter: eventEmitter) + } + } + // Focus private let focusInterfaceView: FocusInterfaceView @@ -44,25 +51,25 @@ public class CameraView: UIView { // scanner @objc public var scanBarcode = false @objc public var showFrame = false - @objc public var onReadCode: RCTDirectEventBlock? @objc public var scanThrottleDelay = 2000 @objc public var frameColor: UIColor? @objc public var laserColor: UIColor? @objc public var barcodeFrameSize: NSDictionary? - // other - @objc public var onOrientationChange: RCTDirectEventBlock? - @objc public var onZoom: RCTDirectEventBlock? @objc public var resetFocusTimeout = 0 @objc public var resetFocusWhenMotionDetected = false @objc public var focusMode: FocusMode = .on @objc public var zoomMode: ZoomMode = .on @objc public var zoom: NSNumber? @objc public var maxZoom: NSNumber? - + // callbacks (only defined for old architecture) + @objc public var onReadCode: RCTDirectEventBlock? + @objc public var onOrientationChange: RCTDirectEventBlock? + @objc public var onZoom: RCTDirectEventBlock? @objc public var onCaptureButtonPressIn: RCTDirectEventBlock? @objc public var onCaptureButtonPressOut: RCTDirectEventBlock? + // interaction on physical volume button var eventInteraction: Any? = nil // MARK: - Setup @@ -84,9 +91,9 @@ public class CameraView: UIView { hasCameraBeenSetup = true #if targetEnvironment(macCatalyst) // Force front camera on Mac Catalyst during initial setup - camera.setup(cameraType: .front, supportedBarcodeType: scanBarcode && onReadCode != nil ? supportedBarcodeType : []) + camera.setup(cameraType: .front, supportedBarcodeType: scanBarcode ? supportedBarcodeType : []) #else - camera.setup(cameraType: cameraType, supportedBarcodeType: scanBarcode && onReadCode != nil ? supportedBarcodeType : []) + camera.setup(cameraType: cameraType, supportedBarcodeType: scanBarcode ? supportedBarcodeType : []) #endif } } @@ -153,13 +160,13 @@ public class CameraView: UIView { let interaction = AVCaptureEventInteraction { [weak self] event in // Capture a photo on "press up" of a hardware button. if event.phase == .began { - self?.onCaptureButtonPressIn?(nil) + self?.eventEmitter?.onCaptureButtonPressIn() } else if event.phase == .ended { - self?.onCaptureButtonPressOut?(nil) + self?.eventEmitter?.onCaptureButtonPressOut() } } // Add the interaction to the view controller's view. - self.addInteraction(interaction) + addInteraction(interaction) eventInteraction = interaction } #endif @@ -200,6 +207,18 @@ public class CameraView: UIView { override public func didSetProps(_ changedProps: [String]) { hasPropBeenSetup = true + // Initialized here for old architecture, initialized in CKCameraViewComponentView for new architecture + if (eventEmitter == nil) { + eventEmitter = OldArchCameraEventEmitter( + onReadCodeProp: onReadCode, + onOrientationChangeProp: onOrientationChange, + onZoomProp: onZoom, + onCaptureButtonPressInProp: onCaptureButtonPressIn, + onCaptureButtonPressOutProp: onCaptureButtonPressOut + ) + camera.update(eventEmitter: eventEmitter) + } + // Camera settings if changedProps.contains("cameraType") { #if targetEnvironment(macCatalyst) @@ -218,14 +237,6 @@ public class CameraView: UIView { if changedProps.contains("maxPhotoQualityPrioritization") { camera.update(maxPhotoQualityPrioritization: maxPhotoQualityPrioritization) } - - if changedProps.contains("onOrientationChange") { - camera.update(onOrientationChange: onOrientationChange) - } - - if changedProps.contains("onZoom") { - camera.update(onZoom: onZoom) - } if changedProps.contains("resizeMode") { camera.update(resizeMode: resizeMode) @@ -251,7 +262,7 @@ public class CameraView: UIView { } // Scanner - if changedProps.contains("scanBarcode") || changedProps.contains("onReadCode") { + if changedProps.contains("scanBarcode") || changedProps.contains("onReadCode") /* only old arch. */ { camera.isBarcodeScannerEnabled(scanBarcode, supportedBarcodeTypes: supportedBarcodeType, onBarcodeRead: { [weak self] (barcode, codeFormat) in @@ -436,7 +447,7 @@ public class CameraView: UIView { lastBarcodeDetectedTime = now - onReadCode?(["codeStringValue": barcode,"codeFormat":codeFormat.rawValue]) + eventEmitter?.onReadCode(codeStringValue: barcode, codeFormat: codeFormat.rawValue) } // MARK: - Gesture selectors diff --git a/ios/ReactNativeCameraKit/NewArchCameraEventEmitter.h b/ios/ReactNativeCameraKit/NewArchCameraEventEmitter.h new file mode 100644 index 000000000..49a6fddb0 --- /dev/null +++ b/ios/ReactNativeCameraKit/NewArchCameraEventEmitter.h @@ -0,0 +1,18 @@ +// +// NewArchCameraEventEmitter.h +// ReactNativeCameraKit +// + +#ifdef RCT_NEW_ARCH_ENABLED + +@class CKCameraViewComponentView; +@protocol CameraEventEmitter; + +/* This unfortunately needs to be in ObjectiveC since it's using C++ implementation */ +@interface NewArchCameraEventEmitter : NSObject + +- (instancetype)initWithCameraViewComponentView:(CKCameraViewComponentView *)viewComponentView; + +@end + +#endif // RCT_NEW_ARCH_ENABLED \ No newline at end of file diff --git a/ios/ReactNativeCameraKit/NewArchCameraEventEmitter.mm b/ios/ReactNativeCameraKit/NewArchCameraEventEmitter.mm new file mode 100644 index 000000000..fb62964c5 --- /dev/null +++ b/ios/ReactNativeCameraKit/NewArchCameraEventEmitter.mm @@ -0,0 +1,67 @@ +// +// NewArchCameraEventEmitter.mm +// ReactNativeCameraKit +// + +#ifdef RCT_NEW_ARCH_ENABLED + +#import "NewArchCameraEventEmitter.h" + +#import "CKCameraViewComponentView.h" +#import "ReactNativeCameraKit-Swift.h" +#import + +@interface NewArchCameraEventEmitter () { + __weak CKCameraViewComponentView *_viewComponentView; +} +@end + +@implementation NewArchCameraEventEmitter + +- (instancetype)initWithCameraViewComponentView:(CKCameraViewComponentView *)viewComponentView { + if (self = [super init]) { + _viewComponentView = viewComponentView; + } + return self; +} + +- (void)onReadCodeWithCodeStringValue:(NSString *)codeStringValue codeFormat:(NSString *)codeFormat { + if ([_viewComponentView eventEmitter] != nullptr) { + auto cameraEventEmitter = std::static_pointer_cast([_viewComponentView eventEmitter]); + cameraEventEmitter->onReadCode({.codeStringValue = [codeStringValue UTF8String], .codeFormat = [codeFormat UTF8String]}); + } +} + +- (void)onOrientationChangeWithOrientation:(NSInteger)orientation { + if ([_viewComponentView eventEmitter] != nullptr) { + auto cameraEventEmitter = std::static_pointer_cast([_viewComponentView eventEmitter]); + cameraEventEmitter->onOrientationChange({.orientation = (int)orientation}); + } +} + +- (void)onZoomWithZoom:(double)zoom { + // log zoom + NSLog(@"Zoom sent: %f", zoom); + if ([_viewComponentView eventEmitter] != nullptr) { + auto cameraEventEmitter = std::static_pointer_cast([_viewComponentView eventEmitter]); + cameraEventEmitter->onZoom({.zoom = zoom}); + } +} + +- (void)onCaptureButtonPressIn { + if ([_viewComponentView eventEmitter] != nullptr) { + auto cameraEventEmitter = std::static_pointer_cast([_viewComponentView eventEmitter]); + cameraEventEmitter->onCaptureButtonPressIn({}); + } +} + +- (void)onCaptureButtonPressOut { + if ([_viewComponentView eventEmitter] != nullptr) { + auto cameraEventEmitter = std::static_pointer_cast([_viewComponentView eventEmitter]); + cameraEventEmitter->onCaptureButtonPressOut({}); + } +} + +@end + +#endif // RCT_NEW_ARCH_ENABLED diff --git a/ios/ReactNativeCameraKit/RealCamera.swift b/ios/ReactNativeCameraKit/RealCamera.swift index 0f684b859..16fdc0917 100644 --- a/ios/ReactNativeCameraKit/RealCamera.swift +++ b/ios/ReactNativeCameraKit/RealCamera.swift @@ -15,6 +15,8 @@ import CoreMotion // swiftlint:disable:next type_body_length class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelegate { var previewView: UIView { cameraPreview } + + private weak var eventEmitter: CameraEventEmitter? private let cameraPreview = RealPreviewView(frame: .zero) private let session = AVCaptureSession() @@ -39,8 +41,6 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega private var onBarcodeRead: ((_ barcode: String,_ codeFormat : CodeFormat) -> Void)? private var scannerFrameSize: CGRect? = nil private var barcodeFrameSize: CGSize? = nil - private var onOrientationChange: RCTDirectEventBlock? - private var onZoomCallback: RCTDirectEventBlock? private var lastOnZoom: Double? private var zoom: Double? private var maxZoom: Double? @@ -164,21 +164,28 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega } } + func update(eventEmitter: CameraEventEmitter?) { + self.eventEmitter = eventEmitter + } + func update(maxZoom: Double?) { self.maxZoom = maxZoom // Re-update zoom value in case the max was increased + print("maxZoom updated!? \(maxZoom) update zoom with \(self.zoom)") self.update(zoom: self.zoom) } func update(zoom: Double?) { + self.zoom = zoom sessionQueue.async { - self.zoom = zoom + print("Zoom updating to \(zoom)") guard let videoDevice = self.videoDeviceInput?.device else { return } guard let zoom else { return } let zoomForDevice = self.getValidZoom(forDevice: videoDevice, zoom: zoom) self.setZoomFor(videoDevice, to: zoomForDevice) + print("Zoom updated to \(zoom)") } } @@ -200,11 +207,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega } lastOnZoom = desiredOrCameraZoom - self.onZoomCallback?(["zoom": desiredOrCameraZoom]) - } - - func update(onZoom: RCTDirectEventBlock?) { - self.onZoomCallback = onZoom + self.eventEmitter?.onZoom(zoom: desiredOrCameraZoom) } func focus(at touchPoint: CGPoint, focusBehavior: FocusBehavior) { @@ -245,10 +248,6 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega } } - func update(onOrientationChange: RCTDirectEventBlock?) { - self.onOrientationChange = onOrientationChange - } - func update(torchMode: TorchMode) { sessionQueue.async { self.torchMode = torchMode @@ -625,7 +624,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega } self.deviceOrientation = newOrientation - self.onOrientationChange?(["orientation": Orientation.init(from: newOrientation)!.rawValue]) + self.eventEmitter?.onOrientationChange(orientation: Orientation.init(from: newOrientation)!.rawValue) }) #endif } diff --git a/ios/ReactNativeCameraKit/SimulatorCamera.swift b/ios/ReactNativeCameraKit/SimulatorCamera.swift index 854311553..25984b3a0 100644 --- a/ios/ReactNativeCameraKit/SimulatorCamera.swift +++ b/ios/ReactNativeCameraKit/SimulatorCamera.swift @@ -10,8 +10,8 @@ import UIKit * Fake camera implementation to be used on simulator */ class SimulatorCamera: CameraProtocol { - private var onOrientationChange: RCTDirectEventBlock? - private var onZoom: RCTDirectEventBlock? + private weak var eventEmitter: CameraEventEmitter? + private var videoDeviceZoomFactor: Double = 1.0 private var videoDeviceMaxAvailableVideoZoomFactor: Double = 150.0 private var wideAngleZoomFactor: Double = 2.0 @@ -49,7 +49,7 @@ class SimulatorCamera: CameraProtocol { return } - self.onOrientationChange?(["orientation": orientation.rawValue]) + self.eventEmitter?.onOrientationChange(orientation: orientation.rawValue) } func cameraRemovedFromSuperview() { @@ -57,12 +57,8 @@ class SimulatorCamera: CameraProtocol { } - func update(onOrientationChange: RCTDirectEventBlock?) { - self.onOrientationChange = onOrientationChange - } - - func update(onZoom: RCTDirectEventBlock?) { - self.onZoom = onZoom + func update(eventEmitter: CameraEventEmitter?) { + self.eventEmitter = eventEmitter } func setVideoDevice(zoomFactor: Double) { @@ -74,7 +70,6 @@ class SimulatorCamera: CameraProtocol { func zoomPinchStart() { DispatchQueue.main.async { self.zoomStartedAt = self.videoDeviceZoomFactor - self.mockPreview.zoomLabel.text = "Zoom start" } } @@ -95,7 +90,7 @@ class SimulatorCamera: CameraProtocol { if self.zoom == nil { self.setVideoDevice(zoomFactor: zoomForDevice) } - self.onZoom?(["zoom": zoomForDevice]) + self.eventEmitter?.onZoom(zoom: zoomForDevice) } } } @@ -160,7 +155,7 @@ class SimulatorCamera: CameraProtocol { // If they wanted to reset, tell them what the default zoom turned out to be // regardless if it's controlled if self.zoom == nil || zoom == 0 { - self.onZoom?(["zoom": zoomForDevice]) + self.eventEmitter?.onZoom(zoom: zoomForDevice) } } }