From 839ccd190807e8d2b9d6dae4f2c621568b44160b Mon Sep 17 00:00:00 2001 From: Adam Gleitman Date: Wed, 25 Nov 2020 14:55:50 -0800 Subject: [PATCH] feat: add macOS support --- RNSVG.podspec | 24 +++--- ios/Brushes/RNSVGContextBrush.m | 4 +- ios/Elements/RNSVGClipPath.m | 4 +- ios/Elements/RNSVGDefs.h | 2 +- ios/Elements/RNSVGDefs.m | 2 +- ios/Elements/RNSVGForeignObject.m | 4 +- ios/Elements/RNSVGGroup.h | 4 +- ios/Elements/RNSVGGroup.m | 12 +-- ios/Elements/RNSVGLinearGradient.m | 2 +- ios/Elements/RNSVGMarker.m | 2 +- ios/Elements/RNSVGMask.m | 2 +- ios/Elements/RNSVGPattern.m | 2 +- ios/Elements/RNSVGRadialGradient.m | 2 +- ios/Elements/RNSVGSvgView.h | 7 +- ios/Elements/RNSVGSvgView.m | 21 +++--- ios/Elements/RNSVGUse.m | 4 +- ios/RNSVGNode.h | 6 +- ios/RNSVGNode.m | 22 +++--- ios/RNSVGRenderable.h | 4 + ios/RNSVGRenderable.m | 11 ++- ios/RNSVGUIKit.h | 41 +++++++++++ ios/RNSVGUIKit.macos.m | 90 +++++++++++++++++++++++ ios/Text/RNSVGFontData.h | 3 +- ios/Text/RNSVGTSpan.h | 5 +- ios/Text/RNSVGTSpan.m | 50 +++++-------- ios/Text/RNSVGText.m | 8 +- ios/Text/RNSVGTopAlignedLabel.h | 13 ++++ ios/Text/RNSVGTopAlignedLabel.ios.m | 21 ++++++ ios/Text/RNSVGTopAlignedLabel.macos.m | 55 ++++++++++++++ ios/Utils/RNSVGBezierElement.h | 3 +- ios/Utils/RNSVGLength.h | 2 +- ios/Utils/RNSVGMarkerPosition.h | 4 +- ios/Utils/RNSVGPathMeasure.h | 2 +- ios/Utils/RNSVGPathParser.h | 3 +- ios/Utils/RNSVGViewBox.h | 3 +- ios/ViewManagers/RNSVGDefsManager.m | 2 +- ios/ViewManagers/RNSVGNodeManager.m | 2 +- ios/ViewManagers/RNSVGRenderableManager.m | 16 ++-- ios/ViewManagers/RNSVGSvgViewManager.m | 6 +- 39 files changed, 350 insertions(+), 120 deletions(-) create mode 100644 ios/RNSVGUIKit.h create mode 100644 ios/RNSVGUIKit.macos.m create mode 100644 ios/Text/RNSVGTopAlignedLabel.h create mode 100644 ios/Text/RNSVGTopAlignedLabel.ios.m create mode 100644 ios/Text/RNSVGTopAlignedLabel.macos.m diff --git a/RNSVG.podspec b/RNSVG.podspec index 1336da48..d8e01ad6 100644 --- a/RNSVG.podspec +++ b/RNSVG.podspec @@ -3,15 +3,17 @@ require 'json' package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) Pod::Spec.new do |s| - s.name = 'RNSVG' - s.version = package['version'] - s.summary = package['description'] - s.license = package['license'] - s.homepage = package['homepage'] - s.authors = 'Horcrux Chen' - s.platforms = { :ios => "9.0", :tvos => "9.2" } - s.source = { :git => 'https://github.com/react-native-community/react-native-svg.git', :tag => "v#{s.version}" } - s.source_files = 'ios/**/*.{h,m}' - s.requires_arc = true - s.dependency 'React' + s.name = 'RNSVG' + s.version = package['version'] + s.summary = package['description'] + s.license = package['license'] + s.homepage = package['homepage'] + s.authors = 'Horcrux Chen' + s.platforms = { :osx => "10.14", :ios => "9.0", :tvos => "9.2" } + s.source = { :git => 'https://github.com/react-native-community/react-native-svg.git', :tag => "v#{s.version}" } + s.source_files = 'ios/**/*.{h,m}' + s.ios.exclude_files = '**/*.macos.{h,m}' + s.osx.exclude_files = '**/*.ios.{h,m}' + s.requires_arc = true + s.dependency 'React' end diff --git a/ios/Brushes/RNSVGContextBrush.m b/ios/Brushes/RNSVGContextBrush.m index 43d7c2af..e25d207b 100644 --- a/ios/Brushes/RNSVGContextBrush.m +++ b/ios/Brushes/RNSVGContextBrush.m @@ -50,7 +50,7 @@ BOOL fillColor; if (brush.class == RNSVGBrush.class) { - CGContextSetFillColorWithColor(context, [element.tintColor CGColor]); + CGContextSetFillColorWithColor(context, [element.defaultColor CGColor]); fillColor = YES; } else { fillColor = [brush applyFillColor:context opacity:opacity]; @@ -73,7 +73,7 @@ BOOL strokeColor; if (brush.class == RNSVGBrush.class) { - CGContextSetStrokeColorWithColor(context, [element.tintColor CGColor]); + CGContextSetStrokeColorWithColor(context, [element.defaultColor CGColor]); strokeColor = YES; } else { strokeColor = [brush applyStrokeColor:context opacity:opacity]; diff --git a/ios/Elements/RNSVGClipPath.m b/ios/Elements/RNSVGClipPath.m index b1746dbd..8def3785 100644 --- a/ios/Elements/RNSVGClipPath.m +++ b/ios/Elements/RNSVGClipPath.m @@ -19,9 +19,9 @@ - (BOOL)isSimpleClipPath { - NSArray *children = self.subviews; + NSArray *children = self.subviews; if (children.count == 1) { - UIView* child = children[0]; + RNSVGView* child = children[0]; if ([child class] != [RNSVGGroup class]) { return true; } diff --git a/ios/Elements/RNSVGDefs.h b/ios/Elements/RNSVGDefs.h index 9e58b40c..be73cfdf 100644 --- a/ios/Elements/RNSVGDefs.h +++ b/ios/Elements/RNSVGDefs.h @@ -9,7 +9,7 @@ #import "RNSVGNode.h" /** - * RNSVG defination are implemented as abstract UIViews for all elements inside Defs. + * RNSVG defination are implemented as abstract views for all elements inside Defs. */ @interface RNSVGDefs : RNSVGNode diff --git a/ios/Elements/RNSVGDefs.m b/ios/Elements/RNSVGDefs.m index 1e32b2eb..22fe9871 100644 --- a/ios/Elements/RNSVGDefs.m +++ b/ios/Elements/RNSVGDefs.m @@ -27,7 +27,7 @@ }]; } -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event +- (RNSVGPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { return nil; } diff --git a/ios/Elements/RNSVGForeignObject.m b/ios/Elements/RNSVGForeignObject.m index fd88f912..a3f8fde6 100644 --- a/ios/Elements/RNSVGForeignObject.m +++ b/ios/Elements/RNSVGForeignObject.m @@ -12,7 +12,7 @@ @implementation RNSVGForeignObject -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event +- (RNSVGPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { return nil; } @@ -42,7 +42,7 @@ __block CGRect bounds = CGRectNull; - [self traverseSubviews:^(UIView *node) { + [self traverseSubviews:^(RNSVGView *node) { if ([node isKindOfClass:[RNSVGMask class]] || [node isKindOfClass:[RNSVGClipPath class]]) { // no-op } else if ([node isKindOfClass:[RNSVGNode class]]) { diff --git a/ios/Elements/RNSVGGroup.h b/ios/Elements/RNSVGGroup.h index 44020140..0d7202f4 100644 --- a/ios/Elements/RNSVGGroup.h +++ b/ios/Elements/RNSVGGroup.h @@ -7,7 +7,9 @@ */ #import -#import + +#import "RNSVGUIKit.h" + #import "RNSVGContainer.h" #import "RNSVGCGFCRule.h" #import "RNSVGSvgView.h" diff --git a/ios/Elements/RNSVGGroup.m b/ios/Elements/RNSVGGroup.m index a8787b8d..0322c7c7 100644 --- a/ios/Elements/RNSVGGroup.m +++ b/ios/Elements/RNSVGGroup.m @@ -38,7 +38,7 @@ __block CGRect bounds = CGRectNull; - [self traverseSubviews:^(UIView *node) { + [self traverseSubviews:^(RNSVGView *node) { if ([node isKindOfClass:[RNSVGMask class]] || [node isKindOfClass:[RNSVGClipPath class]]) { // no-op } else if ([node isKindOfClass:[RNSVGNode class]]) { @@ -160,7 +160,7 @@ return cached; } -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event +- (RNSVGPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { CGPoint transformed = CGPointApplyAffineTransform(point, self.invmatrix); transformed = CGPointApplyAffineTransform(transformed, self.invTransform); @@ -192,7 +192,7 @@ } } - for (UIView *node in [self.subviews reverseObjectEnumerator]) { + for (RNSVGView *node in [self.subviews reverseObjectEnumerator]) { if ([node isKindOfClass:[RNSVGNode class]]) { if ([node isKindOfClass:[RNSVGMask class]]) { continue; @@ -201,21 +201,21 @@ if (event) { svgNode.active = NO; } - UIView *hitChild = [svgNode hitTest:transformed withEvent:event]; + RNSVGPlatformView *hitChild = [svgNode hitTest:transformed withEvent:event]; if (hitChild) { svgNode.active = YES; return (svgNode.responsible || (svgNode != hitChild)) ? hitChild : self; } } else if ([node isKindOfClass:[RNSVGSvgView class]]) { RNSVGSvgView* svgView = (RNSVGSvgView*)node; - UIView *hitChild = [svgView hitTest:transformed withEvent:event]; + RNSVGPlatformView *hitChild = [svgView hitTest:transformed withEvent:event]; if (hitChild) { return hitChild; } } } - UIView *hitSelf = [super hitTest:transformed withEvent:event]; + RNSVGPlatformView *hitSelf = [super hitTest:transformed withEvent:event]; if (hitSelf) { return hitSelf; } diff --git a/ios/Elements/RNSVGLinearGradient.m b/ios/Elements/RNSVGLinearGradient.m index 708aea91..fde9f0a7 100644 --- a/ios/Elements/RNSVGLinearGradient.m +++ b/ios/Elements/RNSVGLinearGradient.m @@ -85,7 +85,7 @@ [self invalidate]; } -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event +- (RNSVGPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { return nil; } diff --git a/ios/Elements/RNSVGMarker.m b/ios/Elements/RNSVGMarker.m index c7b38cab..8065e394 100644 --- a/ios/Elements/RNSVGMarker.m +++ b/ios/Elements/RNSVGMarker.m @@ -13,7 +13,7 @@ @implementation RNSVGMarker -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event +- (RNSVGPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { return nil; } diff --git a/ios/Elements/RNSVGMask.m b/ios/Elements/RNSVGMask.m index 021d0b88..ee921a46 100644 --- a/ios/Elements/RNSVGMask.m +++ b/ios/Elements/RNSVGMask.m @@ -12,7 +12,7 @@ @implementation RNSVGMask -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event +- (RNSVGPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { return nil; } diff --git a/ios/Elements/RNSVGPattern.m b/ios/Elements/RNSVGPattern.m index 0186ea32..a35b5aec 100644 --- a/ios/Elements/RNSVGPattern.m +++ b/ios/Elements/RNSVGPattern.m @@ -20,7 +20,7 @@ return self; } -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event +- (RNSVGPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { return nil; } diff --git a/ios/Elements/RNSVGRadialGradient.m b/ios/Elements/RNSVGRadialGradient.m index 7956cbdf..992e3483 100644 --- a/ios/Elements/RNSVGRadialGradient.m +++ b/ios/Elements/RNSVGRadialGradient.m @@ -103,7 +103,7 @@ [self invalidate]; } -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event +- (RNSVGPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { return nil; } diff --git a/ios/Elements/RNSVGSvgView.h b/ios/Elements/RNSVGSvgView.h index 6510e1b5..b126a5ff 100644 --- a/ios/Elements/RNSVGSvgView.h +++ b/ios/Elements/RNSVGSvgView.h @@ -6,14 +6,15 @@ * LICENSE file in the root directory of this source tree. */ -#import +#import "RNSVGUIKit.h" + #import "RNSVGPainter.h" #import "RNSVGContainer.h" #import "RNSVGVBMOS.h" @class RNSVGNode; -@interface RNSVGSvgView : UIView +@interface RNSVGSvgView : RNSVGView @property (nonatomic, strong) RNSVGLength *bbWidth; @property (nonatomic, strong) RNSVGLength *bbHeight; @@ -30,8 +31,6 @@ @property (nonatomic, assign) CGAffineTransform invInitialCTM; @property (nonatomic, assign) CGAffineTransform viewBoxTransform; - - /** * define content as clipPath template. */ diff --git a/ios/Elements/RNSVGSvgView.m b/ios/Elements/RNSVGSvgView.m index 6cea082f..1f458c6a 100644 --- a/ios/Elements/RNSVGSvgView.m +++ b/ios/Elements/RNSVGSvgView.m @@ -25,22 +25,25 @@ - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { +#if !TARGET_OS_OSX + // TODO: Figure out the proper macOS equivalent // This is necessary to ensure that [self setNeedsDisplay] actually triggers // a redraw when our parent transitions between hidden and visible. self.contentMode = UIViewContentModeRedraw; +#endif rendered = false; } return self; } -- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex +- (void)insertReactSubview:(RNSVGView *)subview atIndex:(NSInteger)atIndex { [super insertReactSubview:subview atIndex:atIndex]; [self insertSubview:subview atIndex:atIndex]; [self invalidate]; } -- (void)removeReactSubview:(UIView *)subview +- (void)removeReactSubview:(RNSVGView *)subview { [super removeReactSubview:subview]; [self invalidate]; @@ -66,7 +69,7 @@ - (void)invalidate { - UIView* parent = self.superview; + RNSVGPlatformView* parent = self.superview; if ([parent isKindOfClass:[RNSVGNode class]]) { if (!rendered) { return; @@ -190,7 +193,7 @@ _invviewBoxTransform = CGAffineTransformIdentity; } - for (UIView *node in self.subviews) { + for (RNSVGView *node in self.subviews) { if ([node isKindOfClass:[RNSVGNode class]]) { RNSVGNode *svg = (RNSVGNode *)node; [svg renderTo:context @@ -203,7 +206,7 @@ - (void)drawRect:(CGRect)rect { - UIView* parent = self.superview; + RNSVGPlatformView* parent = self.superview; if ([parent isKindOfClass:[RNSVGNode class]]) { return; } @@ -214,7 +217,7 @@ _boundingBox = rect; CGContextRef context = UIGraphicsGetCurrentContext(); - for (UIView *node in self.subviews) { + for (RNSVGPlatformView *node in self.subviews) { if ([node isKindOfClass:[RNSVGNode class]]) { RNSVGNode *svg = (RNSVGNode *)node; if (svg.responsible && !self.responsible) { @@ -228,7 +231,7 @@ [self drawToContext:context withRect:rect]; } -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event +- (RNSVGPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { CGPoint transformed = point; if (self.align) { @@ -243,7 +246,7 @@ node.active = NO; } - UIView *hitChild = [node hitTest:transformed withEvent:event]; + RNSVGPlatformView *hitChild = [node hitTest:transformed withEvent:event]; if (hitChild) { node.active = YES; @@ -279,7 +282,7 @@ return base64; } -- (void)reactSetInheritedBackgroundColor:(UIColor *)inheritedBackgroundColor +- (void)reactSetInheritedBackgroundColor:(RNSVGColor *)inheritedBackgroundColor { self.backgroundColor = inheritedBackgroundColor; } diff --git a/ios/Elements/RNSVGUse.m b/ios/Elements/RNSVGUse.m index dc735f1d..4f7a0261 100644 --- a/ios/Elements/RNSVGUse.m +++ b/ios/Elements/RNSVGUse.m @@ -113,7 +113,7 @@ self.frame = bounds; } -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { +- (RNSVGPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { CGPoint transformed = CGPointApplyAffineTransform(point, self.invmatrix); transformed = CGPointApplyAffineTransform(transformed, self.invTransform); RNSVGNode const* template = [self.svgView getDefinedTemplate:self.href]; @@ -122,7 +122,7 @@ } else if (self.active) { return self; } - UIView const* hitChild = [template hitTest:transformed withEvent:event]; + RNSVGPlatformView const* hitChild = [template hitTest:transformed withEvent:event]; if (hitChild) { self.active = YES; return self; diff --git a/ios/RNSVGNode.h b/ios/RNSVGNode.h index 81513b30..340b5b51 100644 --- a/ios/RNSVGNode.h +++ b/ios/RNSVGNode.h @@ -13,11 +13,11 @@ @class RNSVGGroup; /** - * RNSVG nodes are implemented as base UIViews. They should be implementation for all basic + * RNSVG nodes are implemented as base NSViews/UIViews. They should be implementation for all basic *interfaces for all non-definition nodes. */ -@interface RNSVGNode : UIView +@interface RNSVGNode : RNSVGView /* N[1/Sqrt[2], 36] @@ -132,7 +132,7 @@ extern CGFloat const RNSVG_DEFAULT_FONT_SIZE; - (void)endTransparencyLayer:(CGContextRef)context; -- (void)traverseSubviews:(BOOL (^)(__kindof UIView *node))block; +- (void)traverseSubviews:(BOOL (^)(__kindof RNSVGView *node))block; - (void)clearChildCache; diff --git a/ios/RNSVGNode.m b/ios/RNSVGNode.m index 938f89ab..f5922d0d 100644 --- a/ios/RNSVGNode.m +++ b/ios/RNSVGNode.m @@ -46,14 +46,14 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; return self; } -- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex +- (void)insertReactSubview:(RNSVGView *)subview atIndex:(NSInteger)atIndex { [super insertReactSubview:subview atIndex:atIndex]; [self insertSubview:subview atIndex:atIndex]; [self invalidate]; } -- (void)removeReactSubview:(UIView *)subview +- (void)removeReactSubview:(RNSVGView *)subview { [super removeReactSubview:subview]; [self invalidate]; @@ -98,7 +98,7 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; { RNSVGNode* node = self; while (node != nil) { - UIView* parent = [node superview]; + RNSVGPlatformView* parent = [node superview]; if (![parent isKindOfClass:[RNSVGNode class]]) { return; @@ -124,7 +124,7 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; break; } - UIView* parent = [node superview]; + RNSVGPlatformView* parent = [node superview]; if (![node isKindOfClass:[RNSVGNode class]]) { node = nil; @@ -160,7 +160,7 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; return [glyphContext getFontSize]; } -- (void)reactSetInheritedBackgroundColor:(UIColor *)inheritedBackgroundColor +- (void)reactSetInheritedBackgroundColor:(RNSVGColor *)inheritedBackgroundColor { self.backgroundColor = inheritedBackgroundColor; } @@ -170,7 +170,11 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; _pointerEvents = pointerEvents; self.userInteractionEnabled = (pointerEvents != RCTPointerEventsNone); if (pointerEvents == RCTPointerEventsBoxNone) { +#if TARGET_OS_OSX + self.accessibilityModal = NO; +#else self.accessibilityViewIsModal = NO; +#endif } } @@ -390,7 +394,7 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; } // hitTest delagate -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event +- (RNSVGPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { // abstract @@ -403,7 +407,7 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; return _svgView; } - __kindof UIView *parent = self.superview; + __kindof RNSVGPlatformView *parent = self.superview; if ([parent class] == [RNSVGSvgView class]) { _svgView = parent; @@ -587,9 +591,9 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12; } } -- (void)traverseSubviews:(BOOL (^)(__kindof UIView *node))block +- (void)traverseSubviews:(BOOL (^)(__kindof RNSVGView *node))block { - for (UIView *node in self.subviews) { + for (RNSVGView *node in self.subviews) { if (!block(node)) { break; } diff --git a/ios/RNSVGRenderable.h b/ios/RNSVGRenderable.h index 7ae04898..0a451479 100644 --- a/ios/RNSVGRenderable.h +++ b/ios/RNSVGRenderable.h @@ -8,6 +8,8 @@ #import +#import "RNSVGUIKit.h" + #import "RNSVGBrush.h" #import "RNSVGCGFCRule.h" #import "RNSVGNode.h" @@ -32,6 +34,8 @@ @property (nonatomic, copy) NSArray *propList; @property (nonatomic, assign) CGPathRef hitArea; +- (RNSVGColor *)defaultColor; + - (void)setHitArea:(CGPathRef)path; - (NSArray *)getAttributeList; diff --git a/ios/RNSVGRenderable.m b/ios/RNSVGRenderable.m index 5fddadda..ca1c680e 100644 --- a/ios/RNSVGRenderable.m +++ b/ios/RNSVGRenderable.m @@ -41,6 +41,11 @@ static RNSVGRenderable * _contextElement; return self; } +- (RNSVGColor *)defaultColor +{ + return [self tintColor]; +} + - (void)invalidate { _sourceStrokeDashArray = nil; @@ -406,7 +411,7 @@ UInt32 saturate(CGFloat value) { if (self.fill) { if (self.fill.class == RNSVGBrush.class) { - CGContextSetFillColorWithColor(context, [self.tintColor CGColor]); + CGContextSetFillColorWithColor(context, [self.defaultColor CGColor]); fillColor = YES; } else { fillColor = [self.fill applyFillColor:context opacity:self.fillOpacity]; @@ -455,7 +460,7 @@ UInt32 saturate(CGFloat value) { BOOL strokeColor; if (self.stroke.class == RNSVGBrush.class) { - CGContextSetStrokeColorWithColor(context,[self.tintColor CGColor]); + CGContextSetStrokeColorWithColor(context,[self.defaultColor CGColor]); strokeColor = YES; } else { strokeColor = [self.stroke applyStrokeColor:context opacity:self.strokeOpacity]; @@ -513,7 +518,7 @@ UInt32 saturate(CGFloat value) { } // hitTest delegate -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event +- (RNSVGPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (!_hitArea) { return nil; diff --git a/ios/RNSVGUIKit.h b/ios/RNSVGUIKit.h new file mode 100644 index 00000000..fc140bba --- /dev/null +++ b/ios/RNSVGUIKit.h @@ -0,0 +1,41 @@ +// Most (if not all) of this file could probably go away once react-native-macos's version of RCTUIKit.h makes its way upstream. +// https://github.com/microsoft/react-native-macos/issues/242 + +#if !TARGET_OS_OSX + +#import + +#define RNSVGColor UIColor +#define RNSVGPlatformView UIView +#define RNSVGTextView UILabel +#define RNSVGView UIView + +#else // TARGET_OS_OSX [ + +#import + +#define RNSVGColor NSColor +#define RNSVGPlatformView NSView +#define RNSVGTextView NSTextView + +@interface RNSVGView : RCTUIView + +@property CGPoint center; +@property (nonatomic, strong) RNSVGColor *tintColor; + +@end + +// TODO: These could probably be a part of react-native-macos +@interface NSImage (RNSVGMacOSExtensions) +@property (readonly) CGImageRef CGImage; +@end + +@interface NSValue (RNSVGMacOSExtensions) ++ (NSValue *)valueWithCGAffineTransform:(CGAffineTransform)transform; ++ (NSValue *)valueWithCGPoint:(CGPoint)point; + +@property (readonly) CGAffineTransform CGAffineTransformValue; +@property (readonly) CGPoint CGPointValue; +@end + +#endif // ] TARGET_OS_OSX diff --git a/ios/RNSVGUIKit.macos.m b/ios/RNSVGUIKit.macos.m new file mode 100644 index 00000000..16b6fce5 --- /dev/null +++ b/ios/RNSVGUIKit.macos.m @@ -0,0 +1,90 @@ +#if TARGET_OS_OSX + +#import "RNSVGUIKit.h" + +@implementation RNSVGView +{ + NSColor *_tintColor; +} + +- (CGPoint)center +{ + NSRect frameRect = self.frame; + CGFloat xCenter = frameRect.origin.x + frameRect.size.width / 2; + CGFloat yCenter = frameRect.origin.y + frameRect.size.height / 2; + return CGPointMake(xCenter, yCenter); +} + +- (void)setCenter:(CGPoint)point +{ + NSRect frameRect = self.frame; + CGFloat xOrigin = frameRect.origin.x - frameRect.size.width / 2; + CGFloat yOrigin = frameRect.origin.y - frameRect.size.height / 2; + self.frame = CGRectMake(xOrigin, yOrigin, frameRect.size.width, frameRect.size.height); +} + +- (NSColor *)tintColor +{ + if (_tintColor != nil) { + return _tintColor; + } + + // To mimic iOS's tintColor, we crawl up the view hierarchy until either: + // (a) we find a valid color + // (b) we reach a view that isn't an RNSVGView + NSView *parentView = [self superview]; + if ([parentView isKindOfClass:[RNSVGView class]]) { + return [(RNSVGView *)parentView tintColor]; + } else { + return [NSColor controlAccentColor]; + } +} + +- (void)setTintColor:(NSColor *)tintColor +{ + _tintColor = tintColor; + [self setNeedsDisplay:YES]; +} + +@end + + +@implementation NSImage (RNSVGMacOSExtensions) + +- (CGImageRef) CGImage +{ + return [self CGImageForProposedRect:NULL context:NULL hints:NULL]; +} + +@end + + +@implementation NSValue (RNSVGMacOSExtensions) + ++ (NSValue *)valueWithCGAffineTransform:(CGAffineTransform)transform +{ + return [NSValue valueWithBytes:&transform objCType:@encode(CGAffineTransform)]; +} + ++ (NSValue *)valueWithCGPoint:(CGPoint)point +{ + return [NSValue valueWithBytes:&point objCType:@encode(CGPoint)]; +} + +- (CGAffineTransform)CGAffineTransformValue +{ + CGAffineTransform value; + [self getValue:&value]; + return value; +} + +- (CGPoint)CGPointValue +{ + CGPoint value; + [self getValue:&value]; + return value; +} + +@end + +#endif // ] TARGET_OS_OSX diff --git a/ios/Text/RNSVGFontData.h b/ios/Text/RNSVGFontData.h index a988ca4b..35ab92c7 100644 --- a/ios/Text/RNSVGFontData.h +++ b/ios/Text/RNSVGFontData.h @@ -1,5 +1,6 @@ #import -#import + +#import "RNSVGUIKit.h" #import "RNSVGTextProperties.h" #import "RNSVGPropHelper.h" diff --git a/ios/Text/RNSVGTSpan.h b/ios/Text/RNSVGTSpan.h index 9b7509fc..5758cebd 100644 --- a/ios/Text/RNSVGTSpan.h +++ b/ios/Text/RNSVGTSpan.h @@ -5,9 +5,12 @@ * This source code is licensed under the MIT-style license found in the * LICENSE file in the root directory of this source tree. */ -#import + #import #import + +#import "RNSVGUIKit.h" + #import "RNSVGText.h" @interface RNSVGTSpan : RNSVGText diff --git a/ios/Text/RNSVGTSpan.m b/ios/Text/RNSVGTSpan.m index 3567d17b..28bcbb7a 100644 --- a/ios/Text/RNSVGTSpan.m +++ b/ios/Text/RNSVGTSpan.m @@ -6,36 +6,17 @@ * LICENSE file in the root directory of this source tree. */ #import "RNSVGTSpan.h" +#import "RNSVGUIKit.h" #import "RNSVGText.h" #import "RNSVGTextPath.h" #import "RNSVGTextProperties.h" +#import "RNSVGTopAlignedLabel.h" #import "RNSVGPathMeasure.h" #import "RNSVGFontData.h" static NSCharacterSet *RNSVGTSpan_separators = nil; static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI; -@interface TopAlignedLabel : UILabel - -@end - - -@implementation TopAlignedLabel - -- (void)drawTextInRect:(CGRect) rect -{ - NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:self.text attributes:@{NSFontAttributeName:self.font}]; - rect.size.height = [attributedText boundingRectWithSize:rect.size - options:NSStringDrawingUsesLineFragmentOrigin - context:nil].size.height; - if (self.numberOfLines != 0) { - rect.size.height = MIN(rect.size.height, self.numberOfLines * self.font.lineHeight); - } - [super drawTextInRect:rect]; -} - -@end - @implementation RNSVGTSpan { CGFloat startOffset; @@ -87,7 +68,7 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI; CGColorRef color; if (self.fill) { if (self.fill.class == RNSVGBrush.class) { - color = [self.tintColor CGColor]; + color = [self.defaultColor CGColor]; [self drawWrappedText:context gc:gc rect:rect color:color]; } else { color = [self.fill getColorWithOpacity:self.fillOpacity]; @@ -100,7 +81,7 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI; } if (self.stroke) { if (self.stroke.class == RNSVGBrush.class) { - color = [self.tintColor CGColor]; + color = [self.defaultColor CGColor]; [self drawWrappedText:context gc:gc rect:rect color:color]; } else { color = [self.stroke getColorWithOpacity:self.strokeOpacity]; @@ -116,7 +97,7 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI; NSUInteger count = [emoji count]; CGFloat fontSize = [gc getFontSize]; for (NSUInteger i = 0; i < count; i++) { - UILabel *label = [emoji objectAtIndex:i]; + RNSVGPlatformView *label = [emoji objectAtIndex:i]; NSValue *transformValue = [emojiTransform objectAtIndex:i]; CGAffineTransform transform = [transformValue CGAffineTransformValue]; CGContextConcatCTM(context, transform); @@ -163,7 +144,7 @@ static CGFloat RNSVGTSpan_radToDeg = 180 / (CGFloat)M_PI; return attrs; } -TopAlignedLabel *label; +RNSVGTopAlignedLabel *label; - (void)drawWrappedText:(CGContextRef)context gc:(RNSVGGlyphContext *)gc rect:(CGRect)rect color:(CGColorRef)color { [self pushGlyphContext]; if (fontRef != nil) { @@ -198,17 +179,18 @@ TopAlignedLabel *label; UIFont *font = (__bridge UIFont *)(fontRef); if (!label) { - label = [[TopAlignedLabel alloc] init]; + label = [[RNSVGTopAlignedLabel alloc] init]; } label.attributedText = (__bridge NSAttributedString * _Nullable)(attrString); - label.baselineAdjustment = UIBaselineAdjustmentNone; label.lineBreakMode = NSLineBreakByWordWrapping; - label.backgroundColor = UIColor.clearColor; + label.backgroundColor = RNSVGColor.clearColor; label.textAlignment = align; label.numberOfLines = 0; +#if !TARGET_OS_OSX // On macOS, views are transparent by default label.opaque = NO; +#endif label.font = font; - label.textColor = [UIColor colorWithCGColor:color]; + label.textColor = [RNSVGColor colorWithCGColor:color]; CGFloat fontSize = [gc getFontSize]; CGFloat height = CGRectGetHeight(rect); @@ -279,7 +261,7 @@ TopAlignedLabel *label; NSString *str = self.content; if (!str) { - for (UIView *node in self.subviews) { + for (RNSVGView *node in self.subviews) { if ([node isKindOfClass:[RNSVGText class]]) { RNSVGText *text = (RNSVGText*)node; advance += [text getSubtreeTextChunksTotalAdvance]; @@ -1008,14 +990,18 @@ TopAlignedLabel *label; CGFloat width = box.size.width; if (width == 0) { // Render unicode emoji - UILabel *label = [[UILabel alloc] init]; + RNSVGTextView *label = [[RNSVGTextView alloc] init]; CFIndex startIndex = indices[g]; long len = MAX(1, endIndex - startIndex); NSRange range = NSMakeRange(startIndex, len); NSString* currChars = [str substringWithRange:range]; +#if TARGET_OS_OSX + label.string = currChars; +#else label.text = currChars; label.opaque = NO; - label.backgroundColor = UIColor.clearColor; +#endif + label.backgroundColor = RNSVGColor.clearColor; UIFont * customFont = [UIFont systemFontOfSize:fontSize]; CGSize measuredSize = [currChars sizeWithAttributes: diff --git a/ios/Text/RNSVGText.m b/ios/Text/RNSVGText.m index 898cdb1f..c92fc15a 100644 --- a/ios/Text/RNSVGText.m +++ b/ios/Text/RNSVGText.m @@ -196,7 +196,7 @@ return _alignmentBaseline; } - UIView* parent = self.superview; + RNSVGPlatformView* parent = self.superview; while (parent != nil) { if ([parent isKindOfClass:[RNSVGText class]]) { RNSVGText* node = (RNSVGText*)parent; @@ -221,7 +221,7 @@ return _baselineShift; } - UIView* parent = [self superview]; + RNSVGPlatformView* parent = [self superview]; while (parent != nil) { if ([parent isKindOfClass:[RNSVGText class]]) { RNSVGText* node = (RNSVGText*)parent; @@ -271,7 +271,7 @@ RNSVGGlyphContext* gc = [self.textRoot getGlyphContext]; NSArray* font = [gc getFontContext]; RNSVGText* node = self; - UIView* parent = [self superview]; + RNSVGPlatformView* parent = [self superview]; for (NSInteger i = [font count] - 1; i >= 0; i--) { RNSVGFontData* fontData = [font objectAtIndex:i]; if (![parent isKindOfClass:[RNSVGText class]] || @@ -291,7 +291,7 @@ return cachedAdvance; } CGFloat advance = 0; - for (UIView *node in self.subviews) { + for (RNSVGView *node in self.subviews) { if ([node isKindOfClass:[RNSVGText class]]) { RNSVGText *text = (RNSVGText*)node; advance += [text getSubtreeTextChunksTotalAdvance]; diff --git a/ios/Text/RNSVGTopAlignedLabel.h b/ios/Text/RNSVGTopAlignedLabel.h new file mode 100644 index 00000000..8984e498 --- /dev/null +++ b/ios/Text/RNSVGTopAlignedLabel.h @@ -0,0 +1,13 @@ +#if TARGET_OS_OSX +#import +@interface RNSVGTopAlignedLabel : NSTextView + +@property NSAttributedString *attributedText; +@property NSLineBreakMode lineBreakMode; +@property NSInteger numberOfLines; +@property NSString *text; +@property NSTextAlignment textAlignment; +#else +@interface RNSVGTopAlignedLabel : UILabel +#endif +@end diff --git a/ios/Text/RNSVGTopAlignedLabel.ios.m b/ios/Text/RNSVGTopAlignedLabel.ios.m new file mode 100644 index 00000000..c630bac9 --- /dev/null +++ b/ios/Text/RNSVGTopAlignedLabel.ios.m @@ -0,0 +1,21 @@ +#if !TARGET_OS_OSX + +#import "RNSVGTopAlignedLabel.h" + +@implementation RNSVGTopAlignedLabel + +- (void)drawTextInRect:(CGRect) rect +{ + NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:self.text attributes:@{NSFontAttributeName:self.font}]; + rect.size.height = [attributedText boundingRectWithSize:rect.size + options:NSStringDrawingUsesLineFragmentOrigin + context:nil].size.height; + if (self.numberOfLines != 0) { + rect.size.height = MIN(rect.size.height, self.numberOfLines * self.font.lineHeight); + } + [super drawTextInRect:rect]; +} + +@end + +#endif diff --git a/ios/Text/RNSVGTopAlignedLabel.macos.m b/ios/Text/RNSVGTopAlignedLabel.macos.m new file mode 100644 index 00000000..e5e63755 --- /dev/null +++ b/ios/Text/RNSVGTopAlignedLabel.macos.m @@ -0,0 +1,55 @@ +#import "RNSVGTopAlignedLabel.h" + +@implementation RNSVGTopAlignedLabel + +- (NSAttributedString *)attributedText +{ + return self.attributedString; +} + +- (NSLineBreakMode)lineBreakMode +{ + return self.textContainer.lineBreakMode; +} + +- (NSInteger)numberOfLines +{ + return self.textContainer.maximumNumberOfLines; +} + +- (NSString *)text +{ + return self.string; +} + +- (NSTextAlignment)textAlignment +{ + return self.alignment; +} + +- (void)setAttributedText:(NSAttributedString *)attributedString +{ + [self.textStorage setAttributedString:attributedString]; +} + +- (void)setLineBreakMode:(NSLineBreakMode)lineBreakMode +{ + self.textContainer.lineBreakMode = lineBreakMode; +} + +- (void)setNumberOfLines:(NSInteger)numberOfLines +{ + self.textContainer.maximumNumberOfLines = numberOfLines; +} + +- (void)setText:(NSString *)text +{ + self.string = text; +} + +- (void)setTextAlignment:(NSTextAlignment)textAlignment +{ + self.alignment = textAlignment; +} + +@end diff --git a/ios/Utils/RNSVGBezierElement.h b/ios/Utils/RNSVGBezierElement.h index eefbbf18..bad9f7c6 100644 --- a/ios/Utils/RNSVGBezierElement.h +++ b/ios/Utils/RNSVGBezierElement.h @@ -4,9 +4,10 @@ https://github.com/erica/iOS-Drawing/tree/master/C08/Quartz%20Book%20Pack/Bezier */ -#import #import +#import "RNSVGUIKit.h" + #define RNSVGNULLPOINT CGRectNull.origin @interface RNSVGBezierElement : NSObject diff --git a/ios/Utils/RNSVGLength.h b/ios/Utils/RNSVGLength.h index ba7fb092..58f74490 100644 --- a/ios/Utils/RNSVGLength.h +++ b/ios/Utils/RNSVGLength.h @@ -1,4 +1,4 @@ -#import +#import "RNSVGUIKit.h" #ifndef RNSVGLength_h #define RNSVGLength_h diff --git a/ios/Utils/RNSVGMarkerPosition.h b/ios/Utils/RNSVGMarkerPosition.h index bf7b3c0f..2f1b71cc 100644 --- a/ios/Utils/RNSVGMarkerPosition.h +++ b/ios/Utils/RNSVGMarkerPosition.h @@ -1,7 +1,7 @@ - -#import #import +#import "RNSVGUIKit.h" + typedef enum RNSVGMarkerType { kStartMarker, kMidMarker, diff --git a/ios/Utils/RNSVGPathMeasure.h b/ios/Utils/RNSVGPathMeasure.h index 42b16fd8..8e8523ec 100644 --- a/ios/Utils/RNSVGPathMeasure.h +++ b/ios/Utils/RNSVGPathMeasure.h @@ -6,7 +6,7 @@ * LICENSE file in the root directory of this source tree. */ -#import +#import "RNSVGUIKit.h" @interface RNSVGPathMeasure : NSObject diff --git a/ios/Utils/RNSVGPathParser.h b/ios/Utils/RNSVGPathParser.h index 5ea68b82..2e0c310d 100644 --- a/ios/Utils/RNSVGPathParser.h +++ b/ios/Utils/RNSVGPathParser.h @@ -6,8 +6,7 @@ * LICENSE file in the root directory of this source tree. */ - -#import +#import "RNSVGUIKit.h" @interface RNSVGPathParser : NSObject diff --git a/ios/Utils/RNSVGViewBox.h b/ios/Utils/RNSVGViewBox.h index 746ae79f..2c7f51ae 100644 --- a/ios/Utils/RNSVGViewBox.h +++ b/ios/Utils/RNSVGViewBox.h @@ -6,7 +6,8 @@ * LICENSE file in the root directory of this source tree. */ -#import +#import "RNSVGUIKit.h" + #import "RNSVGVBMOS.h" @interface RNSVGViewBox : NSObject diff --git a/ios/ViewManagers/RNSVGDefsManager.m b/ios/ViewManagers/RNSVGDefsManager.m index 8c1e86ad..83a80400 100644 --- a/ios/ViewManagers/RNSVGDefsManager.m +++ b/ios/ViewManagers/RNSVGDefsManager.m @@ -18,7 +18,7 @@ RCT_EXPORT_MODULE() return [RNSVGDefs new]; } -- (UIView *)view +- (RNSVGView *)view { return [self node]; } diff --git a/ios/ViewManagers/RNSVGNodeManager.m b/ios/ViewManagers/RNSVGNodeManager.m index 5667e5d7..418bf479 100644 --- a/ios/ViewManagers/RNSVGNodeManager.m +++ b/ios/ViewManagers/RNSVGNodeManager.m @@ -146,7 +146,7 @@ RCT_EXPORT_MODULE() return [RNSVGNode new]; } -- (UIView *)view +- (RNSVGView *)view { return [self node]; } diff --git a/ios/ViewManagers/RNSVGRenderableManager.m b/ios/ViewManagers/RNSVGRenderableManager.m index fee4ade2..0801a2de 100644 --- a/ios/ViewManagers/RNSVGRenderableManager.m +++ b/ios/ViewManagers/RNSVGRenderableManager.m @@ -40,7 +40,7 @@ RCT_EXPORT_VIEW_PROPERTY(propList, NSArray) RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isPointInFill:(nonnull NSNumber *)reactTag options:(NSDictionary *)options) { - __block UIView *view; + __block RNSVGPlatformView *view; dispatch_sync(dispatch_get_main_queue(), ^{ view = [self.bridge.uiManager viewForReactTag:reactTag]; }); @@ -63,14 +63,14 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isPointInFill:(nonnull NSNumber *)reactTa CGFloat x = (CGFloat)[xo doubleValue]; CGFloat y = (CGFloat)[yo doubleValue]; CGPoint point = CGPointMake(x, y); - UIView *target = [svg hitTest:point withEvent:nil]; + RNSVGPlatformView *target = [svg hitTest:point withEvent:nil]; BOOL hit = target != nil; return [NSNumber numberWithBool:hit]; } RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isPointInStroke:(nonnull NSNumber *)reactTag options:(NSDictionary *)options) { - __block UIView *view; + __block RNSVGPlatformView *view; dispatch_sync(dispatch_get_main_queue(), ^{ view = [self.bridge.uiManager viewForReactTag:reactTag]; }); @@ -100,7 +100,7 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isPointInStroke:(nonnull NSNumber *)react RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getTotalLength:(nonnull NSNumber *)reactTag) { - __block UIView *view; + __block RNSVGPlatformView *view; dispatch_sync(dispatch_get_main_queue(), ^{ view = [self.bridge.uiManager viewForReactTag:reactTag]; }); @@ -119,7 +119,7 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getTotalLength:(nonnull NSNumber *)reactT RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getPointAtLength:(nonnull NSNumber *)reactTag options:(NSDictionary *)options) { - __block UIView *view; + __block RNSVGPlatformView *view; dispatch_sync(dispatch_get_main_queue(), ^{ view = [self.bridge.uiManager viewForReactTag:reactTag]; }); @@ -149,7 +149,7 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getPointAtLength:(nonnull NSNumber *)reac RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getBBox:(nonnull NSNumber *)reactTag options:(NSDictionary *)options) { - __block UIView *view; + __block RNSVGPlatformView *view; dispatch_sync(dispatch_get_main_queue(), ^{ view = [self.bridge.uiManager viewForReactTag:reactTag]; }); @@ -195,7 +195,7 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getBBox:(nonnull NSNumber *)reactTag opti RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getCTM:(nonnull NSNumber *)reactTag) { - __block UIView *view; + __block RNSVGPlatformView *view; dispatch_sync(dispatch_get_main_queue(), ^{ view = [self.bridge.uiManager viewForReactTag:reactTag]; }); @@ -218,7 +218,7 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getCTM:(nonnull NSNumber *)reactTag) RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getScreenCTM:(nonnull NSNumber *)reactTag) { - __block UIView *view; + __block RNSVGPlatformView *view; dispatch_sync(dispatch_get_main_queue(), ^{ view = [self.bridge.uiManager viewForReactTag:reactTag]; }); diff --git a/ios/ViewManagers/RNSVGSvgViewManager.m b/ios/ViewManagers/RNSVGSvgViewManager.m index 932c2fe8..d56adf39 100644 --- a/ios/ViewManagers/RNSVGSvgViewManager.m +++ b/ios/ViewManagers/RNSVGSvgViewManager.m @@ -16,7 +16,7 @@ RCT_EXPORT_MODULE() -- (UIView *)view +- (RNSVGView *)view { return [RNSVGSvgView new]; } @@ -40,8 +40,8 @@ RCT_CUSTOM_VIEW_PROPERTY(color, id, RNSVGSvgView) - (void)toDataURL:(nonnull NSNumber *)reactTag options:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback attempt:(int)attempt { - [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { - __kindof UIView *view = viewRegistry[reactTag]; + [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { + __kindof RNSVGView *view = viewRegistry[reactTag]; NSString * b64; if ([view isKindOfClass:[RNSVGSvgView class]]) { RNSVGSvgView *svg = view;