Permalink
Browse files

[OpaqueImageView] Add (native) opaque image view component.

  • Loading branch information...
alloy committed Apr 23, 2016
1 parent 11d70f7 commit a404ccf1519ec79fc0bfadf6e572d990caa0a9ec
@@ -13,6 +13,7 @@ Pod::Spec.new do |s|

s.subspec 'All' do |ss|
ss.dependency 'Emission/Core'
ss.dependency 'Emission/OpaqueImageViewComponent'
ss.dependency 'Emission/SwitchViewComponent'
end

@@ -30,4 +31,9 @@ Pod::Spec.new do |s|
ss.dependency 'UIView+BooleanAnimations'
ss.dependency 'FLKAutoLayout'
end

s.subspec 'OpaqueImageViewComponent' do |ss|
ss.source_files = 'Pod/Classes/OpaqueImageViewComponent'
ss.dependency 'SDWebImage', '>= 3.7.2'
end
end
@@ -0,0 +1,5 @@
#import <React/RCTShadowView.h>

@interface AROpaqueImageShadowView : RCTShadowView
@property (nonatomic, assign, readwrite) CGFloat aspectRatio;
@end
@@ -0,0 +1,29 @@
#import "AROpaqueImageShadowView.h"

#import <React/RCTUtils.h>

@implementation AROpaqueImageShadowView

static css_dim_t
RCTMeasure(void *context, float width, float height)
{
AROpaqueImageShadowView *shadowImage = (__bridge AROpaqueImageShadowView *)context;
css_dim_t result;
if (!isnan(width)) {
result.dimensions[CSS_WIDTH] = RCTCeilPixelValue(width);
result.dimensions[CSS_HEIGHT] = RCTCeilPixelValue(width / shadowImage.aspectRatio);
} else if (!isnan(height)) {
result.dimensions[CSS_WIDTH] = RCTCeilPixelValue(height * shadowImage.aspectRatio);
result.dimensions[CSS_HEIGHT] = RCTCeilPixelValue(height);
}
NSCAssert(!(isnan(result.dimensions[CSS_WIDTH]) || isnan(result.dimensions[CSS_HEIGHT])), @"Invalid layout!");
return result;
}

- (void)fillCSSNode:(css_node_t *)node
{
[super fillCSSNode:node];
node->measure = RCTMeasure;
}

@end
@@ -0,0 +1,5 @@
#import <UIKit/UIKit.h>

@interface AROpaqueImageView : UIImageView
@property (nonatomic, strong, readwrite) NSURL *imageURL;
@end
@@ -0,0 +1,109 @@
#import "AROpaqueImageView.h"

#import <SDWebImage/SDImageCache.h>
#import <SDWebImage/SDWebImageManager.h>

// * Decode the image into a context so that none of that will occur on the main thread when UIImageView loads it.
// * Do not add an alpha channel, to ensure that the image will be drawn by UIImageView without any blending.
//
static void
LoadImage(UIImage *image, CGSize destinationSize, CGFloat scaleFactor, void (^callback)(UIImage *loadedImage)) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
CGFloat width = destinationSize.width * scaleFactor;
CGFloat height = destinationSize.height * scaleFactor;

CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL,
width,
height,
8,
width * 4,
colourSpace,
kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host);
CGColorSpaceRelease(colourSpace);

CGContextDrawImage(context, CGRectMake(0, 0, width, height), image.CGImage);

CGImageRef outputImage = CGBitmapContextCreateImage(context);
UIImage *loadedImage = [UIImage imageWithCGImage:outputImage
scale:scaleFactor
orientation:UIImageOrientationUp];

CGImageRelease(outputImage);
CGContextRelease(context);

dispatch_async(dispatch_get_main_queue(), ^{
callback(loadedImage);
});
});
}

@interface AROpaqueImageView ()
@property (nonatomic, weak, readwrite) id<SDWebImageOperation> downloadOperation;
@end

