Camera Roll API + Upload From Camera Roll Module
我们将介绍使用内置的React Native Camera Roll API显示相机中的图片。一旦用户选择了一张图片,我们将使用
Upload From Camera Roll Module获得图片的base64数据。如果你只是想让用户看到和选择图片,你可以使用这种方法,但如果是想拍照,你需要使用
camera模块. 如果不想让用户定义和选择图片的界面,你可以使用
Image Picker Module.
导入CameraRoll API
const React = require('react-native');
const {
CameraRoll,
} = React;
CameraRoll.getPhotos接受 3 个参数:第一个参数是一个对像,它定义了从相机胶卷中返回什么样的图片。第二个是一个回调函数,这个函数,接受请求到的图片,第三个参数也是一个回调函数,它用来处理错误。首先,让我们看看第一个参数,它是如何定义要返回的图片。以下是这个对数的属性。
{
first: ..., // (必须的) 在照片中,按反相排序后,在获取的照片的数量
after: ..., // 上一次调用getPhotos返回的指针。cursor
groupTypes: ..., // 指定分组类型,过滤结果
// One of ['Album', 'All', 'Event', 'Faces', 'Library', 'PhotoStream', 'SavedPhotos'(default)]
groupName: ..., // Specifies filter on group names, like 'Recent Photos' or custom album titles
assetType: ... // Specifies filter on assetType
// One of ['All', 'Videos', 'Photos'(default)]
}
const fetchParams = {
first: 25,
}
{
edges: [
node: {
type: ...,
group_name: ...,
image: {
uri: ...,
height: ...,
width: ...,
isStored: ...,
},
timestamp: ...,
location {
...
},
},
node: { ... },
node: { ... },
...
],
page_info: {
has_next_page: ...,
start_cursor: ...,
end_cursor: ...,
}
}
由于节点中的image对像包含了我们将要在App中显示图片的时要使用到的数据,所以我们需要创建一个函数,从edges数组中提取image对像。并且应该将它们保存到一个state变量中.
storeImages(data) {
const assets = data.edges;
const images = assets.map( asset => asset.node.image );
this.setState({
images: images,
});
},
由于我们使用了images变量,所以需要先定义这个变量
getInitialState() {
return {
images: [],
};
},
getPhotos的第三个参数是用来处理错误的回调函数。对于这个例子,我们只是将错误发送到控制台.
logImageError(err) {
console.log(err);
},
到目前为此,我们已经为CameraRoll.getPhotos定义了三个参数。我们将在ComponentDidMount()中调用这个getPhotos, 它会检索一次图片信息。
componentDidMount() {
const fetchParams = {
first: 25,
};
CameraRoll.getPhotos(fetchParams, this.storeImages, this.logImageError);
},
读取图片
我们可以使用Image组件,flexbox样式,以及其它的map function来显示检索到的图片。
让我们先导入StyleSheet和Image组件
const {
CameraRoll,
StyleSheet,
Image,
} = React;
然后在render函数中显示图片
render() {
return (
<ScrollView style={styles.container}>
<View style={styles.imageGrid}>
{ this.state.images.map(image => <Image style={styles.image} source={{ uri: image.uri }} />) }
</View>
</ScrollView>
);
}
然后添加样式
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
imageGrid: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center'
},
image: {
width: 100,
height: 100,
margin: 10,
},
});
到目前为此,你的组件看起来如下
const React = require('react-native');
const {
StyleSheet,
Text,
View,
ScrollView,
Image,
CameraRoll,
} = React;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
imageGrid: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center'
},
image: {
width: 100,
height: 100,
margin: 10,
},
});
const reactImageProject = React.createClass({
getInitialState() {
return {
images: [],
};
},
componentDidMount() {
const fetchParams = {
first: 25,
};
CameraRoll.getPhotos(fetchParams, this.storeImages, this.logImageError);
},
storeImages(data) {
const assets = data.edges;
const images = assets.map((asset) => asset.node.image);
this.setState({
images: images,
});
},
logImageError(err) {
console.log(err);
},
render() {
return (
<ScrollView style={styles.container}>
<View style={styles.imageGrid}>
{ this.state.images.map((image) => <Image style={styles.image} source={{ uri: image.uri }} />) }
</View>
</ScrollView>
);
}
});
当你运行这个项目,你会看到一个最新相机中最新的25张图片
选择图片
为了让用户可以选择其中一张图片,我们冉要让上面的图片可以被点击。我们需要使用TouchableHighlight组件,当点击图片是,将图片的uri保存到另一个state变量中.
我们首先测试选择到图片的uri
selectImage(uri) {
this.setState({
selected: uri,
});
console.log('Selected image: ', uri);
},
getInitialState() {
return {
images: [],
selected: '',
};
},
以下是新的render函数
const {
StyleSheet,
Text,
View,
ScrollView,
Image,
CameraRoll,
TouchableHighlight,
} = React;
render() {
return (
<ScrollView style={styles.container}>
<View style={styles.imageGrid}>
{ this.state.images.map((image) => {
return (
<TouchableHighlight onPress={this.selectImage.bind(null, image.uri)}>
<Image style={styles.image} source={{ uri: image.uri }} />
</TouchableHighlight>
);
})
}
</View>
</ScrollView>
);
}
当你运行这个项目时,点击图片会输出图片的uri到console.
获得图片的base64数据
已经存在一个模块, 它使用React native image 组件获得这个图片的base64版本。但它不能通过NPM安装。所以我们仅要在创建自定义模块是,将它添加进来。如
upload-from-camera-roll中有介绍, 如何在你的项目中包含upload-form-camera-roll模块。
现在,我们可以使用这个模块从image的uri中,来获得base64编码的图片数据。 我们可以改变selectImage函数,把原来输出uri的功能,改成保存和输出图片的base64编码.
selectImage(uri) {
NativeModules.ReadImageData.readImage(uri, (image) => {
this.setState({
selected: image,
});
console.log(image);
});
},
const React = require('react-native');
const {
AppRegistry,
StyleSheet,
Text,
View,
ScrollView,
Image,
CameraRoll,
TouchableHighlight,
NativeModules,
} = React;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
imageGrid: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center'
},
image: {
width: 100,
height: 100,
margin: 10,
}
});
const reactImageProject = React.createClass({
getInitialState() {
return {
images: [],
selected: '',
};
},
componentDidMount() {
const fetchParams = {
first: 25,
};
CameraRoll.getPhotos(fetchParams, this.storeImages, this.logImageError);
},
storeImages(data) {
const assets = data.edges;
const images = assets.map((asset) => asset.node.image);
this.setState({
images: images,
});
},
logImageError(err) {
console.log(err);
},
selectImage(uri) {
NativeModules.ReadImageData.readImage(uri, (image) => {
this.setState({
selected: image,
});
console.log(image);
});
},
render() {
return (
<ScrollView style={styles.container}>
<View style={styles.imageGrid}>
{ this.state.images.map((image) => {
return (
<TouchableHighlight onPress={this.selectImage.bind(null, image.uri)}>
<Image style={styles.image} source={{ uri: image.uri }} />
</TouchableHighlight>
);
})
}
</View>
</ScrollView>
);
}
});
AppRegistry.registerComponent('reactImageProject', () => reactImageProject);
关于NativeModules模块
我们可以在获取到的图片数据中,添加一个base64属性到image对像. 如下
在Xcode中打开Libraries > RCTImage > RCTCameraRollManager.m中打开
替换这个文件为
现在你获得的图片对像就会包含base64属性。你仅需要调整相关的React Native文件,让它知道新的属性
打开/node_modules/react-native/Libraries/CameraRoll/CameraRoll.js,将下面的行,添加到76行
base64: ReactPropTypes.string,
如果你打算使用这个方法上传全分辨率的图片,应用程序会运行的超慢,因为它需要将图片转换为大型的base64字符串。
为了更好的优化,我们创建一个自定义的本地模块,它充许我们的Javascript 与Object-c通话,通过这种方式,我们给 Object-C传递一个图片资源的URI给它,Object-c在返回这个图片的base64数据。现在iOS仅在需要的时候读取一个图片。相对于上面的方法,要读取所有的图片。这是一个非常大的优化 。
创建一个自定义模块,非常简单。
在Xcode中 ,打开Project > Libraries > React > Base. 在Base上右键,选择New File... ,在选择Object-C文件。命名为RCTCustom.m, 并且添加以下的内容。这就创建了一个ReadImageData的模块了.
#import "RCTBridgeModule.h"
#import <AssetsLibrary/AssetsLibrary.h>
#import <UIKit/UIKit.h>
@interface ReadImageData : NSObject <RCTBridgeModule>
@end
@implementation ReadImageData
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(readImage:(NSString *)input callback:(RCTResponseSenderBlock)callback)
{
// Create NSURL from uri
NSURL *url = [[NSURL alloc] initWithString:input];
// Create an ALAssetsLibrary instance. This provides access to the
// videos and photos that are under the control of the Photos application.
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
// Using the ALAssetsLibrary instance and our NSURL object open the image.
[library assetForURL:url resultBlock:^(ALAsset *asset) {
// Create an ALAssetRepresentation object using our asset
// and turn it into a bitmap using the CGImageRef opaque type.
CGImageRef imageRef = [asset thumbnail];
// Create UIImageJPEGRepresentation from CGImageRef
NSData *imageData = UIImageJPEGRepresentation([UIImage imageWithCGImage:imageRef], 0.1);
// Convert to base64 encoded string
NSString *base64Encoded = [imageData base64EncodedStringWithOptions:0];
callback(@[base64Encoded]);
} failureBlock:^(NSError *error) {
NSLog(@"that didn't work %@", error);
}];
}
@end
现在你的Xcode project 已经有了一个新的自定义模块,我们可以在Javascript中导入这个组件.
var {View, Text, Image, NativeModules} = React;
然后我们可以能过NativeModules访问我们 new module
NativeModules.ReadImageData.readImage(ourImage.node.image.uri, (image) => {
console.log(image)
})
然后,能过fetch方法,上传图片资源
fetch('http://your.server/app.php', {
method:'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
body: JSON.stringify({imageData:image})
}
})