Ported TabBarIOS to OSS and unified implementation

This commit is contained in:
Nick Lockwood
2015-03-05 16:36:41 -08:00
parent ab2537816f
commit fb2f063ef5
51 changed files with 1148 additions and 478 deletions

View File

@@ -0,0 +1,8 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <ImageIO/ImageIO.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <QuartzCore/QuartzCore.h>
extern CAKeyframeAnimation *RCTGIFImageWithData(NSData *data);
extern CAKeyframeAnimation *RCTGIFImageWithFileURL(NSURL *URL);

View File

@@ -0,0 +1,91 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTGIFImage.h"
#import "RCTLog.h"
static CAKeyframeAnimation *RCTGIFImageWithImageSource(CGImageSourceRef imageSource)
{
if (!UTTypeConformsTo(CGImageSourceGetType(imageSource), kUTTypeGIF)) {
CFRelease(imageSource);
return nil;
}
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(imageSource, NULL);
NSUInteger loopCount = [properties[(id)kCGImagePropertyGIFDictionary][(id)kCGImagePropertyGIFLoopCount] unsignedIntegerValue];
size_t imageCount = CGImageSourceGetCount(imageSource);
NSTimeInterval duration = 0;
NSMutableArray *delays = [NSMutableArray arrayWithCapacity:imageCount];
NSMutableArray *images = [NSMutableArray arrayWithCapacity:imageCount];
for (size_t i = 0; i < imageCount; i++) {
CGImageRef image = CGImageSourceCreateImageAtIndex(imageSource, i, NULL);
NSDictionary *frameProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imageSource, i, NULL);
NSDictionary *frameGIFProperties = frameProperties[(id)kCGImagePropertyGIFDictionary];
const NSTimeInterval kDelayTimeIntervalDefault = 0.1;
NSNumber *delayTime = frameGIFProperties[(id)kCGImagePropertyGIFUnclampedDelayTime] ?: frameGIFProperties[(id)kCGImagePropertyGIFDelayTime];
if (delayTime == nil) {
if (i == 0) {
delayTime = @(kDelayTimeIntervalDefault);
} else {
delayTime = delays[i - 1];
}
}
const NSTimeInterval kDelayTimeIntervalMinimum = 0.02;
if (delayTime.floatValue < (float)kDelayTimeIntervalMinimum - FLT_EPSILON) {
delayTime = @(kDelayTimeIntervalDefault);
}
duration += delayTime.doubleValue;
delays[i] = delayTime;
images[i] = (__bridge_transfer id)image;
}
NSMutableArray *keyTimes = [NSMutableArray arrayWithCapacity:delays.count];
NSTimeInterval runningDuration = 0;
for (NSNumber *delayNumber in delays) {
[keyTimes addObject:@(runningDuration / duration)];
runningDuration += delayNumber.doubleValue;
}
[keyTimes addObject:@1.0];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
animation.calculationMode = kCAAnimationDiscrete;
animation.repeatCount = loopCount == 0 ? HUGE_VALF : loopCount;
animation.keyTimes = keyTimes;
animation.values = images;
animation.duration = duration;
return animation;
}
CAKeyframeAnimation *RCTGIFImageWithData(NSData *data)
{
if (data.length == 0) {
return nil;
}
CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)data, NULL);
CAKeyframeAnimation *animation = RCTGIFImageWithImageSource(imageSource);
CFRelease(imageSource);
return animation;
}
CAKeyframeAnimation *RCTGIFImageWithFileURL(NSURL *URL)
{
if (!URL) {
return nil;
}
if (![URL isFileURL]) {
RCTLogError(@"Loading remote image URLs synchronously is a really bad idea.");
return nil;
}
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)URL, NULL);
CAKeyframeAnimation *animation = RCTGIFImageWithImageSource(imageSource);
CFRelease(imageSource);
return animation;
}

View File

