mirror of
https://github.com/status-im/react-native.git
synced 2025-01-09 09:12:02 +00:00
e4c53c28ae
Summary: public React Native currently exposes the iOS layer shadow properties more-or-less directly, however there are a number of problems with this: 1) Performance when using these properties is poor by default. That's because iOS calculates the shadow by getting the exact pixel mask of the view, including any tranlucent content, and all of its subviews, which is very CPU and GPU-intensive. 2) The iOS shadow properties do not match the syntax or semantics of the CSS box-shadow standard, and are unlikely to be possible to implement on Android. 3) We don't expose the `layer.shadowPath` property, which is crucial to getting good performance out of layer shadows. This diff solves problem number 1) by implementing a default `shadowPath` that matches the view border for views with an opaque background. This improves the performance of shadows by optimizing for the common usage case. I've also reinstated background color propagation for views which have shadow props - this should help ensure that this best-case scenario occurs more often. For views with an explicit transparent background, the shadow will continue to work as it did before ( `shadowPath` will be left unset, and the shadow will be derived exactly from the pixels of the view and its subviews). This is the worst-case path for performance, however, so you should avoid it unless absolutely necessary. **Support for this may be disabled by default in future, or dropped altogether.** For translucent images, it is suggested that you bake the shadow into the image itself, or use another mechanism to pre-generate the shadow. For text shadows, you should use the textShadow properties, which work cross-platform and have much better performance. Problem number 2) will be solved in a future diff, possibly by renaming the iOS shadowXXX properties to boxShadowXXX, and changing the syntax and semantics to match the CSS standards. Problem number 3) is now mostly moot, since we generate the shadowPath automatically. In future, we may provide an iOS-specific prop to set the path explicitly if there's a demand for more precise control of the shadow. Reviewed By: weicool Differential Revision: D2827581 fb-gh-sync-id: 853aa018e1d61d5f88304c6fc1b78f9d7e739804
176 lines
5.9 KiB
Objective-C
176 lines
5.9 KiB
Objective-C
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
|
|
#import "RCTTextManager.h"
|
|
|
|
#import "RCTAccessibilityManager.h"
|
|
#import "RCTAssert.h"
|
|
#import "RCTConvert.h"
|
|
#import "RCTLog.h"
|
|
#import "RCTShadowRawText.h"
|
|
#import "RCTShadowText.h"
|
|
#import "RCTText.h"
|
|
#import "RCTTextView.h"
|
|
#import "UIView+React.h"
|
|
|
|
@interface RCTShadowText (Private)
|
|
|
|
- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width;
|
|
|
|
@end
|
|
|
|
|
|
@implementation RCTTextManager
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
- (UIView *)view
|
|
{
|
|
return [RCTText new];
|
|
}
|
|
|
|
- (RCTShadowView *)shadowView
|
|
{
|
|
return [RCTShadowText new];
|
|
}
|
|
|
|
#pragma mark - Shadow properties
|
|
|
|
RCT_EXPORT_SHADOW_PROPERTY(color, UIColor)
|
|
RCT_EXPORT_SHADOW_PROPERTY(fontFamily, NSString)
|
|
RCT_EXPORT_SHADOW_PROPERTY(fontSize, CGFloat)
|
|
RCT_EXPORT_SHADOW_PROPERTY(fontWeight, NSString)
|
|
RCT_EXPORT_SHADOW_PROPERTY(fontStyle, NSString)
|
|
RCT_EXPORT_SHADOW_PROPERTY(isHighlighted, BOOL)
|
|
RCT_EXPORT_SHADOW_PROPERTY(letterSpacing, CGFloat)
|
|
RCT_EXPORT_SHADOW_PROPERTY(lineHeight, CGFloat)
|
|
RCT_EXPORT_SHADOW_PROPERTY(numberOfLines, NSUInteger)
|
|
RCT_EXPORT_SHADOW_PROPERTY(textAlign, NSTextAlignment)
|
|
RCT_EXPORT_SHADOW_PROPERTY(textDecorationStyle, NSUnderlineStyle)
|
|
RCT_EXPORT_SHADOW_PROPERTY(textDecorationColor, UIColor)
|
|
RCT_EXPORT_SHADOW_PROPERTY(textDecorationLine, RCTTextDecorationLineType)
|
|
RCT_EXPORT_SHADOW_PROPERTY(writingDirection, NSWritingDirection)
|
|
RCT_EXPORT_SHADOW_PROPERTY(allowFontScaling, BOOL)
|
|
RCT_EXPORT_SHADOW_PROPERTY(opacity, CGFloat)
|
|
RCT_EXPORT_SHADOW_PROPERTY(textShadowOffset, CGSize)
|
|
RCT_EXPORT_SHADOW_PROPERTY(textShadowRadius, CGFloat)
|
|
RCT_EXPORT_SHADOW_PROPERTY(textShadowColor, UIColor)
|
|
|
|
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(NSDictionary<NSNumber *, RCTShadowView *> *)shadowViewRegistry
|
|
{
|
|
NSMutableSet *textViewTagsToUpdate = [NSMutableSet new];
|
|
for (RCTShadowView *rootView in shadowViewRegistry.allValues) {
|
|
if (![rootView isReactRootView]) {
|
|
// This isn't a root view
|
|
continue;
|
|
}
|
|
|
|
if (![rootView isTextDirty]) {
|
|
// No text processing to be done
|
|
continue;
|
|
}
|
|
|
|
NSMutableArray<RCTShadowView *> *queue = [NSMutableArray arrayWithObject:rootView];
|
|
for (NSInteger i = 0; i < queue.count; i++) {
|
|
RCTShadowView *shadowView = queue[i];
|
|
RCTAssert([shadowView isTextDirty], @"Don't process any nodes that don't have dirty text");
|
|
|
|
if ([shadowView isKindOfClass:[RCTShadowText class]]) {
|
|
((RCTShadowText *)shadowView).fontSizeMultiplier = self.bridge.accessibilityManager.multiplier;
|
|
[(RCTShadowText *)shadowView recomputeText];
|
|
} else if ([shadowView isKindOfClass:[RCTShadowRawText class]]) {
|
|
RCTLogError(@"Raw text cannot be used outside of a <Text> tag. Not rendering string: '%@'",
|
|
[(RCTShadowRawText *)shadowView text]);
|
|
} else {
|
|
NSNumber *reactTag = shadowView.reactTag;
|
|
// This isn't pretty, but hopefully it's temporary
|
|
// the problem is, there's no easy way (besides the viewName)
|
|
// to tell from the shadowView if the view is an RKTextView
|
|
if ([shadowView.viewName hasSuffix:@"TextView"]) {
|
|
// Add to textViewTagsToUpdate only if has a RCTShadowText subview
|
|
for (RCTShadowView *subview in shadowView.reactSubviews) {
|
|
if ([subview isKindOfClass:[RCTShadowText class]]) {
|
|
[textViewTagsToUpdate addObject:reactTag];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for (RCTShadowView *child in [shadowView reactSubviews]) {
|
|
if ([child isTextDirty]) {
|
|
[queue addObject:child];
|
|
}
|
|
}
|
|
}
|
|
|
|
[shadowView setTextComputed];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* NOTE: this logic is included to support rich text editing inside multiline
|
|
* `<TextInput>` controls, a feature which is not yet supported in open source.
|
|
* It is required in order to ensure that the textStorage (aka attributed
|
|
* string) is copied over from the RCTShadowText to the RCTText view in time
|
|
* to be used to update the editable text content.
|
|
*/
|
|
if (textViewTagsToUpdate.count) {
|
|
|
|
NSMutableArray<RCTViewManagerUIBlock> *uiBlocks = [NSMutableArray new];
|
|
for (NSNumber *reactTag in textViewTagsToUpdate) {
|
|
RCTShadowView *shadowTextView = shadowViewRegistry[reactTag];
|
|
RCTShadowText *shadowText;
|
|
for (RCTShadowText *subview in shadowTextView.reactSubviews) {
|
|
if ([subview isKindOfClass:[RCTShadowText class]]) {
|
|
shadowText = subview;
|
|
break;
|
|
}
|
|
}
|
|
|
|
UIEdgeInsets padding = shadowText.paddingAsInsets;
|
|
CGFloat width = shadowText.frame.size.width - (padding.left + padding.right);
|
|
NSTextStorage *textStorage = [shadowText buildTextStorageForWidth:width];
|
|
|
|
[uiBlocks addObject:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTTextView *> *viewRegistry) {
|
|
RCTTextView *textView = viewRegistry[reactTag];
|
|
RCTText *text;
|
|
for (RCTText *subview in textView.reactSubviews) {
|
|
if ([subview isKindOfClass:[RCTText class]]) {
|
|
text = subview;
|
|
break;
|
|
}
|
|
}
|
|
|
|
text.textStorage = textStorage;
|
|
[textView performTextUpdate];
|
|
}];
|
|
}
|
|
|
|
return ^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
for (RCTViewManagerUIBlock uiBlock in uiBlocks) {
|
|
uiBlock(uiManager, viewRegistry);
|
|
}
|
|
};
|
|
} else {
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowText *)shadowView
|
|
{
|
|
NSNumber *reactTag = shadowView.reactTag;
|
|
UIEdgeInsets padding = shadowView.paddingAsInsets;
|
|
|
|
return ^(RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTText *> *viewRegistry) {
|
|
RCTText *text = viewRegistry[reactTag];
|
|
text.contentInset = padding;
|
|
};
|
|
}
|
|
|
|
@end
|