Skip to content

Commit 2376770

Browse files
authored
Merge branch 'master' into @wolewicki/add-new-arch
2 parents 09578bd + ace123e commit 2376770

14 files changed

+112
-28
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ Additionally, the Camera can be used for barcode scanning
195195
| `resetFocusWhenMotionDetected` | Boolean | Dismiss tap to focus when focus area content changes. Native iOS feature, see documentation: https://developer.apple.com/documentation/avfoundation/avcapturedevice/1624644-subjectareachangemonitoringenabl?language=objc). Default `true`. |
196196
| `resizeMode` | `'cover' / 'contain'` | Determines the scaling and cropping behavior of content within the view. `cover` (resizeAspectFill on iOS) scales the content to fill the view completely, potentially cropping content if its aspect ratio differs from the view. `contain` (resizeAspect on iOS) scales the content to fit within the view's bounds without cropping, ensuring all content is visible but may introduce letterboxing. Default behavior depends on the specific use case. |
197197
| `scanThrottleDelay` | `number` | Duration between scan detection in milliseconds. Default 2000 (2s) |
198+
| `maxPhotoQualityPrioritization` | `'balanced'` / `'quality'` / `'speed'` | [iOS 13 and newer](https://developer.apple.com/documentation/avfoundation/avcapturephotooutput/3182995-maxphotoqualityprioritization). `'speed'` provides a 60-80% median capture time reduction vs 'quality' setting. Tested on iPhone 6S Max (66% faster) and iPhone 15 Pro Max (76% faster!). Default `balanced` |
198199
| `onCaptureButtonPressIn` | Function | Callback when iPhone capture button is pressed in. Ex: `onCaptureButtonPressIn={() => console.log("volume button pressed in")}` |
199200
| `onCaptureButtonPressOut` | Function | Callback when iPhone capture button is released. Ex: `onCaptureButtonPressOut={() => console.log("volume button released")}` |
200201
| **Barcode only** |

ReactNativeCameraKit.podspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
55
Pod::Spec.new do |s|
66
s.name = "ReactNativeCameraKit"
77
s.version = package["version"]
8-
s.summary = "Advanced native camera and gallery controls and device photos API"
8+
s.summary = "A high performance, easy to use camera API"
99
s.license = "MIT"
1010

1111
s.authors = "CameraKit"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict/>
5+
</plist>

example/ios/CameraKitExample/Info.plist

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
<true/>
2727
<key>NSAppTransportSecurity</key>
2828
<dict>
29-
<!-- Do not change NSAllowsArbitraryLoads to true, or you will risk app rejection! -->
3029
<key>NSAllowsArbitraryLoads</key>
3130
<false/>
3231
<key>NSAllowsLocalNetworking</key>
@@ -50,6 +49,8 @@
5049
<string>UIInterfaceOrientationLandscapeLeft</string>
5150
<string>UIInterfaceOrientationLandscapeRight</string>
5251
</array>
52+
<key>UIFileSharingEnabled</key>
53+
<true/>
5354
<key>UIViewControllerBasedStatusBarAppearance</key>
5455
<false/>
5556
</dict>

example/src/CameraExample.tsx

+39-21
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useState, useRef } from 'react';
2-
import { StyleSheet, Text, View, TouchableOpacity, Image, SafeAreaView, Animated, StatusBar } from 'react-native';
2+
import { StyleSheet, Text, View, TouchableOpacity, Image, SafeAreaView, Animated, StatusBar, ScrollView } from 'react-native';
33
import Camera from '../../src/Camera';
44
import { CameraApi, CameraType, CaptureData } from '../../src/types';
55
import { Orientation } from '../../src';
@@ -25,6 +25,12 @@ const flashArray = [
2525
},
2626
] as const;
2727

