Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lay the foundation for a Swift migration #547

Merged
merged 20 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .prettierrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module.exports = {
printWidth: 120,
semi: true,
jsxBracketSameLine: false,
jsxSingleQuote: true,
jsxSingleQuote: false,
arrowParens: 'always',
};

22 changes: 5 additions & 17 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
TouchableOpacity,
ScrollView,
} from 'react-native';
import { StyleSheet, Text, View, TouchableOpacity, ScrollView } from 'react-native';

import BarcodeScreenExample from './BarcodeScreenExample';
import CameraExample from './CameraExample';

type State = {
example?: any;
}
};

export default class App extends Component {
state: State;
Expand All @@ -33,20 +27,14 @@ export default class App extends Component {
<ScrollView style={{ flex: 1 }}>
<View style={styles.headerContainer}>
<Text style={{ fontSize: 60 }}>🎈</Text>
<Text style={styles.headerText}>
React Native Camera Kit
</Text>
<Text style={styles.headerText}>React Native Camera Kit</Text>
</View>
<View style={styles.container}>
<TouchableOpacity style={styles.button} onPress={() => this.setState({ example: CameraExample })}>
<Text style={styles.buttonText}>
Camera
</Text>
<Text style={styles.buttonText}>Camera</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={() => this.setState({ example: BarcodeScreenExample })}>
<Text style={styles.buttonText}>
Barcode Scanner
</Text>
<Text style={styles.buttonText}>Barcode Scanner</Text>
</TouchableOpacity>
</View>
</ScrollView>
Expand Down
10 changes: 5 additions & 5 deletions example/src/BarcodeScreenExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,16 @@ const BarcodeExample = ({ onBack }: { onBack: () => void }) => {
<View style={styles.topButtons}>
{flashData.image && (
<TouchableOpacity style={styles.flashMode} onPress={() => onSetFlash()}>
<Image source={flashData.image} resizeMode='contain' />
<Image source={flashData.image} resizeMode="contain" />
</TouchableOpacity>
)}
<TouchableOpacity style={styles.switchCamera} onPress={() => onSwitchCameraPressed()}>
<Image source={require('../images/cameraFlipIcon.png')} resizeMode='contain' />
<Image source={require('../images/cameraFlipIcon.png')} resizeMode="contain" />
</TouchableOpacity>
<TouchableOpacity style={styles.torch} onPress={() => onSetTorch()}>
<Image
source={torchMode ? require('../images/torchOn.png') : require('../images/torchOff.png')}
resizeMode='contain'
resizeMode="contain"
/>
</TouchableOpacity>
</View>
Expand All @@ -126,8 +126,8 @@ const BarcodeExample = ({ onBack }: { onBack: () => void }) => {
style={{ width: window.width, height: window.width * cameraRatio }}
cameraType={cameraType}
flashMode={flashData?.mode}
zoomMode='on'
focusMode='on'
zoomMode="on"
focusMode="on"
torchMode={torchMode ? 'on' : 'off'}
onOrientationChange={(e) => {
console.log('orientationChange', e.nativeEvent);
Expand Down
59 changes: 37 additions & 22 deletions example/src/CameraExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from 'react-native';
import Camera from '../../src/Camera';
import { CameraApi, CameraType, CaptureData } from '../../src/types';
import { Orientation } from '../../src';

const { width, height } = Dimensions.get('window');

Expand Down Expand Up @@ -46,6 +47,11 @@ const CameraExample = ({ onBack }: { onBack: () => void }) => {
const [cameraType, setCameraType] = useState(CameraType.Back);
const [showImageUri, setShowImageUri] = useState<string>('');

// iOS will error out if capturing too fast,
// so block capturing until the current capture is done
// This also minimizes issues of delayed capturing
const isCapturing = useRef(false);

const numberOfImagesTaken = () => {
const numberTook = captureImages.length;
if (numberTook >= 2) {
Expand Down Expand Up @@ -73,13 +79,25 @@ const CameraExample = ({ onBack }: { onBack: () => void }) => {
};

const onCaptureImagePressed = async () => {
if (!cameraRef.current) return;
const image = await cameraRef.current.capture();
if (image) {
setCaptured(true);
setCaptureImages([...captureImages, image]);
console.log('image', image);
if (showImageUri) {
setShowImageUri('');
return;
}
if (!cameraRef.current || isCapturing.current) return;
let image: CaptureData | undefined;
try {
isCapturing.current = true;
image = await cameraRef.current.capture();
} catch (e) {
console.log('error', e);
} finally {
isCapturing.current = false;
}
if (!image) return;

setCaptured(true);
setCaptureImages([...captureImages, image]);
console.log('image', image);
};

const window = useWindowDimensions();
Expand All @@ -91,16 +109,16 @@ const CameraExample = ({ onBack }: { onBack: () => void }) => {
<View style={styles.topButtons}>
{flashData.image && (
<TouchableOpacity style={styles.flashMode} onPress={() => onSetFlash()}>
<Image source={flashData.image} resizeMode='contain' />
<Image source={flashData.image} resizeMode="contain" />
</TouchableOpacity>
)}
<TouchableOpacity style={styles.switchCamera} onPress={() => onSwitchCameraPressed()}>
<Image source={require('../images/cameraFlipIcon.png')} resizeMode='contain' />
<Image source={require('../images/cameraFlipIcon.png')} resizeMode="contain" />
</TouchableOpacity>
<TouchableOpacity style={styles.torch} onPress={() => onSetTorch()}>
<Image
source={torchMode ? require('../images/torchOn.png') : require('../images/torchOff.png')}
resizeMode='contain'
resizeMode="contain"
/>
</TouchableOpacity>
</View>
Expand All @@ -110,35 +128,32 @@ const CameraExample = ({ onBack }: { onBack: () => void }) => {
<Image
source={{ uri: showImageUri }}
style={{ width: window.width, height: window.width * cameraRatio }}
resizeMode='contain'
resizeMode="contain"
/>
) : (
<Camera
ref={cameraRef}
style={{ width: window.width, height: window.width * cameraRatio }}
style={{ width: window.width, height: window.width * cameraRatio, backgroundColor: 'magenta' }}
cameraType={cameraType}
flashMode={flashData?.mode}
zoomMode="on"
focusMode="on"
torchMode={torchMode ? 'on' : 'off'}
onOrientationChange={(e) => {
console.log('orientationChange', e.nativeEvent)
// We recommend locking the camera UI to portrait (using a different library)
// and rotating the UI elements counter to the orientation
// However, we include onOrientationChange so you can match your UI to what the camera does
const isLandscape = [Orientation.LANDSCAPE_LEFT, Orientation.LANDSCAPE_RIGHT].includes(
e.nativeEvent.orientation,
);
console.log('orientationChange', isLandscape ? 'landscape' : 'portrait');
}}
/>
)}
</View>
<SafeAreaView style={styles.bottomButtons}>
<View style={styles.bottomButtonsInner}>
<TouchableOpacity
style={styles.backBtn}
onPress={() => {
if (showImageUri) {
setShowImageUri('');
} else {
onBack();
}
}}
>
<TouchableOpacity style={styles.backBtn} onPress={() => onBack()}>
<Text style={styles.textStyle}>Back</Text>
</TouchableOpacity>
<View style={styles.captureButtonContainer}>
Expand Down
4 changes: 2 additions & 2 deletions ios/ReactNativeCameraKit/CameraProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ protocol CameraProtocol: AnyObject, FocusInterfaceViewDelegate {
func setup(cameraType: CameraType, supportedBarcodeType: [AVMetadataObject.ObjectType])
func cameraRemovedFromSuperview()

func update(zoomScale: CGFloat)
func update(pinchVelocity: CGFloat, pinchScale: CGFloat)
func update(torchMode: TorchMode)
func update(flashMode: FlashMode)
func update(cameraType: CameraType)
Expand All @@ -23,6 +23,6 @@ protocol CameraProtocol: AnyObject, FocusInterfaceViewDelegate {
func update(scannerFrameSize: CGRect?)

func capturePicture(onWillCapture: @escaping () -> Void,
onSuccess: @escaping (_ imageData: Data) -> (),
onSuccess: @escaping (_ imageData: Data, _ thumbnailData: Data?) -> (),
onError: @escaping (_ message: String) -> ())
}
17 changes: 11 additions & 6 deletions ios/ReactNativeCameraKit/CameraView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,9 @@ class CameraView: UIView {
self?.camera.previewView.alpha = 1
})
}
}, onSuccess: { [weak self] imageData in
}, onSuccess: { [weak self] imageData, thumbnailData in
DispatchQueue.global(qos: .default).async {
self?.writeCaptured(imageData: imageData, onSuccess: onSuccess, onError: onError)
self?.writeCaptured(imageData: imageData, thumbnailData: thumbnailData, onSuccess: onSuccess, onError: onError)

self?.focusInterfaceView.resetFocus()
}
Expand Down Expand Up @@ -262,15 +262,21 @@ class CameraView: UIView {
}
}

private func writeCaptured(imageData: Data,
private func writeCaptured(imageData: Data,
thumbnailData: Data?,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you getting a thumbnail? Seems like we have to explicitly enable it

If you requested a preview image by specifying the previewPhotoFormat property of your photo settings when requesting capture, this property offers access to the resulting preview image pixel data

https://developer.apple.com/documentation/avfoundation/avcapturephoto/2873984-previewpixelbuffer

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's just laying some preparation for later changes. I had it working locally but have not committed enabling it since it needs several other features enabled and must be an opt-in feature. We can leave it in for now.

onSuccess: @escaping (_ imageObject: [String: Any]) -> (),
onError: @escaping (_ error: String) -> ()) {
do {
let temporaryFileURL = try saveToTmpFolder(imageData)
var temporaryThumbFileURL: URL? = nil
if let t = thumbnailData {
temporaryThumbFileURL = try saveToTmpFolder(t)
}
onSuccess([
"size": imageData.count,
"uri": temporaryFileURL.description,
"name": temporaryFileURL.lastPathComponent
"name": temporaryFileURL.lastPathComponent,
"thumb": temporaryThumbFileURL?.description ?? ""
])
} catch {
let errorMessage = "Error occurred while writing image data to a temporary file: \(error)"
Expand Down Expand Up @@ -304,9 +310,8 @@ class CameraView: UIView {
// MARK: - Gesture selectors

@objc func handlePinchToZoomRecognizer(_ pinchRecognizer: UIPinchGestureRecognizer) {
var zoomScale = pinchRecognizer.scale
if pinchRecognizer.state == .changed {
camera.update(zoomScale: zoomScale)
camera.update(pinchVelocity: pinchRecognizer.velocity, pinchScale: pinchRecognizer.scale)
Copy link
Contributor Author

@DavidBertet DavidBertet Jul 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do want to use pinchRecognizer.scale here (seems not used)

  • Should put the line pinchRecognizer.scale = 1.0 first?
  • Or move it when we set it up?
  • Well, not sure what it does :D and it's late...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't matter a ton to me. I was just playing with various algorithms to get an improved behavior.

pinchRecognizer.scale = 1.0
}
}
Expand Down
6 changes: 3 additions & 3 deletions ios/ReactNativeCameraKit/PhotoCaptureDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ class PhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate {
private(set) var requestedPhotoSettings: AVCapturePhotoSettings

private let onWillCapture: () -> Void
private let onCaptureSuccess: (_ uniqueID: Int64, _ imageData: Data) -> Void
private let onCaptureSuccess: (_ uniqueID: Int64, _ imageData: Data, _ photo: AVCapturePhoto) -> Void
private let onCaptureError: (_ uniqueID: Int64, _ message: String) -> Void

init(with requestedPhotoSettings: AVCapturePhotoSettings,
onWillCapture: @escaping () -> Void,
onCaptureSuccess: @escaping (_ uniqueID: Int64, _ imageData: Data) -> Void,
onCaptureSuccess: @escaping (_ uniqueID: Int64, _ imageData: Data, _ photo: AVCapturePhoto) -> Void,
onCaptureError: @escaping (_ uniqueID: Int64, _ errorMessage: String) -> Void) {
self.requestedPhotoSettings = requestedPhotoSettings
self.onWillCapture = onWillCapture
Expand All @@ -43,6 +43,6 @@ class PhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate {
return
}

onCaptureSuccess(requestedPhotoSettings.uniqueID, imageData)
onCaptureSuccess(requestedPhotoSettings.uniqueID, imageData, photo)
}
}
Loading