Skip to content

Commit

Permalink
cherrypick: Bring changes to improve our RCTUIGraphicsImageRenderer s…
Browse files Browse the repository at this point in the history
…him (#2219)

* Rename `RCTUIGraphicsImageRenderer` to `RCTMakeUIGraphicsImageRenderer` (facebook#46772)

Summary:
Because `UIGraphicsImageRenderer` doesn't exist on macOS, I need to shim it for React Native macOS (See #2209). I planned to use the name `RCTUIGraphicsImageRenderer`. However.. it seems that is used by a static helper function in `RCTBorderDrawing.m`. So.. let's rename it? The function is just a helper method to make an instance of the class, so I think the name `RCTMakeUIGraphicsImageRenderer` is slightly more idiomatic anyway.

This method is not public, so it should not break the public API of React Native.

[IOS] [CHANGED] - Rename `RCTUIGraphicsImageRenderer` to `RCTMakeUIGraphicsImageRenderer`

Pull Request resolved: facebook#46772

Test Plan: CI should pass

Reviewed By: joevilches

Differential Revision: D63765490

Pulled By: cipolleschi

fbshipit-source-id: de68dce0f92ec249ea8586dbf7b9ba34a8476074

* fix(iOS): Replace uses of `CGColorRef` with UIColor to avoid manual memory management (facebook#46847)

Summary:
Update React Native on iOS to do less manual memory management, by replacing uses of `CGColorRef` with `UIColor`, and only calling `UIColor.CGColor` when needed. This results in much less manual memory management, which is probably a good thing in 2024 :D. The downside is there is a breaking change: the signature of a method in `RCTBorderDrawing` changes.

This is a followup to facebook#46797 . After that PR merged and I tested merging React Native macOS to the `0.76-stable` branch cut commit again, I saw even more places I needed to manually call `CGColorRetain` / `CGColorRelease`. The reason is due to React Native macOS specifics (explained below) and I could update React Native macOS to not need these changes, but I thought I would at least throw up a PR to propose the changes, as it may be good to move away from using Core Graphics' C base API as much as possible.

With #2209 , I wrote a shim of [UIGraphicsImageRenderer](https://developer.apple.com/documentation/uikit/uigraphicsimagerenderer) for macOS. The main difference is my shim calls the NSImage API [imageWithSize:flipped:drawingHandler:](https://developer.apple.com/documentation/appkit/nsimage/1519860-imagewithsize?language=objc) to shim the UIGraphicsImageRenderer API [imageWithData:](https://developer.apple.com/documentation/uikit/uiimage/1624137-imagewithdata). The difference between the two is that the macOS API copies the block, and executes it when Appkit is about to draw the image, while the iOS API executes the block right away. Because of this, I think I am hitting way more places where `CGColorRef` variables needed to be retained / released. Additionally, I hit more of them when I merge to the 0.76 branch cut commit (this is why I didn't catch it with facebook#46797).

Given this constraint, I have a couple of options:
1. Refactor my macOS shim to use the deprecated API [[NSImage lockFocus]](https://developer.apple.com/documentation/appkit/nsimage/1519891-lockfocus)
    - I am not a fan of this because `lockFocus` was deprecated for `imageWithSize:flipped:drawingHandler:`
2. Refactor my macOS shim to do what we used to do: Create a CGContext manually and write it to an image
    - This is probably OK. Relies on less Appkit specifics, and potentially gives more control to RN on the rendering layer, which I recall lenaic told me is better for Fabric)
3. Refactor React Native to avoid CGColorRef altogether, and use `UIColor` (which ARC will memory manage for us) as much as possible.
    - This is the approach of this PR and my preferred approach. The downside is this changes the signature of some of the methods in `RCTBorderDrawing` which is a breaking change. I've seen other PRs do this, so maybe its OK?

[IOS] [BREAKING] -  Replace uses of `CGColorRef` with UIColor to avoid manual memory management

Pull Request resolved: facebook#46847

Test Plan: Launching RNTester's View example (which tests a lot of the border / outline / shadow rendering) does not crash for me on both iOS and macOS.

Reviewed By: NickGerleman

Differential Revision: D63989547

Pulled By: joevilches

fbshipit-source-id: 5e85e17567e3dd8b4b0388452398954ad2e02464

* Followup fixes

* Remove extra scaleFactor argument that is now unused

* Further reduce diffs related to scaleFactor

* fix typos and dead code
  • Loading branch information
Saadnajmi authored Oct 13, 2024
1 parent 8cd5b67 commit db89ff4
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,8 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &

// `shadowColor`
if (oldViewProps.shadowColor != newViewProps.shadowColor) {
CGColorRef shadowColor = RCTCreateCGColorRefFromSharedColor(newViewProps.shadowColor);
self.layer.shadowColor = shadowColor;
CGColorRelease(shadowColor);
RCTUIColor *shadowColor = RCTUIColorFromSharedColor(newViewProps.shadowColor); // [macOS]
self.layer.shadowColor = shadowColor.CGColor;
needsInvalidateLayer = YES;
}

Expand Down Expand Up @@ -584,18 +583,10 @@ static RCTCornerRadii RCTCornerRadiiFromBorderRadii(BorderRadii borderRadii)
static RCTBorderColors RCTCreateRCTBorderColorsFromBorderColors(BorderColors borderColors)
{
return RCTBorderColors{
.top = RCTCreateCGColorRefFromSharedColor(borderColors.top),
.left = RCTCreateCGColorRefFromSharedColor(borderColors.left),
.bottom = RCTCreateCGColorRefFromSharedColor(borderColors.bottom),
.right = RCTCreateCGColorRefFromSharedColor(borderColors.right)};
}

static void RCTReleaseRCTBorderColors(RCTBorderColors borderColors)
{
CGColorRelease(borderColors.top);
CGColorRelease(borderColors.left);
CGColorRelease(borderColors.bottom);
CGColorRelease(borderColors.right);
.top = RCTUIColorFromSharedColor(borderColors.top),
.left = RCTUIColorFromSharedColor(borderColors.left),
.bottom = RCTUIColorFromSharedColor(borderColors.bottom),
.right = RCTUIColorFromSharedColor(borderColors.right)};
}

static CALayerCornerCurve CornerCurveFromBorderCurve(BorderCurve borderCurve)
Expand Down Expand Up @@ -800,24 +791,22 @@ - (void)invalidateLayer
(*borderMetrics.borderColors.left).getUIColor() != nullptr));

