Skip to content

Commit 4673dca

Browse files
committed
Merge pull request facebook#1375 from vjeux/Updates_Fri_22_May
Updates fri 22 may
2 parents d20a6e9 + 800c62b commit 4673dca

File tree

31 files changed

+932
-87
lines changed

31 files changed

+932
-87
lines changed

Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
1341802C1AA9178B003F314A /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1341802B1AA91779003F314A /* libRCTNetwork.a */; };
1414
134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1344545A1AAFCAAE003F0779 /* libRCTAdSupport.a */; };
1515
134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; };
16+
1353F5461B0E64F9009B4FAC /* ClippingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1353F5451B0E64F9009B4FAC /* ClippingTests.m */; };
1617
139FDEDB1B0651FB00C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDED91B0651EA00C62182 /* libRCTWebSocket.a */; };
1718
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
1819
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
@@ -129,6 +130,7 @@
129130
134180261AA91779003F314A /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../../Libraries/Network/RCTNetwork.xcodeproj; sourceTree = "<group>"; };
130131
134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAdSupport.xcodeproj; path = ../../Libraries/AdSupport/RCTAdSupport.xcodeproj; sourceTree = "<group>"; };
131132
134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = "<group>"; };
133+
1353F5451B0E64F9009B4FAC /* ClippingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ClippingTests.m; sourceTree = "<group>"; };
132134
139FDECA1B0651EA00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = "<group>"; };
133135
13B07F961A680F5B00A75B9A /* UIExplorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UIExplorer.app; sourceTree = BUILT_PRODUCTS_DIR; };
134136
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = UIExplorer/AppDelegate.h; sourceTree = "<group>"; };
@@ -179,6 +181,7 @@
179181
isa = PBXGroup;
180182
children = (
181183
004D28A21AAF61C70097A701 /* UIExplorerTests.m */,
184+
1353F5451B0E64F9009B4FAC /* ClippingTests.m */,
182185
004D28A01AAF61C70097A701 /* Supporting Files */,
183186
);
184187
path = UIExplorerTests;
@@ -575,6 +578,7 @@
575578
buildActionMask = 2147483647;
576579
files = (
577580
004D28A31AAF61C70097A701 /* UIExplorerTests.m in Sources */,
581+
1353F5461B0E64F9009B4FAC /* ClippingTests.m in Sources */,
578582
);
579583
runOnlyForDeploymentPostprocessing = 0;
580584
};
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/**
2+
* The examples provided by Facebook are for non-commercial testing and
3+
* evaluation purposes only.
4+
*
5+
* Facebook reserves all rights not expressly granted.
6+
*
7+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
8+
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
9+
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
10+
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
11+
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
12+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13+
*/
14+
15+
#import <CoreGraphics/CoreGraphics.h>
16+
#import <Foundation/Foundation.h>
17+
#import <UIKit/UIView.h>
18+
#import <XCTest/XCTest.h>
19+
20+
extern CGRect RCTClipRect(CGSize contentSize, CGFloat contentScale,
21+
CGSize targetSize, CGFloat targetScale,
22+
UIViewContentMode resizeMode);
23+
24+
#define RCTAssertEqualPoints(a, b) { \
25+
XCTAssertEqual(a.x, b.x); \
26+
XCTAssertEqual(a.y, b.y); \
27+
}
28+
29+
#define RCTAssertEqualSizes(a, b) { \
30+
XCTAssertEqual(a.width, b.width); \
31+
XCTAssertEqual(a.height, b.height); \
32+
}
33+
34+
#define RCTAssertEqualRects(a, b) { \
35+
RCTAssertEqualPoints(a.origin, b.origin); \
36+
RCTAssertEqualSizes(a.size, b.size); \
37+
}
38+
39+
@interface ClippingTests : XCTestCase
40+
41+
@end
42+
43+
@implementation ClippingTests
44+
45+
- (void)testLandscapeSourceLandscapeTarget
46+
{
47+
CGSize content = {1000, 100};
48+
CGSize target = {100, 20};
49+
50+
{
51+
CGRect expected = {CGPointZero, {100, 20}};
52+
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleToFill);
53+
RCTAssertEqualRects(expected, result);
54+
}
55+
56+
{
57+
CGRect expected = {CGPointZero, {100, 10}};
58+
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFit);
59+
RCTAssertEqualRects(expected, result);
60+
}
61+
62+
{
63+
CGRect expected = {{-50, 0}, {200, 20}};
64+
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFill);
65+
RCTAssertEqualRects(expected, result);
66+
}
67+
}
68+
69+
- (void)testPortraitSourceLandscapeTarget
70+
{
71+
CGSize content = {10, 100};
72+
CGSize target = {100, 20};
73+
74+
{
75+
CGRect expected = {CGPointZero, {10, 20}};
76+
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleToFill);
77+
RCTAssertEqualRects(expected, result);
78+
}
79+
80+
{
81+
CGRect expected = {CGPointZero, {2, 20}};
82+
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFit);
83+
RCTAssertEqualRects(expected, result);
84+
}
85+
86+
{
87+
CGRect expected = {{0, -49}, {10, 100}};
88+
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFill);
89+
RCTAssertEqualRects(expected, result);
90+
}
91+
}
92+
93+
- (void)testPortraitSourcePortraitTarget
94+
{
95+
CGSize content = {10, 100};
96+
CGSize target = {20, 50};
97+
98+
{
99+
CGRect expected = {CGPointZero, {10, 50}};
100+
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleToFill);
101+
RCTAssertEqualRects(expected, result);
102+
}
103+
104+
{
105+
CGRect expected = {CGPointZero, {5, 50}};
106+
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFit);
107+
RCTAssertEqualRects(expected, result);
108+
}
109+
110+
{
111+
CGRect expected = {{0, -37.5}, {10, 100}};
112+
CGRect result = RCTClipRect(content, 1, target, 1, UIViewContentModeScaleAspectFill);
113+
RCTAssertEqualRects(expected, result);
114+
}
115+
}
116+
117+
- (void)testScaling
118+
{
119+
CGSize content = {2, 2};
120+
CGSize target = {3, 3};
121+
122+
CGRect expected = {CGPointZero, {3, 3}};
123+
CGRect result = RCTClipRect(content, 2, target, 1, UIViewContentModeScaleToFill);
124+
RCTAssertEqualRects(expected, result);
125+
}
126+
127+
@end