@@ -7,6 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */; };
1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */; };
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; };
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; };
58B511901A9E6BD600147676 /* RCTNetworkImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118C1A9E6BD600147676 /* RCTNetworkImageView.m */; };
58B511911A9E6BD600147676 /* RCTNetworkImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118E1A9E6BD600147676 /* RCTNetworkImageViewManager.m */; };
@@ -25,7 +28,13 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
58B5115D1A9E6B3D00147676 /* libRCTNetworkImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTNetworkImage.a; sourceTree = BUILT_PRODUCTS_DIR; };
1304D5A71AA8C4A30002E2BE /* RCTStaticImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStaticImage.h; sourceTree = "<group>"; };
1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStaticImage.m; sourceTree = "<group>"; };
1304D5A91AA8C4A30002E2BE /* RCTStaticImageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStaticImageManager.h; sourceTree = "<group>"; };
1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStaticImageManager.m; sourceTree = "<group>"; };
1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTGIFImage.h; sourceTree = "<group>"; };
1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImage.m; sourceTree = "<group>"; };
58B5115D1A9E6B3D00147676 /* libRCTImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTImage.a; sourceTree = BUILT_PRODUCTS_DIR; };
58B511891A9E6BD600147676 /* RCTImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageDownloader.h; sourceTree = "<group>"; };
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = "<group>"; };
58B5118B1A9E6BD600147676 /* RCTNetworkImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNetworkImageView.h; sourceTree = "<group>"; };
@@ -48,12 +57,18 @@
58B511541A9E6B3D00147676 = {
isa = PBXGroup;
children = (
1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */,
1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */,
58B511891A9E6BD600147676 /* RCTImageDownloader.h */,
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */,
58B5118B1A9E6BD600147676 /* RCTNetworkImageView.h */,
58B5118C1A9E6BD600147676 /* RCTNetworkImageView.m */,
58B5118D1A9E6BD600147676 /* RCTNetworkImageViewManager.h */,
58B5118E1A9E6BD600147676 /* RCTNetworkImageViewManager.m */,
1304D5A71AA8C4A30002E2BE /* RCTStaticImage.h */,
1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */,
1304D5A91AA8C4A30002E2BE /* RCTStaticImageManager.h */,
1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */,
58B5115E1A9E6B3D00147676 /* Products */,
);
sourceTree = "<group>";
@@ -61,7 +76,7 @@
58B5115E1A9E6B3D00147676 /* Products */ = {
isa = PBXGroup;
children = (
58B5115D1A9E6B3D00147676 /* libRCTNetworkImage.a */,
58B5115D1A9E6B3D00147676 /* libRCTImage.a */,
);
name = Products;
sourceTree = "<group>";
@@ -69,9 +84,9 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
58B5115C1A9E6B3D00147676 /* RCTNetworkImage */ = {
58B5115C1A9E6B3D00147676 /* RCTImage */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511711A9E6B3D00147676 /* Build configuration list for PBXNativeTarget "RCTNetworkImage" */;
buildConfigurationList = 58B511711A9E6B3D00147676 /* Build configuration list for PBXNativeTarget "RCTImage" */;
buildPhases = (
58B511591A9E6B3D00147676 /* Sources */,
58B5115A1A9E6B3D00147676 /* Frameworks */,
@@ -81,9 +96,9 @@
);
dependencies = (
);
name = RCTNetworkImage;
name = RCTImage;
productName = RCTNetworkImage;
productReference = 58B5115D1A9E6B3D00147676 /* libRCTNetworkImage.a */;
productReference = 58B5115D1A9E6B3D00147676 /* libRCTImage.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
@@ -100,7 +115,7 @@
};
};
};
buildConfigurationList = 58B511581A9E6B3D00147676 /* Build configuration list for PBXProject "RCTNetworkImage" */;
buildConfigurationList = 58B511581A9E6B3D00147676 /* Build configuration list for PBXProject "RCTImage" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
@@ -112,7 +127,7 @@
projectDirPath = "";
projectRoot = "";
targets = (
58B5115C1A9E6B3D00147676 /* RCTNetworkImage */,
58B5115C1A9E6B3D00147676 /* RCTImage */,
);
};
/* End PBXProject section */
@@ -124,7 +139,10 @@
files = (
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */,
58B511911A9E6BD600147676 /* RCTNetworkImageViewManager.m in Sources */,
1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */,
58B511901A9E6BD600147676 /* RCTNetworkImageView.m in Sources */,
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */,
1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -213,8 +231,12 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../ReactKit/**",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/UIExplorer-gjaibsjtheitasdxdtcvxxqavkvy/Build/Products/Debug-iphoneos",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
PRODUCT_NAME = RCTImage;
SKIP_INSTALL = YES;
};
name = Debug;
@@ -227,8 +249,12 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../ReactKit/**",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/UIExplorer-gjaibsjtheitasdxdtcvxxqavkvy/Build/Products/Debug-iphoneos",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
PRODUCT_NAME = RCTImage;
SKIP_INSTALL = YES;
};
name = Release;
@@ -236,7 +262,7 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
58B511581A9E6B3D00147676 /* Build configuration list for PBXProject "RCTNetworkImage" */ = {
58B511581A9E6B3D00147676 /* Build configuration list for PBXProject "RCTImage" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B5116F1A9E6B3D00147676 /* Debug */,
@@ -245,7 +271,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511711A9E6B3D00147676 /* Build configuration list for PBXNativeTarget "RCTNetworkImage" */ = {
58B511711A9E6B3D00147676 /* Build configuration list for PBXNativeTarget "RCTImage" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511721A9E6B3D00147676 /* Debug */,

View File

@@ -109,39 +109,37 @@ typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *e
- (id)downloadImageForURL:(NSURL *)url size:(CGSize)size scale:(CGFloat)scale block:(RCTImageDownloadBlock)block
{
NSString *cacheKey = [self cacheKeyForURL:url];
__weak RCTImageDownloader *weakSelf = self;
return [self _downloadDataForURL:url block:^(BOOL cached, NSData *data, NSError *error) {
if (!data) {
return dispatch_async(dispatch_get_main_queue(), ^{
block(nil, error);
});
}
return [self downloadDataForURL:url block:^(NSData *data, NSError *error) {
UIImage *image = [UIImage imageWithData:data scale:scale];
if (image) {
// Resize (TODO: should we take aspect ratio into account?)
CGSize imageSize = size;
if (CGSizeEqualToSize(imageSize, CGSizeZero)) {
imageSize = image.size;
} else {
imageSize = (CGSize){
MIN(size.width, image.size.width),
MIN(size.height, image.size.height)
};
}
// Rescale image if required size is smaller
CGFloat imageScale = scale;
if (imageScale == 0 || imageScale > image.scale) {
if (imageScale == 0 || imageScale < image.scale) {
imageScale = image.scale;
}
// Decompress image at required size
UIGraphicsBeginImageContextWithOptions(imageSize, NO, imageScale);
[image drawInRect:(CGRect){{0, 0}, imageSize}];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (!cached) {
RCTImageDownloader *strongSelf = weakSelf;
[strongSelf->_cache setData:UIImagePNGRepresentation(image) forKey:cacheKey];
}
}
// TODO: should we cache the decompressed image?
dispatch_async(dispatch_get_main_queue(), ^{
block(image, nil);
});

View File

@@ -2,9 +2,10 @@
#import "RCTNetworkImageView.h"
#import "RCTConvert.h"
#import "RCTGIFImage.h"
#import "RCTImageDownloader.h"
#import "RCTUtils.h"
#import "RCTConvert.h"
@implementation RCTNetworkImageView
{
@@ -53,7 +54,7 @@
if ([imageURL.pathExtension caseInsensitiveCompare:@"gif"] == NSOrderedSame) {
_downloadToken = [_imageDownloader downloadDataForURL:imageURL block:^(NSData *data, NSError *error) {
if (data) {
CAKeyframeAnimation *animation = [RCTConvert GIF:data];
CAKeyframeAnimation *animation = RCTGIFImageWithData(data);
CGImageRef firstFrame = (__bridge CGImageRef)animation.values.firstObject;
self.layer.bounds = CGRectMake(0, 0, CGImageGetWidth(firstFrame), CGImageGetHeight(firstFrame));
self.layer.contentsScale = 1.0;

View File

@@ -23,4 +23,3 @@ RCT_REMAP_VIEW_PROPERTY(src, imageURL)
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode)
@end

View File

@@ -0,0 +1,10 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
@interface RCTStaticImage : UIImageView
@property (nonatomic, assign) UIEdgeInsets capInsets;
@property (nonatomic, assign) UIImageRenderingMode renderingMode;
@end

View File

@@ -0,0 +1,55 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTStaticImage.h"
@implementation RCTStaticImage
- (void)_updateImage
{
UIImage *image = self.image;
if (!image) {
return;
}
// Apply rendering mode
if (_renderingMode != image.renderingMode) {
image = [image imageWithRenderingMode:_renderingMode];
}
// Applying capInsets of 0 will switch the "resizingMode" of the image to "tile" which is undesired
if (!UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, _capInsets)) {
image = [image resizableImageWithCapInsets:_capInsets resizingMode:UIImageResizingModeStretch];
}
// Apply trilinear filtering to smooth out mis-sized images
self.layer.minificationFilter = kCAFilterTrilinear;
self.layer.magnificationFilter = kCAFilterTrilinear;
super.image = image;
}
- (void)setImage:(UIImage *)image
{
if (image != super.image) {
super.image = image;
[self _updateImage];
}
}
- (void)setCapInsets:(UIEdgeInsets)capInsets
{
if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, capInsets)) {
_capInsets = capInsets;
[self _updateImage];
}
}
- (void)setRenderingMode:(UIImageRenderingMode)renderingMode
{
if (_renderingMode != renderingMode) {
_renderingMode = renderingMode;
[self _updateImage];
}
}
@end

View File

@@ -0,0 +1,7 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTViewManager.h"
@interface RCTStaticImageManager : RCTViewManager
@end

View File

@@ -0,0 +1,43 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTStaticImageManager.h"
#import <UIKit/UIKit.h>
#import "RCTConvert.h"
#import "RCTGIFImage.h"
#import "RCTStaticImage.h"
@implementation RCTStaticImageManager
- (UIView *)view
{
return [[RCTStaticImage alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(capInsets)
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode)
RCT_CUSTOM_VIEW_PROPERTY(src, RCTStaticImage *)
{
if (json) {
if ([[[json description] pathExtension] caseInsensitiveCompare:@"gif"] == NSOrderedSame) {
[view.layer addAnimation:RCTGIFImageWithFileURL([RCTConvert NSURL:json]) forKey:@"contents"];
} else {
view.image = [RCTConvert UIImage:json];
}
} else {
view.image = defaultView.image;
}
}
RCT_CUSTOM_VIEW_PROPERTY(tintColor, RCTStaticImage *)
{
if (json) {
view.renderingMode = UIImageRenderingModeAlwaysTemplate;
view.tintColor = [RCTConvert UIColor:json];
} else {
view.renderingMode = defaultView.renderingMode;
view.tintColor = defaultView.tintColor;
}
}
@end