#if !TARGET_OS_OSX // [macOS]
CGColorRef backgroundColor = [_backgroundColor resolvedColorWithTraitCollection:self.traitCollection].CGColor;
RCTUIColor *backgroundColor = [_backgroundColor resolvedColorWithTraitCollection:self.traitCollection];
#else // [macOS
CGColorRef backgroundColor = _backgroundColor.CGColor;
RCTUIColor *backgroundColor = _backgroundColor;
#endif // macOS]

if (useCoreAnimationBorderRendering) {
layer.mask = nil;
[_borderLayer removeFromSuperlayer];

layer.borderWidth = (CGFloat)borderMetrics.borderWidths.left;
CGColorRef borderColor = RCTCreateCGColorRefFromSharedColor(borderMetrics.borderColors.left);
layer.borderColor = borderColor;
CGColorRelease(borderColor);
layer.borderColor = RCTUIColorFromSharedColor(borderMetrics.borderColors.left).CGColor;
layer.cornerRadius = (CGFloat)borderMetrics.borderRadii.topLeft;

layer.cornerCurve = CornerCurveFromBorderCurve(borderMetrics.borderCurves.topLeft);

layer.backgroundColor = backgroundColor;
layer.backgroundColor = backgroundColor.CGColor;
} else {
if (!_borderLayer) {
CALayer *borderLayer = [CALayer new];
Expand All @@ -835,23 +824,15 @@ - (void)invalidateLayer

RCTBorderColors borderColors = RCTCreateRCTBorderColorsFromBorderColors(borderMetrics.borderColors);

#if TARGET_OS_OSX // [macOS
CGFloat scaleFactor = _layoutMetrics.pointScaleFactor;
#else
// On iOS setting the scaleFactor to 0.0 will default to the device's native scale factor.
CGFloat scaleFactor = 0.0;
#endif // macOS]
UIImage *image = RCTGetBorderImage(
RCTBorderStyleFromBorderStyle(borderMetrics.borderStyles.left),
layer.bounds.size,
RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii),
RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths),
borderColors,
backgroundColor,
self.clipsToBounds,
scaleFactor); // [macOS]
self.clipsToBounds);