28+
function median(values: number[]): number {
29+
values = [...values].sort((a, b) => a - b);
30+
const half = Math.floor(values.length / 2);
31+
return values.length % 2 ? values[half] : (values[half - 1] + values[half]) / 2;
32+
}
33+
2834
const CameraExample = ({ onBack }: { onBack: () => void }) => {
2935
const cameraRef = useRef<CameraApi>(null);
3036
const [currentFlashArrayPosition, setCurrentFlashArrayPosition] = useState(0);
@@ -79,25 +85,31 @@ const CameraExample = ({ onBack }: { onBack: () => void }) => {
7985
};
8086

8187
const onCaptureImagePressed = async () => {
82-
if (showImageUri) {
83-
setShowImageUri('');
84-
return;
85-
}
86-
if (!cameraRef.current || isCapturing.current) return;
87-
let image: CaptureData | undefined;
88-
try {
89-
isCapturing.current = true;
90-
image = await cameraRef.current.capture();
91-
} catch (e) {
92-
console.log('error', e);
93-
} finally {
94-
isCapturing.current = false;
95-
}
96-
if (!image) return;
88+
const times: number[] = [];
89+
for (let i = 1; i <= 5; i++) {
90+
const start = Date.now();
91+
if (showImageUri) {
92+
setShowImageUri('');
93+
return;
94+
}
95+
if (!cameraRef.current || isCapturing.current) return;
96+
let image: CaptureData | undefined;
97+
try {
98+
isCapturing.current = true;
99+
image = await cameraRef.current.capture();
100+
} catch (e) {
101+
console.log('error', e);
102+
} finally {
103+
isCapturing.current = false;
104+
}
105+
if (!image) return;
97106

98-
setCaptured(true);
99-
setCaptureImages([...captureImages, image]);
100-
console.log('image', image);
107+
setCaptured(true);
108+
setCaptureImages(prev => [...prev, image]);
109+
console.log('image', image);
110+
times.push(Date.now() - start);
111+
}
112+
console.log(`median capture time: ${median(times)}ms`);
101113
};
102114

103115
function CaptureButton({ onPress, children }: { onPress: () => void; children?: React.ReactNode }) {
@@ -202,7 +214,12 @@ const CameraExample = ({ onBack }: { onBack: () => void }) => {
202214

203215
<View style={styles.cameraContainer}>
204216
{showImageUri ? (
205-
<Image source={{ uri: showImageUri }} style={styles.cameraPreview} resizeMode="contain" />
217+
<ScrollView
218+
maximumZoomScale={10}
219+
contentContainerStyle={{ flexGrow: 1 }}
220+
>
221+
<Image source={{ uri: showImageUri }} style={styles.cameraPreview} />
222+
</ScrollView>
206223
) : (
207224
<Camera
208225
ref={cameraRef}
@@ -219,6 +236,7 @@ const CameraExample = ({ onBack }: { onBack: () => void }) => {
219236
}}
220237
torchMode={torchMode ? 'on' : 'off'}
221238
shutterPhotoSound
239+
maxPhotoQualityPrioritization="quality"
222240
onCaptureButtonPressIn={() => {
223241
console.log('capture button pressed in');
224242
}}
@@ -322,8 +340,8 @@ const styles = StyleSheet.create({
322340
flex: 1,
323341
},
324342
cameraPreview: {
325-
flex: 1,
326343
width: '100%',
344+
height: '100%',
327345
},
328346
bottomButtons: {
329347
margin: 10,

ios/ReactNativeCameraKit/CKCameraManager.m

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ @interface RCT_EXTERN_MODULE(CKCameraManager, RCTViewManager)
1717

1818
RCT_EXPORT_VIEW_PROPERTY(cameraType, CKCameraType)
1919
RCT_EXPORT_VIEW_PROPERTY(flashMode, CKFlashMode)
20+
RCT_EXPORT_VIEW_PROPERTY(maxPhotoQualityPrioritization, CKMaxPhotoQualityPrioritization)
2021
RCT_EXPORT_VIEW_PROPERTY(torchMode, CKTorchMode)
2122
RCT_EXPORT_VIEW_PROPERTY(ratioOverlay, NSString)
2223
RCT_EXPORT_VIEW_PROPERTY(ratioOverlayColor, UIColor)

ios/ReactNativeCameraKit/CKTypes+RCTConvert.m

+6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ @implementation RCTConvert (CKTypes)
2626
@"auto": @(CKFlashModeAuto)
2727
}), CKFlashModeAuto, integerValue)
2828

29+
RCT_ENUM_CONVERTER(CKMaxPhotoQualityPrioritization, (@{
30+
@"balanced": @(CKMaxPhotoQualityPrioritizationBalanced),
31+
@"quality": @(CKMaxPhotoQualityPrioritizationQuality),
32+
@"speed": @(CKMaxPhotoQualityPrioritizationSpeed)
33+
}), CKMaxPhotoQualityPrioritizationBalanced, integerValue)
34+
2935
RCT_ENUM_CONVERTER(CKTorchMode, (@{
3036
@"on": @(CKTorchModeOn),
3137
@"off": @(CKTorchModeOff)

ios/ReactNativeCameraKit/CameraProtocol.swift

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ protocol CameraProtocol: AnyObject, FocusInterfaceViewDelegate {
1919
func update(zoom: Double?)
2020
func update(maxZoom: Double?)
2121
func update(resizeMode: ResizeMode)
22+
func update(maxPhotoQualityPrioritization: MaxPhotoQualityPrioritization?)
2223

2324
func zoomPinchStart()
2425
func zoomPinchChange(pinchScale: CGFloat)

ios/ReactNativeCameraKit/CameraView.swift

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public class CameraView: UIView {
3737
@objc public var resizeMode: ResizeMode = .contain
3838
@objc public var flashMode: FlashMode = .auto
3939
@objc public var torchMode: TorchMode = .off
40+
@objc public var maxPhotoQualityPrioritization: MaxPhotoQualityPrioritization = .balanced
4041
// ratio overlay
4142
@objc public var ratioOverlay: String?
4243
@objc public var ratioOverlayColor: UIColor?
@@ -184,6 +185,9 @@ public class CameraView: UIView {
184185
if changedProps.contains("cameraType") || changedProps.contains("torchMode") {
185186
camera.update(torchMode: torchMode)
186187
}
188+
if changedProps.contains("maxPhotoQualityPrioritization") {
189+
camera.update(maxPhotoQualityPrioritization: maxPhotoQualityPrioritization)
190+
}
187191

188192
if changedProps.contains("onOrientationChange") {
189193
camera.update(onOrientationChange: onOrientationChange)

ios/ReactNativeCameraKit/RealCamera.swift

+22-3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
3333
private var resizeMode: ResizeMode = .contain
3434
private var flashMode: FlashMode = .auto
3535
private var torchMode: TorchMode = .off
36+
private var maxPhotoQualityPrioritization: MaxPhotoQualityPrioritization?
3637
private var resetFocus: (() -> Void)?
3738
private var focusFinished: (() -> Void)?
3839
private var onBarcodeRead: ((_ barcode: String,_ codeFormat : CodeFormat) -> Void)?
@@ -258,6 +259,16 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
258259
func update(flashMode: FlashMode) {
259260
self.flashMode = flashMode
260261
}
262+
263+
func update(maxPhotoQualityPrioritization: MaxPhotoQualityPrioritization?) {
264+
guard maxPhotoQualityPrioritization != self.maxPhotoQualityPrioritization else { return }
265+
if #available(iOS 13.0, *) {
266+
self.session.beginConfiguration()
267+
self.maxPhotoQualityPrioritization = maxPhotoQualityPrioritization
268+
self.photoOutput.maxPhotoQualityPrioritization = maxPhotoQualityPrioritization?.avQualityPrioritization ?? .balanced
269+
self.session.commitConfiguration()
270+
}
271+
}
261272

262273
func update(cameraType: CameraType) {
263274
sessionQueue.async {
@@ -325,7 +336,9 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
325336
}
326337

327338
let settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])
328-
settings.isAutoStillImageStabilizationEnabled = true
339+
if #available(iOS 13.0, *) {
340+
settings.photoQualityPrioritization = self.photoOutput.maxPhotoQualityPrioritization
341+
}
329342

330343
if self.videoDeviceInput?.device.isFlashAvailable == true {
331344
settings.flashMode = self.flashMode.avFlashMode
@@ -477,7 +490,13 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
477490
session.beginConfiguration()
478491

479492
session.sessionPreset = .photo
480-
493+
494+
if #available(iOS 13.0, *) {
495+
if let maxPhotoQualityPrioritization {
496+
photoOutput.maxPhotoQualityPrioritization = maxPhotoQualityPrioritization.avQualityPrioritization
497+
}
498+
}
499+
481500
if session.canAddInput(videoDeviceInput) {
482501
session.addInput(videoDeviceInput)
483502

@@ -510,7 +529,7 @@ class RealCamera: NSObject, CameraProtocol, AVCaptureMetadataOutputObjectsDelega
510529

511530
metadataOutput.metadataObjectTypes = filteredTypes
512531
}
513-
532+
514533
session.commitConfiguration()
515534

516535
return .success

ios/ReactNativeCameraKit/SimulatorCamera.swift

+3
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ class SimulatorCamera: CameraProtocol {
118118
self.mockPreview.torchModeLabel.text = "Torch mode: \(torchMode)"
119119
}
120120
}
121+
122+
func update(maxPhotoQualityPrioritization: MaxPhotoQualityPrioritization?) {
123+
}
121124

122125
func update(flashMode: FlashMode) {
123126
DispatchQueue.main.async {

ios/ReactNativeCameraKit/Types.swift

+23
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,29 @@ public enum FlashMode: Int, CustomStringConvertible {
5252
}
5353
}
5454

55+
@objc(CKMaxPhotoQualityPrioritization)
56+
public enum MaxPhotoQualityPrioritization: Int, CustomStringConvertible {
57+
case speed
58+
case balanced
59+
case quality
60+
61+
var avQualityPrioritization: AVCapturePhotoOutput.QualityPrioritization {
62+
switch self {
63+
case .speed: return .speed
64+
case .balanced: return .balanced
65+
case .quality: return .quality
66+
}
67+
}
68+
69+
public var description: String {
70+
switch self {
71+
case .speed: return "speed"
72+
case .balanced: return "balanced"
73+
case .quality: return "quality"
74+
}
75+
}
76+
}
77+
5578
@objc(CKTorchMode)
5679
public enum TorchMode: Int, CustomStringConvertible {
5780
case on

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"publishConfig": {
88
"registry": "https://registry.npmjs.org/"
99
},
10-
"version": "14.0.0-beta15",
10+
"version": "14.0.0",
1111
"description": "A high performance, fully featured, rock solid camera library for React Native applications",
1212
"nativePackage": true,
1313
"scripts": {

src/CameraProps.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export type OnReadCodeData = {
1919

2020
export type OnOrientationChangeData = {
2121
nativeEvent: {
22-
orientation: typeof Orientation;
22+
orientation: typeof Orientation[keyof typeof Orientation];
2323
};
2424
};
2525

@@ -106,6 +106,8 @@ export interface CameraProps extends ViewProps {
106106
resizeMode?: ResizeMode;
107107
/** **iOS Only**. Throttle how often the barcode scanner triggers a new scan */
108108
scanThrottleDelay?: number;
109+
/** **iOS Only**. 'speed' provides 60-80% faster image capturing */
110+
maxPhotoQualityPrioritization?: 'balanced' | 'quality' | 'speed';
109111
/** **Android only**. Play a shutter capture sound when capturing a photo */
110112
shutterPhotoSound?: boolean;
111113
onCaptureButtonPressIn?: ({ nativeEvent: {} }) => void;

0 commit comments

Comments
 (0)