Skip to content

Commit

Permalink
Unify LinearGradient behavior across platforms (expo#2624)
Browse files Browse the repository at this point in the history
* Add EXLinearGradientLayer to unify gradient rendering across platforms

- copied from react-native-linear-gradient
- moved to LinearGradient directory under Components

* Clamp the gradient

* Add an example of this confusing gradient to NCL

* Add CHANGELOG entry
  • Loading branch information
sjchmiela authored and tsapeta committed Nov 6, 2018
1 parent bc91ba5 commit 9b19e49
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This is the log of notable changes to the Expo client that are developer-facing.
### 🐛 Bug fixes

- decycle objects when sending logs to remote console by [@sjchmiela](https://github.com/sjchmiela) ([#2598](https://github.com/expo/expo/pull/2598))
- unify linear gradient behavior across platforms by [@sjchmiela](https://github.com/sjchmiela) ([#2624](https://github.com/expo/expo/pull/2624))

## 31.0.3

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ private void drawGradient() {
mEndPos[1] * mSize[1],
mColors,
mLocations,
Shader.TileMode.MIRROR);
Shader.TileMode.CLAMP);
mPaint.setShader(mShader);
invalidate();
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 26 additions & 6 deletions apps/native-component-list/screens/LinearGradientScreen.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { LinearGradient } from 'expo';
import React from 'react';
import { Text, View } from 'react-native';
import { Text, ScrollView, View, Image } from 'react-native';

function incrementColor(color, step) {
const intColor = parseInt(color.substr(1), 16);
Expand Down Expand Up @@ -35,11 +35,10 @@ export default class LinearGradientScreen extends React.Component {

render() {
return (
<View
style={{
flex: 1,
<ScrollView
style={{ flex: 1 }}
contentContainerStyle={{
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 10,
}}>
<LinearGradient
Expand All @@ -48,7 +47,28 @@ export default class LinearGradientScreen extends React.Component {
/>
<Text style={{ color: this.state.colorTop }}>{this.state.colorTop}</Text>
<Text style={{ color: this.state.colorBottom }}>{this.state.colorBottom}</Text>
</View>

<View
style={{ flexDirection: 'row', alignSelf: 'stretch', justifyContent: 'space-evenly' }}>
<LinearGradient
colors={['white', 'red']}
start={[0.5, 0.5]}
end={[1, 1]}
style={{
width: 100,
height: 200,
borderWidth: 1,
marginVertical: 20,
borderColor: 'black',
}}
/>
<Image
source={require('../assets/images/confusing_gradient.png')}
style={{ width: 100, height: 200, marginVertical: 20 }}
/>
</View>
<Text style={{ marginHorizontal: 20 }}>The gradients above should look the same.</Text>
</ScrollView>
);
}
}
22 changes: 18 additions & 4 deletions ios/Exponent.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
19B2EF751FB25A1A003F2E1B /* EXCachedResourceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 19B2EF741FB25A1A003F2E1B /* EXCachedResourceManager.m */; };
2547422E20F4911E009F5FDA /* EXNativeMediaViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2547422D20F4911E009F5FDA /* EXNativeMediaViewManager.m */; };
2547423120F4ABFF009F5FDA /* EXAdIconViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 2547423020F4ABFF009F5FDA /* EXAdIconViewManager.m */; };
310AC14421918CB800E9F37E /* EXLinearGradientLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 310AC14321918CB800E9F37E /* EXLinearGradientLayer.m */; };
313D418020C84E9000FC913D /* EXModuleRegistryBinding.m in Sources */ = {isa = PBXBuildFile; fileRef = 313D417F20C84E9000FC913D /* EXModuleRegistryBinding.m */; };
3159BB0421806DAD002D2A81 /* RNSVGPainterBrush.m in Sources */ = {isa = PBXBuildFile; fileRef = 3159BAFC21806DAD002D2A81 /* RNSVGPainterBrush.m */; };
3159BB0521806DAD002D2A81 /* RNSVGBrush.m in Sources */ = {isa = PBXBuildFile; fileRef = 3159BB0021806DAD002D2A81 /* RNSVGBrush.m */; };
Expand Down Expand Up @@ -367,6 +368,8 @@
2547422D20F4911E009F5FDA /* EXNativeMediaViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EXNativeMediaViewManager.m; sourceTree = "<group>"; };
2547422F20F4ABFF009F5FDA /* EXAdIconViewManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EXAdIconViewManager.h; sourceTree = "<group>"; };
2547423020F4ABFF009F5FDA /* EXAdIconViewManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EXAdIconViewManager.m; sourceTree = "<group>"; };
310AC14221918CB800E9F37E /* EXLinearGradientLayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EXLinearGradientLayer.h; sourceTree = "<group>"; };
310AC14321918CB800E9F37E /* EXLinearGradientLayer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EXLinearGradientLayer.m; sourceTree = "<group>"; };
313D417E20C84E8200FC913D /* EXModuleRegistryBinding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EXModuleRegistryBinding.h; sourceTree = "<group>"; };
313D417F20C84E9000FC913D /* EXModuleRegistryBinding.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EXModuleRegistryBinding.m; sourceTree = "<group>"; };
3159BAFB21806DAD002D2A81 /* RNSVGBrush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSVGBrush.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1003,6 +1006,19 @@
name = Frameworks;
sourceTree = "<group>";
};
310AC14521918CC500E9F37E /* LinearGradient */ = {
isa = PBXGroup;
children = (
B5FB729B1FF6DADB001C764B /* EXLinearGradient.h */,
B5FB729C1FF6DADB001C764B /* EXLinearGradient.m */,
310AC14221918CB800E9F37E /* EXLinearGradientLayer.h */,
310AC14321918CB800E9F37E /* EXLinearGradientLayer.m */,
B5FB729D1FF6DADB001C764B /* EXLinearGradientManager.h */,
B5FB729E1FF6DADB001C764B /* EXLinearGradientManager.m */,
);
path = LinearGradient;
sourceTree = "<group>";
};
31DBC6AA20B5B4C60049A476 /* UniversalModules */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1726,10 +1742,7 @@
B5FB72961FF6DADB001C764B /* EXBlurView.m */,
B5FB72971FF6DADB001C764B /* EXBlurViewManager.h */,
B5FB72981FF6DADB001C764B /* EXBlurViewManager.m */,
B5FB729B1FF6DADB001C764B /* EXLinearGradient.h */,
B5FB729C1FF6DADB001C764B /* EXLinearGradient.m */,
B5FB729D1FF6DADB001C764B /* EXLinearGradientManager.h */,
B5FB729E1FF6DADB001C764B /* EXLinearGradientManager.m */,
310AC14521918CC500E9F37E /* LinearGradient */,
BB50E86820B9C0D3003C752D /* GestureHandler */,
B5FB72BC1FF6DADB001C764B /* GoogleMaps */,
B5FB72E11FF6DADB001C764B /* Lottie */,
Expand Down Expand Up @@ -2583,6 +2596,7 @@
783435DF205B8BEE00DD28C3 /* AIRGMSPolygon.m in Sources */,
B5FB74DB1FF6DADC001C764B /* RNBranchAgingItem.m in Sources */,
78D8252A204F826700CBD9D9 /* EXApiV2Result.m in Sources */,
310AC14421918CB800E9F37E /* EXLinearGradientLayer.m in Sources */,
B56C65CC204769F9002B2424 /* EXMenuViewController.m in Sources */,
B5AC39A71E95A90B00540AA7 /* EXKernel.m in Sources */,
783435DC205B8BEE00DD28C3 /* AIRGoogleMapCalloutManager.m in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
// Copyright 2015-present 650 Industries. All rights reserved.

#import "EXLinearGradient.h"
#import "EXLinearGradientLayer.h"
#import <React/RCTConvert.h>
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>

@implementation EXLinearGradient

+ (Class)layerClass
{
return [CAGradientLayer class];
return [EXLinearGradientLayer class];
}

- (CAGradientLayer *)gradientLayer
- (EXLinearGradientLayer *)gradientLayer
{
return (CAGradientLayer *)self.layer;
return (EXLinearGradientLayer *)self.layer;
}

- (void)setColors:(NSArray *)colorStrings
Expand All @@ -23,7 +23,7 @@ - (void)setColors:(NSArray *)colorStrings
for (NSString *colorString in colorStrings) {
UIColor *convertedColor = [RCTConvert UIColor:colorString];
if (convertedColor) {
[colors addObject:(id)convertedColor.CGColor];
[colors addObject:convertedColor];
}
}
self.gradientLayer.colors = colors;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2018-present 650 Industries. All rights reserved.

#import <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>

@interface EXLinearGradientLayer : CALayer

@property (nullable, nonatomic, copy) NSArray<UIColor *> *colors;
@property (nullable, nonatomic, copy) NSArray<NSNumber *> *locations;
@property (nonatomic) CGPoint startPoint;
@property (nonatomic) CGPoint endPoint;

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright 2018-present 650 Industries. All rights reserved.

#import "EXLinearGradientLayer.h"

@implementation EXLinearGradientLayer

- (instancetype)init
{
self = [super init];

if (self) {
self.needsDisplayOnBoundsChange = YES;
self.masksToBounds = YES;
_startPoint = CGPointMake(0.5, 0.0);
_endPoint = CGPointMake(0.5, 1.0);
}

return self;
}

- (void)setColors:(NSArray<id> *)colors
{
_colors = colors;
[self setNeedsDisplay];
}

- (void)setLocations:(NSArray<NSNumber *> *)locations
{
_locations = locations;
[self setNeedsDisplay];
}

- (void)setStartPoint:(CGPoint)startPoint
{
_startPoint = startPoint;
[self setNeedsDisplay];
}

- (void)setEndPoint:(CGPoint)endPoint
{
_endPoint = endPoint;
[self setNeedsDisplay];
}

- (void)display {
[super display];

BOOL hasAlpha = NO;

for (NSInteger i = 0; i < self.colors.count; i++) {
hasAlpha = hasAlpha || CGColorGetAlpha(self.colors[i].CGColor) < 1.0;
}

UIGraphicsBeginImageContextWithOptions(self.bounds.size, !hasAlpha, 0.0);
CGContextRef ref = UIGraphicsGetCurrentContext();
[self drawInContext:ref];

UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
self.contents = (__bridge id _Nullable)(image.CGImage);
self.contentsScale = image.scale;

UIGraphicsEndImageContext();
}

- (void)drawInContext:(CGContextRef)ctx
{
[super drawInContext:ctx];

CGContextSaveGState(ctx);

CGSize size = self.bounds.size;
if (!self.colors || self.colors.count == 0 || size.width == 0.0 || size.height == 0.0)
return;


CGFloat *locations = nil;

locations = malloc(sizeof(CGFloat) * self.colors.count);

for (NSInteger i = 0; i < self.colors.count; i++) {
if (self.locations.count > i) {
locations[i] = self.locations[i].floatValue;
} else {
locations[i] = (1.0 / (self.colors.count - 1)) * i;
}
}

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
NSMutableArray *colors = [[NSMutableArray alloc] initWithCapacity:self.colors.count];
for (UIColor *color in self.colors) {
[colors addObject:(id)color.CGColor];
}

CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFArrayRef)colors, locations);

free(locations);

CGPoint start = self.startPoint, end = self.endPoint;

CGContextDrawLinearGradient(ctx, gradient,
CGPointMake(start.x * size.width, start.y * size.height),
CGPointMake(end.x * size.width, end.y * size.height),
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);

CGContextRestoreGState(ctx);
}

@end

0 comments on commit 9b19e49

Please sign in to comment.