RCTReleaseRCTBorderColors(borderColors);

if (image == nil) {
_borderLayer.contents = nil;
Expand All @@ -866,6 +847,7 @@ - (void)invalidateLayer
_borderLayer.contents = (id)image.CGImage;
_borderLayer.contentsScale = image.scale;
#else // [macOS
CGFloat scaleFactor = _layoutMetrics.pointScaleFactor;
_borderLayer.contents = [image layerContentsForContentsScale:scaleFactor];
_borderLayer.contentsScale = scaleFactor;
#endif // macOS]
Expand Down
6 changes: 0 additions & 6 deletions packages/react-native/React/Fabric/RCTConversions.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,6 @@ inline RCTUIColor *_Nullable RCTUIColorFromSharedColor(const facebook::react::Sh
return RCTPlatformColorFromColor(*sharedColor);
}

inline CF_RETURNS_RETAINED CGColorRef _Nullable RCTCreateCGColorRefFromSharedColor(
const facebook::react::SharedColor &sharedColor)
{
return CGColorRetain(RCTUIColorFromSharedColor(sharedColor).CGColor);
}

inline CGPoint RCTCGPointFromPoint(const facebook::react::Point &point)
{
return {point.x, point.y};
Expand Down
13 changes: 6 additions & 7 deletions packages/react-native/React/Views/RCTBorderDrawing.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ typedef struct {
} RCTCornerInsets;

typedef struct {
CGColorRef top;
CGColorRef left;
CGColorRef bottom;
CGColorRef right;
RCTUIColor *top;
RCTUIColor *left;
RCTUIColor *bottom;
RCTUIColor *right;
} RCTBorderColors;

/**
Expand Down Expand Up @@ -64,6 +64,5 @@ RCT_EXTERN UIImage *RCTGetBorderImage(
RCTCornerRadii cornerRadii,
UIEdgeInsets borderInsets,
RCTBorderColors borderColors,
CGColorRef backgroundColor,
BOOL drawToEdge,
CGFloat scaleFactor); // [macOS]
RCTUIColor *backgroundColor, // [macOS]
BOOL drawToEdge);
65 changes: 30 additions & 35 deletions packages/react-native/React/Views/RCTBorderDrawing.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ BOOL RCTCornerRadiiAreEqual(RCTCornerRadii cornerRadii)

BOOL RCTBorderColorsAreEqual(RCTBorderColors borderColors)
{
return CGColorEqualToColor(borderColors.left, borderColors.right) &&
CGColorEqualToColor(borderColors.left, borderColors.top) &&
CGColorEqualToColor(borderColors.left, borderColors.bottom);
return CGColorEqualToColor(borderColors.left.CGColor, borderColors.right.CGColor) &&
CGColorEqualToColor(borderColors.left.CGColor, borderColors.top.CGColor) &&
CGColorEqualToColor(borderColors.left.CGColor, borderColors.bottom.CGColor);
}

RCTCornerInsets RCTGetCornerInsets(RCTCornerRadii cornerRadii, UIEdgeInsets edgeInsets)
Expand Down Expand Up @@ -172,9 +172,9 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
}

static RCTUIGraphicsImageRenderer * // [macOS]
RCTMakeUIGraphicsImageRenderer(CGSize size, CGColorRef backgroundColor, BOOL hasCornerRadii, BOOL drawToEdge) // [macOS]
RCTMakeUIGraphicsImageRenderer(CGSize size, RCTUIColor *backgroundColor, BOOL hasCornerRadii, BOOL drawToEdge) // [macOS]
{
const CGFloat alpha = CGColorGetAlpha(backgroundColor);
const CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor);
const BOOL opaque = (drawToEdge || !hasCornerRadii) && alpha == 1.0;
RCTUIGraphicsImageRendererFormat *const rendererFormat = [RCTUIGraphicsImageRendererFormat defaultFormat]; // [macOS]
rendererFormat.opaque = opaque;
Expand All @@ -187,10 +187,9 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
CGSize viewSize,
UIEdgeInsets borderInsets,
RCTBorderColors borderColors,
CGColorRef backgroundColor,
BOOL drawToEdge,
CGFloat scaleFactor // [macOS]
) {
RCTUIColor *backgroundColor, // [macOS]
BOOL drawToEdge)
{
const BOOL hasCornerRadii = RCTCornerRadiiAreAboveThreshold(cornerRadii);
const RCTCornerInsets cornerInsets = RCTGetCornerInsets(cornerRadii, borderInsets);

Expand Down Expand Up @@ -227,19 +226,17 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
} // macOS]

RCTUIGraphicsImageRenderer *const imageRenderer =
RCTMakeUIGraphicsImageRenderer(size, backgroundColor, hasCornerRadii, drawToEdge); // [macOS]
CGColorRetain(backgroundColor); // [macOS] CGColorRefs are not atuomtically retained when passed into a block
RCTMakeUIGraphicsImageRenderer(size, backgroundColor, hasCornerRadii, drawToEdge);
UIImage *image = [imageRenderer imageWithActions:^(RCTUIGraphicsImageRendererContext *_Nonnull rendererContext) { // [macOS]
const CGContextRef context = rendererContext.CGContext;
const CGRect rect = {.size = size};
CGPathRef path = RCTPathCreateOuterOutline(drawToEdge, rect, cornerRadii);

if (backgroundColor) {
CGContextSetFillColorWithColor(context, backgroundColor);
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGContextAddPath(context, path);
CGContextFillPath(context);
}
CGColorRelease(backgroundColor); // [macOS]

CGContextAddPath(context, path);
CGPathRelease(path);
Expand All @@ -251,7 +248,7 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn

BOOL hasEqualColors = RCTBorderColorsAreEqual(borderColors);
if ((drawToEdge || !hasCornerRadii) && hasEqualColors) {
CGContextSetFillColorWithColor(context, borderColors.left);
CGContextSetFillColorWithColor(context, borderColors.left.CGColor);
CGContextAddRect(context, rect);
CGContextAddPath(context, insetPath);
CGContextEOFillPath(context);
Expand Down Expand Up @@ -316,7 +313,7 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
}
}

CGColorRef currentColor = NULL;
RCTUIColor *currentColor = nil;

// RIGHT
if (borderInsets.right > 0) {
Expand All @@ -340,8 +337,8 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
(CGPoint){size.width, size.height},
};

if (!CGColorEqualToColor(currentColor, borderColors.bottom)) {
CGContextSetFillColorWithColor(context, currentColor);
if (!CGColorEqualToColor(currentColor.CGColor, borderColors.bottom.CGColor)) {
CGContextSetFillColorWithColor(context, currentColor.CGColor);
CGContextFillPath(context);
currentColor = borderColors.bottom;
}
Expand All @@ -357,8 +354,8 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
(CGPoint){0, size.height},
};

if (!CGColorEqualToColor(currentColor, borderColors.left)) {
CGContextSetFillColorWithColor(context, currentColor);
if (!CGColorEqualToColor(currentColor.CGColor, borderColors.left.CGColor)) {
CGContextSetFillColorWithColor(context, currentColor.CGColor);
CGContextFillPath(context);
currentColor = borderColors.left;
}
Expand All @@ -374,15 +371,15 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
(CGPoint){size.width, 0},
};

if (!CGColorEqualToColor(currentColor, borderColors.top)) {
CGContextSetFillColorWithColor(context, currentColor);
if (!CGColorEqualToColor(currentColor.CGColor, borderColors.top.CGColor)) {
CGContextSetFillColorWithColor(context, currentColor.CGColor);
CGContextFillPath(context);
currentColor = borderColors.top;
}
CGContextAddLines(context, points, sizeof(points) / sizeof(*points));
}

CGContextSetFillColorWithColor(context, currentColor);
CGContextSetFillColorWithColor(context, currentColor.CGColor);
CGContextFillPath(context);
}

Expand Down Expand Up @@ -466,10 +463,9 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
CGSize viewSize,
UIEdgeInsets borderInsets,
RCTBorderColors borderColors,
CGColorRef backgroundColor,
BOOL drawToEdge,
CGFloat scaleFactor // [macOS]
) {
RCTUIColor *backgroundColor, // [macOS]
BOOL drawToEdge)
{
NSCParameterAssert(borderStyle == RCTBorderStyleDashed || borderStyle == RCTBorderStyleDotted);

if (!RCTBorderColorsAreEqual(borderColors) || !RCTBorderInsetsAreEqual(borderInsets)) {
Expand All @@ -489,7 +485,7 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn

const BOOL hasCornerRadii = RCTCornerRadiiAreAboveThreshold(cornerRadii);
RCTUIGraphicsImageRenderer *const imageRenderer = // [macOS]
RCTMakeUIGraphicsImageRenderer(viewSize, backgroundColor, hasCornerRadii, drawToEdge); // [macOS]
RCTMakeUIGraphicsImageRenderer(viewSize, backgroundColor, hasCornerRadii, drawToEdge);
return [imageRenderer imageWithActions:^(RCTUIGraphicsImageRendererContext *_Nonnull rendererContext) { // [macOS]
const CGContextRef context = rendererContext.CGContext;
const CGRect rect = {.size = viewSize};
Expand All @@ -499,7 +495,7 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
CGContextAddPath(context, outerPath);
CGPathRelease(outerPath);

CGContextSetFillColorWithColor(context, backgroundColor);
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
CGContextFillPath(context);
}

Expand All @@ -518,7 +514,7 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
CGContextSetStrokeColorWithColor(context, [RCTUIColor yellowColor].CGColor); // [macOS]

CGContextAddPath(context, path);
CGContextSetStrokeColorWithColor(context, borderColors.top);
CGContextSetStrokeColorWithColor(context, borderColors.top.CGColor);
CGContextStrokePath(context);

CGPathRelease(path);
Expand All @@ -531,17 +527,16 @@ static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCorn
RCTCornerRadii cornerRadii,
UIEdgeInsets borderInsets,
RCTBorderColors borderColors,
CGColorRef backgroundColor,
BOOL drawToEdge,
CGFloat scaleFactor // [macOS]
) {
RCTUIColor *backgroundColor, // [macOS]
BOOL drawToEdge)
{
switch (borderStyle) {
case RCTBorderStyleSolid:
return RCTGetSolidBorderImage(cornerRadii, viewSize, borderInsets, borderColors, backgroundColor, drawToEdge, scaleFactor); // [macOS]
return RCTGetSolidBorderImage(cornerRadii, viewSize, borderInsets, borderColors, backgroundColor, drawToEdge);
case RCTBorderStyleDashed:
case RCTBorderStyleDotted:
return RCTGetDashedOrDottedBorderImage(
borderStyle, cornerRadii, viewSize, borderInsets, borderColors, backgroundColor, drawToEdge, scaleFactor); // [macOS]
borderStyle, cornerRadii, viewSize, borderInsets, borderColors, backgroundColor, drawToEdge);
case RCTBorderStyleUnset:
break;
}
Expand Down
37 changes: 12 additions & 25 deletions packages/react-native/React/Views/RCTView.m
Original file line number Diff line number Diff line change
Expand Up @@ -1117,10 +1117,10 @@ - (RCTBorderColors)borderColors
#endif // [macOS]

return (RCTBorderColors){
(borderTopColor ?: borderColor).CGColor,
(directionAwareBorderLeftColor ?: borderColor).CGColor,
(borderBottomColor ?: borderColor).CGColor,
(directionAwareBorderRightColor ?: borderColor).CGColor,
(borderTopColor ?: borderColor),
(directionAwareBorderLeftColor ?: borderColor),
(borderBottomColor ?: borderColor),
(directionAwareBorderRightColor ?: borderColor),
};
}

Expand Down Expand Up @@ -1165,18 +1165,17 @@ - (void)displayLayer:(CALayer *)layer
// the content. For this reason, only use iOS border drawing when clipping
// or when the border is hidden.

(borderInsets.top == 0 || (borderColors.top && CGColorGetAlpha(borderColors.top) == 0) || self.clipsToBounds);
(borderInsets.top == 0 || (borderColors.top && CGColorGetAlpha(borderColors.top.CGColor) == 0) ||
self.clipsToBounds);

// iOS clips to the outside of the border, but CSS clips to the inside. To
// solve this, we'll need to add a container view inside the main view to
// correctly clip the subviews.

CGColorRef backgroundColor;

#if !TARGET_OS_OSX // [macOS]
backgroundColor = [_backgroundColor resolvedColorWithTraitCollection:self.traitCollection].CGColor;
RCTUIColor *backgroundColor = [_backgroundColor resolvedColorWithTraitCollection:self.traitCollection];
#else // [macOS
backgroundColor = [_backgroundColor CGColor];
RCTUIColor *backgroundColor = _backgroundColor;
#endif // macOS]

#if TARGET_OS_OSX // [macOS
Expand All @@ -1194,30 +1193,17 @@ - (void)displayLayer:(CALayer *)layer
#endif // macOS]
if (useIOSBorderRendering) {
layer.cornerRadius = cornerRadii.topLeft;
layer.borderColor = borderColors.left;
layer.borderColor = borderColors.left.CGColor;
layer.borderWidth = borderInsets.left;
layer.backgroundColor = backgroundColor;
layer.backgroundColor = backgroundColor.CGColor;
layer.contents = nil;
layer.needsDisplayOnBoundsChange = NO;
layer.mask = nil;
return;
}

#if TARGET_OS_OSX // [macOS
CGFloat scaleFactor = self.window.backingScaleFactor;
if (scaleFactor == 0.0 && RCTRunningInTestEnvironment()) {
// When running in the test environment the view is not on screen.
// Use a scaleFactor of 1 so that the test results are machine independent.
scaleFactor = 1;
}
RCTAssert(scaleFactor != 0.0, @"displayLayer occurs before the view is in a window?");
#else
// On iOS setting the scaleFactor to 0.0 will default to the device's native scale factor.
CGFloat scaleFactor = 0.0;
#endif // macOS]

UIImage *image = RCTGetBorderImage(
_borderStyle, layer.bounds.size, cornerRadii, borderInsets, borderColors, backgroundColor, self.clipsToBounds, scaleFactor); // [macOS]
_borderStyle, layer.bounds.size, cornerRadii, borderInsets, borderColors, backgroundColor, self.clipsToBounds);

layer.backgroundColor = NULL;

Expand All @@ -1238,6 +1224,7 @@ - (void)displayLayer:(CALayer *)layer
layer.contents = (id)image.CGImage;
layer.contentsScale = image.scale;
#else // [macOS
CGFloat scaleFactor = self.window.backingScaleFactor;
layer.contents = [image layerContentsForContentsScale:scaleFactor];
layer.contentsScale = scaleFactor;
#endif // macOS]
Expand Down

0 comments on commit db89ff4

Please sign in to comment.