Skip to content

Commit 749b8ed

Browse files
committed
fix(ImagePicker): 增加图片预览 & 多张图片同时上传
1 parent 1be6a4e commit 749b8ed

File tree

9 files changed

+122
-52
lines changed

9 files changed

+122
-52
lines changed

example/base/ios/Podfile

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,14 @@ target 'AwesomeProject' do
3131
use_flipper!()
3232

3333
post_install do |installer|
34-
react_native_post_install(installer)
34+
installer.pods_project.targets.each do |target|
35+
target.build_configurations.each do |config|
36+
config.build_settings['SWIFT_VERSION'] = '5.0'
37+
if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] < '12.4'
38+
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.4'
39+
end
40+
end
41+
end
3542
__apply_Xcode_12_5_M1_post_install_workaround(installer)
3643
end
3744
end

example/base/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@
1010
"lint": "eslint ."
1111
},
1212
"dependencies": {
13-
"@uiw/react-native": "3.2.3",
14-
"@uiw/react-native-image-picker": "3.2.3",
1513
"react": "18.2.0",
1614
"react-native": "0.69.7",
15+
"@uiw/react-native": "3.2.3",
16+
"@uiw/react-native-image-picker": "3.2.3",
1717
"react-native-svg": "12.1.1",
18-
"react-native-image-picker":"^4.10.0"
18+
"react-native-gesture-handler": "2.8.0",
19+
"react-native-root-siblings": "4.1.1",
20+
"react-native-image-picker":"^5.3.1",
21+
"react-native-image-viewing":"~0.2.2"
1922
},
2023
"devDependencies": {
2124
"@babel/core": "~7.20.7",

example/examples/ios/Podfile

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,14 @@ target 'examples' do
3131
use_flipper!()
3232

3333
post_install do |installer|
34-
react_native_post_install(installer)
34+
installer.pods_project.targets.each do |target|
35+
target.build_configurations.each do |config|
36+
config.build_settings['SWIFT_VERSION'] = '5.0'
37+
if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] < '12.4'
38+
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.4'
39+
end
40+
end
41+
end
3542
__apply_Xcode_12_5_M1_post_install_workaround(installer)
3643
end
3744
end

example/examples/ios/examples/Info.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
</dict>
3636
</dict>
3737
</dict>
38+
<key>NSCameraUsageDescription</key>
39+
<string>This app requires access to the camera.</string>
3840
<key>NSLocationWhenInUseUsageDescription</key>
3941
<string></string>
4042
<key>UILaunchStoryboardName</key>

