react-native/Libraries/Image/Image.ios.js

401 lines
14 KiB
JavaScript

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule Image
* @flow
*/
'use strict';
const EdgeInsetsPropType = require('EdgeInsetsPropType');
const ImageResizeMode = require('ImageResizeMode');
const ImageSourcePropType = require('ImageSourcePropType');
const ImageStylePropTypes = require('ImageStylePropTypes');
const NativeMethodsMixin = require('NativeMethodsMixin');
const NativeModules = require('NativeModules');
const React = require('React');
const PropTypes = require('prop-types');
const ReactNativeViewAttributes = require('ReactNativeViewAttributes');
const StyleSheet = require('StyleSheet');
const StyleSheetPropType = require('StyleSheetPropType');
const createReactClass = require('create-react-class');
const flattenStyle = require('flattenStyle');
const requireNativeComponent = require('requireNativeComponent');
const resolveAssetSource = require('resolveAssetSource');
const ImageViewManager = NativeModules.ImageViewManager;
/**
* A React component for displaying different types of images,
* including network images, static resources, temporary local images, and
* images from local disk, such as the camera roll.
*
* This example shows fetching and displaying an image from local storage
* as well as one from network and even from data provided in the `'data:'` uri scheme.
*
* > Note that for network and data images, you will need to manually specify the dimensions of your image!
*
* ```ReactNativeWebPlayer
* import React, { Component } from 'react';
* import { AppRegistry, View, Image } from 'react-native';
*
* export default class DisplayAnImage extends Component {
* render() {
* return (
* <View>
* <Image
* source={require('./img/favicon.png')}
* />
* <Image
* style={{width: 50, height: 50}}
* source={{uri: 'https://facebook.github.io/react-native/img/favicon.png'}}
* />
* <Image
* style={{width: 66, height: 58}}
* source={{uri: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADMAAAAzCAYAAAA6oTAqAAAAEXRFWHRTb2Z0d2FyZQBwbmdjcnVzaEB1SfMAAABQSURBVGje7dSxCQBACARB+2/ab8BEeQNhFi6WSYzYLYudDQYGBgYGBgYGBgYGBgYGBgZmcvDqYGBgmhivGQYGBgYGBgYGBgYGBgYGBgbmQw+P/eMrC5UTVAAAAABJRU5ErkJggg=='}}
* />
* </View>
* );
* }
* }
*
* // skip this line if using Create React Native App
* AppRegistry.registerComponent('DisplayAnImage', () => DisplayAnImage);
* ```
*
* You can also add `style` to an image:
*
* ```ReactNativeWebPlayer
* import React, { Component } from 'react';
* import { AppRegistry, View, Image, StyleSheet } from 'react-native';
*
* const styles = StyleSheet.create({
* stretch: {
* width: 50,
* height: 200
* }
* });
*
* export default class DisplayAnImageWithStyle extends Component {
* render() {
* return (
* <View>
* <Image
* style={styles.stretch}
* source={require('./img/favicon.png')}
* />
* </View>
* );
* }
* }
*
* // skip these lines if using Create React Native App
* AppRegistry.registerComponent(
* 'DisplayAnImageWithStyle',
* () => DisplayAnImageWithStyle
* );
* ```
*
* ### GIF and WebP support on Android
*
* When building your own native code, GIF and WebP are not supported by default on Android.
*
* You will need to add some optional modules in `android/app/build.gradle`, depending on the needs of your app.
*
* ```
* dependencies {
* // If your app supports Android versions before Ice Cream Sandwich (API level 14)
* compile 'com.facebook.fresco:animated-base-support:1.3.0'
*
* // For animated GIF support
* compile 'com.facebook.fresco:animated-gif:1.3.0'
*
* // For WebP support, including animated WebP
* compile 'com.facebook.fresco:animated-webp:1.3.0'
* compile 'com.facebook.fresco:webpsupport:1.3.0'
*
* // For WebP support, without animations
* compile 'com.facebook.fresco:webpsupport:1.3.0'
* }
* ```
*
* Also, if you use GIF with ProGuard, you will need to add this rule in `proguard-rules.pro` :
* ```
* -keep class com.facebook.imagepipeline.animated.factory.AnimatedFactoryImpl {
* public AnimatedFactoryImpl(com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory, com.facebook.imagepipeline.core.ExecutorSupplier);
* }
* ```
*
*/
const Image = createReactClass({
displayName: 'Image',
propTypes: {
/**
* > `ImageResizeMode` is an `Enum` for different image resizing modes, set via the
* > `resizeMode` style property on `Image` components. The values are `contain`, `cover`,
* > `stretch`, `center`, `repeat`.
*/
style: StyleSheetPropType(ImageStylePropTypes),
/**
* The image source (either a remote URL or a local file resource).
*
* This prop can also contain several remote URLs, specified together with
* their width and height and potentially with scale/other URI arguments.
* The native side will then choose the best `uri` to display based on the
* measured size of the image container. A `cache` property can be added to
* control how networked request interacts with the local cache.
*
* The currently supported formats are `png`, `jpg`, `jpeg`, `bmp`, `gif`,
* `webp` (Android only), `psd` (iOS only).
*/
source: ImageSourcePropType,
/**
* A static image to display while loading the image source.
*
* - `uri` - a string representing the resource identifier for the image, which
* should be either a local file path or the name of a static image resource
* (which should be wrapped in the `require('./path/to/image.png')` function).
* - `width`, `height` - can be specified if known at build time, in which case
* these will be used to set the default `<Image/>` component dimensions.
* - `scale` - used to indicate the scale factor of the image. Defaults to 1.0 if
* unspecified, meaning that one image pixel equates to one display point / DIP.
* - `number` - Opaque type returned by something like `require('./image.jpg')`.
*
* @platform ios
*/
defaultSource: PropTypes.oneOfType([
// TODO: Tooling to support documenting these directly and having them display in the docs.
PropTypes.shape({
uri: PropTypes.string,
width: PropTypes.number,
height: PropTypes.number,
scale: PropTypes.number,
}),
PropTypes.number,
]),
/**
* When true, indicates the image is an accessibility element.
* @platform ios
*/
accessible: PropTypes.bool,
/**
* The text that's read by the screen reader when the user interacts with
* the image.
* @platform ios
*/
accessibilityLabel: PropTypes.node,
/**
* blurRadius: the blur radius of the blur filter added to the image
*/
blurRadius: PropTypes.number,
/**
* When the image is resized, the corners of the size specified
* by `capInsets` will stay a fixed size, but the center content and borders
* of the image will be stretched. This is useful for creating resizable
* rounded buttons, shadows, and other resizable assets. More info in the
* [official Apple documentation](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/index.html#//apple_ref/occ/instm/UIImage/resizableImageWithCapInsets).
*
* @platform ios
*/
capInsets: EdgeInsetsPropType,
/**
* The mechanism that should be used to resize the image when the image's dimensions
* differ from the image view's dimensions. Defaults to `auto`.
*
* - `auto`: Use heuristics to pick between `resize` and `scale`.
*
* - `resize`: A software operation which changes the encoded image in memory before it
* gets decoded. This should be used instead of `scale` when the image is much larger
* than the view.
*
* - `scale`: The image gets drawn downscaled or upscaled. Compared to `resize`, `scale` is
* faster (usually hardware accelerated) and produces higher quality images. This
* should be used if the image is smaller than the view. It should also be used if the
* image is slightly bigger than the view.
*
* More details about `resize` and `scale` can be found at http://frescolib.org/docs/resizing-rotating.html.
*
* @platform android
*/
resizeMethod: PropTypes.oneOf(['auto', 'resize', 'scale']),
/**
* Determines how to resize the image when the frame doesn't match the raw
* image dimensions.
*
* - `cover`: Scale the image uniformly (maintain the image's aspect ratio)
* so that both dimensions (width and height) of the image will be equal
* to or larger than the corresponding dimension of the view (minus padding).
*
* - `contain`: Scale the image uniformly (maintain the image's aspect ratio)
* so that both dimensions (width and height) of the image will be equal to
* or less than the corresponding dimension of the view (minus padding).
*
* - `stretch`: Scale width and height independently, This may change the
* aspect ratio of the src.
*
* - `repeat`: Repeat the image to cover the frame of the view. The
* image will keep it's size and aspect ratio. (iOS only)
*/
resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch', 'repeat', 'center']),
/**
* A unique identifier for this element to be used in UI Automation
* testing scripts.
*/
testID: PropTypes.string,
/**
* Invoked on mount and layout changes with
* `{nativeEvent: {layout: {x, y, width, height}}}`.
*/
onLayout: PropTypes.func,
/**
* Invoked on load start.
*
* e.g., `onLoadStart={(e) => this.setState({loading: true})}`
*/
onLoadStart: PropTypes.func,
/**
* Invoked on download progress with `{nativeEvent: {loaded, total}}`.
* @platform ios
*/
onProgress: PropTypes.func,
/**
* Invoked on load error with `{nativeEvent: {error}}`.
*/
onError: PropTypes.func,
/**
* Invoked when a partial load of the image is complete. The definition of
* what constitutes a "partial load" is loader specific though this is meant
* for progressive JPEG loads.
* @platform ios
*/
onPartialLoad: PropTypes.func,
/**
* Invoked when load completes successfully.
*/
onLoad: PropTypes.func,
/**
* Invoked when load either succeeds or fails.
*/
onLoadEnd: PropTypes.func,
},
statics: {
resizeMode: ImageResizeMode,
/**
* Retrieve the width and height (in pixels) of an image prior to displaying it.
* This method can fail if the image cannot be found, or fails to download.
*
* In order to retrieve the image dimensions, the image may first need to be
* loaded or downloaded, after which it will be cached. This means that in
* principle you could use this method to preload images, however it is not
* optimized for that purpose, and may in future be implemented in a way that
* does not fully load/download the image data. A proper, supported way to
* preload images will be provided as a separate API.
*
* Does not work for static image resources.
*
* @param uri The location of the image.
* @param success The function that will be called if the image was successfully found and width
* and height retrieved.
* @param failure The function that will be called if there was an error, such as failing to
* to retrieve the image.
*
* @returns void
*
* @platform ios
*/
getSize: function(
uri: string,
success: (width: number, height: number) => void,
failure?: (error: any) => void,
) {
ImageViewManager.getSize(uri, success, failure || function() {
console.warn('Failed to get size for image: ' + uri);
});
},
/**
* Prefetches a remote image for later use by downloading it to the disk
* cache
*
* @param url The remote location of the image.
*
* @return The prefetched image.
*/
prefetch(url: string) {
return ImageViewManager.prefetchImage(url);
},
/**
* Resolves an asset reference into an object which has the properties `uri`, `width`,
* and `height`. The input may either be a number (opaque type returned by
* require('./foo.png')) or an `ImageSource` like { uri: '<http location || file path>' }
*/
resolveAssetSource: resolveAssetSource,
},
mixins: [NativeMethodsMixin],
/**
* `NativeMethodsMixin` will look for this when invoking `setNativeProps`. We
* make `this` look like an actual native component class.
*/
viewConfig: {
uiViewClassName: 'UIView',
validAttributes: ReactNativeViewAttributes.UIView
},
render: function() {
const source = resolveAssetSource(this.props.source) || { uri: undefined, width: undefined, height: undefined };
let sources;
let style;
if (Array.isArray(source)) {
style = flattenStyle([styles.base, this.props.style]) || {};
sources = source;
} else {
const {width, height, uri} = source;
style = flattenStyle([{width, height}, styles.base, this.props.style]) || {};
sources = [source];
if (uri === '') {
console.warn('source.uri should not be an empty string');
}
}
const resizeMode = this.props.resizeMode || (style || {}).resizeMode || 'cover'; // Workaround for flow bug t7737108
const tintColor = (style || {}).tintColor; // Workaround for flow bug t7737108
if (this.props.src) {
console.warn('The <Image> component requires a `source` property rather than `src`.');
}
if (this.props.children) {
throw new Error('The <Image> component cannot contain children. If you want to render content on top of the image, consider using the <ImageBackground> component or absolute positioning.');
}
return (
<RCTImageView
{...this.props}
style={style}
resizeMode={resizeMode}
tintColor={tintColor}
source={sources}
/>
);
},
});
const styles = StyleSheet.create({
base: {
overflow: 'hidden',
},
});
const RCTImageView = requireNativeComponent('RCTImageView', Image);
module.exports = Image;