Libraries/CustomComponents/Navigator/Navigator.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1298,6 +1298,14 @@ var Navigator = React.createClass({
12981298
if (i !== this.state.presentedIndex) {
12991299
disabledSceneStyle = styles.disabledScene;
13001300
}
1301+
var originalRef = child.ref;
1302+
if (originalRef != null && typeof originalRef !== 'function') {
1303+
console.warn(
1304+
'String refs are not supported for navigator scenes. Use a callback ' +
1305+
'ref instead. Ignoring ref: ' + originalRef
1306+
);
1307+
originalRef = null;
1308+
}
13011309
return (
13021310
<View
13031311
key={this.state.idStack[i]}
@@ -1307,7 +1315,12 @@ var Navigator = React.createClass({
13071315
}}
13081316
style={[styles.baseScene, this.props.sceneStyle, disabledSceneStyle]}>
13091317
{React.cloneElement(child, {
1310-
ref: this._handleItemRef.bind(null, this.state.idStack[i], route),
1318+
ref: component => {
1319+
this._handleItemRef(this.state.idStack[i], route, component);
1320+
if (originalRef) {
1321+
originalRef(component);
1322+
}
1323+
}
13111324
})}
13121325
</View>
13131326
);

Libraries/Image/RCTImageDownloader.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error);
3333
- (id)downloadImageForURL:(NSURL *)url
3434
size:(CGSize)size
3535
scale:(CGFloat)scale
36+
resizeMode:(UIViewContentMode)resizeMode
37+
backgroundColor:(UIColor *)backgroundColor
3638
block:(RCTImageDownloadBlock)block;
3739

3840
/**

Libraries/Image/RCTImageDownloader.m

Lines changed: 119 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#import "RCTImageDownloader.h"
1111

1212
#import "RCTCache.h"
13+
#import "RCTLog.h"
1314
#import "RCTUtils.h"
1415

1516
typedef void (^RCTCachedDataDownloadBlock)(BOOL cached, NSData *data, NSError *error);
@@ -121,34 +122,134 @@ - (id)downloadDataForURL:(NSURL *)url block:(RCTDataDownloadBlock)block
121122
}];
122123
}
123124

124-
- (id)downloadImageForURL:(NSURL *)url size:(CGSize)size
125-
scale:(CGFloat)scale block:(RCTImageDownloadBlock)block
125+
/**
126+
* Returns the optimal context size for an image drawn using the clip rect
127+
* returned by RCTClipRect.
128+
*/
129+
CGSize RCTTargetSizeForClipRect(CGRect);
130+
CGSize RCTTargetSizeForClipRect(CGRect clipRect)
131+
{
132+
return (CGSize){
133+
clipRect.size.width + clipRect.origin.x * 2,
134+
clipRect.size.height + clipRect.origin.y * 2
135+
};
136+
}
137+
138+
/**
139+
* This function takes an input content size & scale (typically from an image),
140+
* a target size & scale that it will be drawn into (typically a CGContext) and
141+
* then calculates the optimal rectangle to draw the image into so that it will
142+
* be sized and positioned correctly if drawn using the specified content mode.
143+
*/
144+
CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
145+
CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
146+
CGSize destSize, CGFloat destScale,
147+
UIViewContentMode resizeMode)
148+
{
149+
// Precompensate for scale
150+
CGFloat scale = sourceScale / destScale;
151+
sourceSize.width *= scale;
152+
sourceSize.height *= scale;
153+
154+
// Calculate aspect ratios if needed (don't bother is resizeMode == stretch)
155+
CGFloat aspect = 0.0, targetAspect = 0.0;
156+
if (resizeMode != UIViewContentModeScaleToFill) {
157+
aspect = sourceSize.width / sourceSize.height;
158+
targetAspect = destSize.width / destSize.height;
159+
if (aspect == targetAspect) {
160+
resizeMode = UIViewContentModeScaleToFill;
161+
}
162+
}
163+
164+
switch (resizeMode) {
165+
case UIViewContentModeScaleToFill: // stretch
166+
167+
sourceSize.width = MIN(destSize.width, sourceSize.width);
168+
sourceSize.height = MIN(destSize.height, sourceSize.height);
169+
return (CGRect){CGPointZero, sourceSize};
170+
171+
case UIViewContentModeScaleAspectFit: // contain
172+
173+
if (targetAspect <= aspect) { // target is taller than content
174+
sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width);
175+
sourceSize.height = sourceSize.width / aspect;
176+
} else { // target is wider than content
177+
sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height);
178+
sourceSize.width = sourceSize.height * aspect;
179+
}
180+
return (CGRect){CGPointZero, sourceSize};
181+
182+
case UIViewContentModeScaleAspectFill: // cover
183+
184+
if (targetAspect <= aspect) { // target is taller than content
185+
186+
sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height);
187+
sourceSize.width = sourceSize.height * aspect;
188+
destSize.width = destSize.height * targetAspect;
189+
return (CGRect){{(destSize.width - sourceSize.width) / 2, 0}, sourceSize};
190+
191+
} else { // target is wider than content
192+
193+
sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width);
194+
sourceSize.height = sourceSize.width / aspect;
195+
destSize.height = destSize.width / targetAspect;
196+
return (CGRect){{0, (destSize.height - sourceSize.height) / 2}, sourceSize};
197+
}
198+
199+
default:
200+
201+
RCTLogError(@"A resizeMode value of %zd is not supported", resizeMode);
202+
return (CGRect){CGPointZero, destSize};
203+
}
204+
}
205+
206+
- (id)downloadImageForURL:(NSURL *)url
207+
size:(CGSize)size
208+
scale:(CGFloat)scale
209+
resizeMode:(UIViewContentMode)resizeMode
210+
backgroundColor:(UIColor *)backgroundColor
211+
block:(RCTImageDownloadBlock)block
126212
{
127213
return [self downloadDataForURL:url block:^(NSData *data, NSError *error) {
128214

215+
if (!data || error) {
216+
block(nil, error);
217+
return;
218+
}
219+
220+
if (CGSizeEqualToSize(size, CGSizeZero)) {
221+
// Target size wasn't available yet, so abort image drawing
222+
block(nil, nil);
223+
return;
224+
}
225+
129226
UIImage *image = [UIImage imageWithData:data scale:scale];
130227
if (image) {
131228

132-
// Resize (TODO: should we take aspect ratio into account?)
133-
CGSize imageSize = size;
134-
if (CGSizeEqualToSize(imageSize, CGSizeZero)) {
135-
imageSize = image.size;
136-
} else {
137-
imageSize = (CGSize){
138-
MIN(size.width, image.size.width),
139-
MIN(size.height, image.size.height)
140-
};
141-
}
229+
// Get scale and size
230+
CGFloat destScale = scale ?: RCTScreenScale();
231+
CGRect imageRect = RCTClipRect(image.size, image.scale, size, destScale, resizeMode);
232+
CGSize destSize = RCTTargetSizeForClipRect(imageRect);
142233

143-
// Rescale image if required size is smaller
144-
CGFloat imageScale = scale;
145-
if (imageScale == 0 || imageScale < image.scale) {
146-
imageScale = image.scale;
234+
// Opacity optimizations
235+
UIColor *blendColor = nil;
236+
BOOL opaque = !RCTImageHasAlpha(image.CGImage);
237+
if (!opaque && backgroundColor) {
238+
CGFloat alpha;
239+
[backgroundColor getRed:NULL green:NULL blue:NULL alpha:&alpha];
240+
if (alpha > 0.999) { // no benefit to blending if background is translucent
241+
opaque = YES;
242+
blendColor = backgroundColor;
243+
}
147244
}
148245

149246
// Decompress image at required size
150-
UIGraphicsBeginImageContextWithOptions(imageSize, NO, imageScale);
151-
[image drawInRect:(CGRect){{0, 0}, imageSize}];
247+
UIGraphicsBeginImageContextWithOptions(destSize, opaque, destScale);
248+
if (blendColor) {
249+
[blendColor setFill];
250+
UIRectFill((CGRect){CGPointZero, destSize});
251+
}
252+
[image drawInRect:imageRect];
152253
image = UIGraphicsGetImageFromCurrentImageContext();
153254
UIGraphicsEndImageContext();
154255
}

Libraries/Image/RCTImageLoader.m

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
2626
static dispatch_once_t onceToken;
2727
dispatch_once(&onceToken, ^{
2828
queue = dispatch_queue_create("com.facebook.rctImageLoader", DISPATCH_QUEUE_SERIAL);
29-
dispatch_set_target_queue(queue,
30-
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
3129
});
3230

3331
return queue;

0 commit comments

Comments
 (0)