example/examples/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
"react-native-safe-area-context": "~4.3.1",
2121
"react-native-screens": "~3.15.0",
2222
"react-native-svg": "12.1.1",
23-
"react-native-image-picker":"^4.10.0",
24-
"@uiw/react-native-image-picker": "3.2.3"
23+
"react-native-image-picker":"^5.3.1",
24+
"react-native-image-viewing":"~0.2.2",
25+
"@uiw/react-native-image-picker": "3.2.3",
26+
"@react-native-community/cameraroll":"^4.1.2"
2527
},
2628
"devDependencies": {
2729
"@babel/core": "~7.20.7",

example/examples/src/routes/ImagePicker/index.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ const {Header, Body, Card, Footer} = Layout;
77

88
export interface ImagePickerProps extends ComProps {}
99

10+
interface fileProps {
11+
fileName: string;
12+
fileType: string;
13+
uri: string;
14+
fileSize?: number;
15+
}
16+
1017
export default function MenuDropdownView(props: ImagePickerProps) {
1118
const {route} = props || {};
1219
const description = route.params.description;
@@ -17,7 +24,14 @@ export default function MenuDropdownView(props: ImagePickerProps) {
1724
<Header title={title} description={description} />
1825
<Body>
1926
<Card title="基础实例">
20-
<ImagePicker upload={async file => await file.uri} />
27+
<ImagePicker
28+
upload={(file: fileProps[]) => {
29+
let imageList: string[] = [];
30+
file.forEach(file => imageList.push(file.uri));
31+
return imageList;
32+
}}
33+
selectionLimit={2}
34+
/>
2135
</Card>
2236
</Body>
2337
<Footer />

packages/react-native-image-picker/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@uiw/react-native-image-picker",
33
"version": "3.2.3",
4-
"description": "UIW for React Native",
4+
"description": "基于 React Native 的上传图片组件",
55
"homepage": "https://uiwjs.github.io/react-native-uiw/",
66
"main": "lib/index.js",
77
"types": "lib/index.d.ts",
@@ -30,13 +30,14 @@
3030
"@shopify/restyle":"~2.4.2",
3131
"@uiw/react-native":"~3.2.3",
3232
"react-native-svg": "12.1.1",
33-
"react-native-image-picker":"^4.10.0"
33+
"react-native-image-picker":"^5.3.1",
34+
"react-native-image-viewing":"~0.2.2"
3435
},
3536
"peerDependencies": {
3637
"react": ">=16.9.0",
3738
"react-native": ">=0.60.0",
3839
"react-native-svg": ">=9.9.3",
39-
"react-native-image-picker":">= 4.0.0"
40+
"react-native-image-picker":">= 5.0.0"
4041
},
4142
"devDependencies": {
4243
"@babel/core": "~7.20.7",

packages/react-native-image-picker/src/ImagePicker/index.tsx

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
1-
import React, { PropsWithChildren, useMemo } from 'react';
2-
import { View, StyleSheet, Rationale, TouchableOpacity, Dimensions, Image } from 'react-native';
1+
import React, { PropsWithChildren, useMemo, ComponentType } from 'react';
2+
import {
3+
View,
4+
StyleSheet,
5+
Rationale,
6+
TouchableOpacity,
7+
ModalProps,
8+
ImageURISource,
9+
ImageRequireSource,
10+
} from 'react-native';
311
import { CameraOptions, ImagePickerResponse } from 'react-native-image-picker';
4-
import { Theme, Flex, ActionSheet, ActionSheetItem, MaskLayer, TransitionImage, Icon, Text } from '@uiw/react-native';
12+
import { Theme, Flex, ActionSheet, ActionSheetItem, TransitionImage, Icon, Text } from '@uiw/react-native';
13+
import ImageView from 'react-native-image-viewing';
514
import { useTheme } from '@shopify/restyle';
615
import useImagePicker from './useImagePicker';
716

17+
export type ImageSource = ImageURISource | ImageRequireSource;
818
export interface File {
919
fileName: string;
1020
fileType: string;
@@ -24,17 +34,19 @@ export type ImagePickerProps = PropsWithChildren<{
2434
/** 上传图片后是否在背景图展示,如果为 true 上传后会自动展示上传图片(此时只能上传一张) */
2535
showUploadImg?: boolean;
2636
/** 上传文件之前的钩子,参数为上传的文件,若返回 false 则停止上传,同时可以在里面执行一些上传提示操作 */
27-
beforeUpload?: (file: File) => boolean | ((file: File) => Promise<boolean>);
37+
beforeUpload?: (file: File[]) => boolean | ((file: File) => Promise<boolean>);
2838
/** 上传 */
29-
upload?: (file: File) => Promise<string>;
39+
upload?: (file: File[]) => string[];
3040
/** 上传完成 */
31-
uploadFinish?: (result?: string) => void;
41+
uploadFinish?: (result?: string[] | undefined) => void;
3242
/** 取消上传事件回调 */
3343
onCancel?: (response: ImagePickerResponse) => void;
3444
/** 上传失败事件回调 */
3545
onFail?: (response: ImagePickerResponse) => void;
3646
/** 授权失败的回调 */
3747
onGrantFail?: () => void;
48+
/** 预览时长按图片保存回调 */
49+
onSave?: ((image: any) => void) | undefined;
3850
/** 打开相册授权的文本 */
3951
libraryRationale?: Rationale;
4052
/** 打开摄像头授权的文本 */
@@ -43,11 +55,29 @@ export type ImagePickerProps = PropsWithChildren<{
4355
launchLibraryText?: string;
4456
/** 打开摄像头文本 */
4557
launchCameraText?: string;
58+
/** 从相册选择多少张图片 */
59+
selectionLimit?: number;
60+
// ImageViewProps
61+
onLongPress?: (image: ImageSource) => void;
62+
onImageIndexChange?: (imageIndex: number) => void;
63+
presentationStyle?: ModalProps['presentationStyle'];
64+
animationType?: ModalProps['animationType'];
65+
backgroundColor?: string;
66+
swipeToCloseEnabled?: boolean;
67+
doubleTapToZoomEnabled?: boolean;
68+
delayLongPress?: number;
69+
HeaderComponent?: ComponentType<{
70+
imageIndex: number;
71+
}>;
72+
FooterComponent?: ComponentType<{
73+
imageIndex: number;
74+
}>;
4675
}>;
4776

4877
const ImagePicker = ({
4978
width = 100,
5079
height = 100,
80+
selectionLimit = 1,
5181
value = [],
5282
options,
5383
showUploadImg = true,
@@ -72,7 +102,8 @@ const ImagePicker = ({
72102
buttonNeutral: '下次再说',
73103
},
74104
launchLibraryText = '打开相册',
75-
launchCameraText = '打开摄像头',
105+
launchCameraText = '打开相机',
106+
...others
76107
}: ImagePickerProps) => {
77108
const theme = useTheme<Theme>();
78109
const styles = createStyles({
@@ -84,7 +115,7 @@ const ImagePicker = ({
84115

85116
const {
86117
currentImgSource,
87-
previewSrc,
118+
current,
88119
loading,
89120
launchLibrary,
90121
launchCamera,
@@ -106,6 +137,7 @@ const ImagePicker = ({
106137
onGrantFail,
107138
cameraRationale,
108139
libraryRationale,
140+
selectionLimit,
109141
});
110142

111143
const pictureList = useMemo(() => {
@@ -136,9 +168,14 @@ const ImagePicker = ({
136168
return null;
137169
}, [showUploadImg, JSON.stringify(currentImgSource)]);
138170

171+
const previewImages = useMemo(() => {
172+
return currentImgSource.map((item) => ({ uri: item }));
173+
}, [JSON.stringify(currentImgSource), current]);
174+
139175
return (
140176
<View>
141177
<Flex justify="start" wrap="wrap">
178+
{pictureList}
142179
<Flex.Item>
143180
<View style={styles.box}>
144181
<TouchableOpacity
@@ -147,21 +184,22 @@ const ImagePicker = ({
147184
disabled={loading}
148185
style={{ justifyContent: 'center', alignItems: 'center', width, height }}
149186
>
150-
<Icon name="plus-square-o" color="#A9A9A9" size={20} />
187+
<Icon name="plus-square-o" color={theme.colors.text || '#A9A9A9'} size={20} />
151188
</TouchableOpacity>
152189
</View>
153190
</Flex.Item>
154-
{pictureList}
155191
</Flex>
156-
<ActionSheet onCancel={setLaunchVisibleFalse} visible={launchVisible} style={{ zIndex: 99 }}>
192+
<ActionSheet onCancel={setLaunchVisibleFalse} visible={launchVisible}>
157193
<ActionSheetItem onPress={launchLibrary}>{launchLibraryText}</ActionSheetItem>
158194
<ActionSheetItem onPress={launchCamera}>{launchCameraText}</ActionSheetItem>
159195
</ActionSheet>
160-
<MaskLayer visible={previewVisible} style={{ zIndex: 999 }} onDismiss={closePreviewImage} opacity={0.9}>
161-
<View style={styles.content}>
162-
<Image style={styles.image} source={{ uri: previewSrc }} />
163-
</View>
164-
</MaskLayer>
196+
<ImageView
197+
visible={previewVisible}
198+
onRequestClose={closePreviewImage}
199+
imageIndex={current || 0}
200+
images={previewImages}
201+
{...others}
202+
/>
165203
</View>
166204
);
167205
};
@@ -180,15 +218,6 @@ function createStyles({ width = 100, height = 100, borderColor = '#CCCCCC', back
180218
backgroundColor: backgroundColor,
181219
margin: 4,
182220
},
183-
content: {
184-
marginTop: Dimensions.get('window').width / 3 - 20,
185-
height: Dimensions.get('window').height / 3 - 20,
186-
},
187-
image: {
188-
width: '100%',
189-
height: '100%',
190-
resizeMode: 'contain',
191-
},
192221
previewBox: {
193222
position: 'absolute',
194223
left: 0,

packages/react-native-image-picker/src/ImagePicker/useImagePicker.ts

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ function filterSource(source: string[] = []) {
2626
export default function useImagePicker({
2727
value,
2828
options,
29-
showUploadImg = true,
29+
selectionLimit,
3030
beforeUpload,
3131
upload,
3232
uploadFinish,
@@ -45,7 +45,7 @@ export default function useImagePicker({
4545
/** loading */
4646
const [loading, setLoading] = useSafeState(false);
4747
/** 预览照片地址 */
48-
const [previewSrc, setPreviewSrc] = useSafeState<string | undefined>(undefined); // 选中的图片下标
48+
const [current, setCurrent] = useSafeState<number | undefined>(undefined); // 选中的图片下标
4949
// 刷新
5050
const [refresh, setRefresh] = useState(false);
5151

@@ -81,6 +81,7 @@ export default function useImagePicker({
8181
setTimeout(() => {
8282
launchImageLibrary(
8383
{
84+
selectionLimit: selectionLimit,
8485
...initialOptions,
8586
...options,
8687
},
@@ -120,26 +121,30 @@ export default function useImagePicker({
120121
onFail?.(response);
121122
} else {
122123
if (!response.assets || response.assets.length === 0) return;
123-
const file: File = {
124-
fileName: response.assets[0].fileName!,
125-
fileType: response.assets[0].type!,
126-
uri: response.assets[0].uri!,
127-
fileSize: response.assets[0].fileSize!,
128-
};
124+
let imageFiles: File[] = [];
125+
response.assets.forEach((item) => {
126+
const file: File = {
127+
fileName: item.fileName!,
128+
fileType: item.type!,
129+
uri: item.uri!,
130+
fileSize: item.fileSize!,
131+
};
132+
imageFiles.push(file);
133+
});
129134
// 执行上传前的操作及判断
130135
if (beforeUpload) {
131-
const result = await beforeUpload(file);
136+
const result = await beforeUpload(imageFiles);
132137
if (!result) {
133138
return;
134139
}
135140
}
136141
setLoading(true);
137-
const result = await upload?.(file);
142+
const result = upload?.(imageFiles);
138143
setLoading(false);
139144
uploadFinish?.(result);
140145
setLaunchVisibleFalse();
141-
if (result) {
142-
const datas = [...currentImgSource, result];
146+
if (result && result.length > 0) {
147+
const datas = [...currentImgSource, ...result];
143148
setCurrentImgSource(datas);
144149
}
145150
}
@@ -151,13 +156,13 @@ export default function useImagePicker({
151156

152157
// 预览照片
153158
const previewImage = (key: number) => {
154-
setPreviewSrc(currentImgSource[key]);
159+
setCurrent(key);
155160
setPreviewVisibleTrue();
156161
};
157162

158163
// 关闭预览照片
159164
const closePreviewImage = () => {
160-
setPreviewSrc(undefined);
165+
setCurrent(undefined);
161166
setPreviewVisibleFalse();
162167
};
163168

@@ -167,7 +172,7 @@ export default function useImagePicker({
167172
setCurrentImgSource(currentImgSource);
168173
// 刷新页面
169174
setRefresh(!refresh);
170-
uploadFinish?.('');
175+
uploadFinish?.(undefined);
171176
};
172177

173178
// 打开上传
@@ -178,7 +183,7 @@ export default function useImagePicker({
178183

179184
return {
180185
currentImgSource,
181-
previewSrc,
186+
current,
182187
loading,
183188
launchLibrary,
184189
launchCamera,

0 commit comments

Comments
 (0)