@implementation AROpaqueImageView

- (instancetype)initWithFrame:(CGRect)frame;
{
if ((self = [super initWithFrame:frame])) {
self.opaque = YES;
}
return self;
}

- (void)setImage:(UIImage *)image;
{
// This will cancel an in-flight download operation, if one exists.
self.imageURL = nil;
[super setImage:image];
}

- (void)setImageURL:(NSURL *)imageURL;
{
if ([_imageURL isEqual:imageURL]) {
return;
}

// This is a weak reference, so either an operation is in-flight and
// needs cancelling, or it will be nil and this is a no-op.
[self.downloadOperation cancel];

_imageURL = imageURL;
if (_imageURL == nil) {
return;
}

// TODO Setting decompress to NO, because Eigen sets it to YES.
// We need to send a PR to SDWebImage to disable decoding
// with an option to the download method.
//
SDWebImageManager *manager = [SDWebImageManager sharedManager];
manager.imageCache.shouldDecompressImages = NO;
manager.imageDownloader.shouldDecompressImages = NO;

__weak typeof(self) weakSelf = self;
self.downloadOperation = [manager downloadImageWithURL:self.imageURL
options:0
progress:nil
completed:^(UIImage *image,
NSError *_,
SDImageCacheType __,
BOOL ____,
NSURL *imageURL) {
__strong typeof(weakSelf) strongSelf = weakSelf;
// Only really assign if the URL we downloaded still matches `self.imageURL`.
if (strongSelf && [imageURL isEqual:strongSelf.imageURL]) {
// The view might not yet be associated with a window, in which case
// the view will always return 1 for contentScaleFactor.
CGFloat scaleFactor = [[UIScreen mainScreen] scale];
LoadImage(image, strongSelf.bounds.size, scaleFactor, ^(UIImage *loadedImage) {
if ([imageURL isEqual:weakSelf.imageURL]) {
weakSelf.image = loadedImage;
}
});
}
}];
}

@end
@@ -0,0 +1,4 @@
#import <React/RCTViewManager.h>

@interface AROpaqueImageViewManager : RCTViewManager
@end
@@ -0,0 +1,25 @@
#import "AROpaqueImageViewManager.h"

#import "AROpaqueImageView.h"
#import "AROpaqueImageShadowView.h"

@implementation AROpaqueImageViewManager

RCT_EXPORT_MODULE();
RCT_EXPORT_SHADOW_PROPERTY(aspectRatio, CGFloat);
RCT_CUSTOM_VIEW_PROPERTY(imageURL, NSString, AROpaqueImageView)
{
view.imageURL = [NSURL URLWithString:json];
}

- (UIView *)view;
{
return [AROpaqueImageView new];
}

- (RCTShadowView *)shadowView;
{
return [AROpaqueImageShadowView new];
}

@end
@@ -17,7 +17,7 @@
- (void)setTitle:(NSString *)title forButtonAtIndex:(NSInteger)index;

@property (nonatomic, weak, readwrite) id<ARSwitchViewDelegate> delegate;
@property (nonatomic, strong, readonly) NSArray<UIButton *> *buttons;
@property (nonatomic, copy, readonly) NSArray<UIButton *> *buttons;

// Assigning to this replaces all existing buttons with new ones.
@property (nonatomic, strong, readwrite) NSArray<NSString *> *titles;
@@ -0,0 +1,17 @@
/* @flow */
'use strict';

import React from 'react-native';

export default class OpaqueImageView extends React.Component {
render() {
return <NativeOpaqueImageView {...this.props} />;
}
}

OpaqueImageView.propTypes = {
imageURL: React.PropTypes.string,
aspectRatio: React.PropTypes.number,
};

const NativeOpaqueImageView = React.requireNativeComponent('AROpaqueImageView', OpaqueImageView);

0 comments on commit a404ccf

Please sign in to comment.