// Copyright 2004-present Facebook. All Rights Reserved. #import "RCTTextManager.h" #import "RCTAssert.h" #import "RCTConvert.h" #import "RCTLog.h" #import "RCTShadowRawText.h" #import "RCTShadowText.h" #import "RCTSparseArray.h" #import "RCTText.h" #import "UIView+ReactKit.h" @implementation RCTTextManager - (UIView *)view { return [[RCTText alloc] init]; } - (RCTShadowView *)shadowView { return [[RCTShadowText alloc] init]; } RCT_REMAP_VIEW_PROPERTY(containerBackgroundColor, backgroundColor) - (void)set_textAlign:(id)json forShadowView:(RCTShadowText *)shadowView withDefaultView:(RCTShadowText *)defaultView { shadowView.textAlign = json ? [RCTConvert NSTextAlignment:json] : defaultView.textAlign; } - (void)set_numberOfLines:(id)json forView:(RCTText *)view withDefaultView:(RCTText *)defaultView { NSLineBreakMode truncationMode = NSLineBreakByClipping; view.numberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.numberOfLines; if (view.numberOfLines > 0) { truncationMode = NSLineBreakByTruncatingTail; } view.lineBreakMode = truncationMode; } - (void)set_numberOfLines:(id)json forShadowView:(RCTShadowText *)shadowView withDefaultView:(RCTShadowText *)defaultView { NSLineBreakMode truncationMode = NSLineBreakByClipping; shadowView.maxNumberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.maxNumberOfLines; if (shadowView.maxNumberOfLines > 0) { truncationMode = NSLineBreakByTruncatingTail; } shadowView.truncationMode = truncationMode; } - (void)set_backgroundColor:(id)json forShadowView:(RCTShadowText *)shadowView withDefaultView:(RCTShadowText *)defaultView { shadowView.textBackgroundColor = json ? [RCTConvert UIColor:json] : defaultView.textBackgroundColor; } - (void)set_containerBackgroundColor:(id)json forShadowView:(RCTShadowText *)shadowView withDefaultView:(RCTShadowText *)defaultView { shadowView.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor; shadowView.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet; } // TODO: the purpose of this block is effectively just to copy properties from the shadow views // to their equivalent UIViews. In this case, the property being copied is the attributed text, // but the same principle could be used to copy any property. The implementation is really ugly tho // because the RCTViewManager doesn't retain a reference to the views that it manages, so it basically // has to search the entire view hierarchy for relevant views. Not awesome. This seems like something // where we could introduce a generic solution - perhaps a method on RCTShadowView that is called after // layout to copy its properties across? - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry { NSMutableArray *uiBlocks = [NSMutableArray new]; // TODO: are modules global, or specific to a given rootView? for (RCTShadowView *rootView in shadowViewRegistry.allObjects) { if (![rootView isReactRootView]) { // This isn't a root view continue; } if (![rootView isTextDirty]) { // No text processing to be done continue; } // TODO: this is a slightly weird way to do this - a recursive approach would be cleaner RCTSparseArray *reactTaggedAttributedStrings = [[RCTSparseArray alloc] init]; NSMutableArray *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 *shadowText = (RCTShadowText *)shadowView; reactTaggedAttributedStrings[shadowText.reactTag] = [shadowText attributedString]; } else if ([shadowView isKindOfClass:[RCTShadowRawText class]]) { RCTLogError(@"Raw text cannot be used outside of a tag. Not rendering string: '%@'", [(RCTShadowRawText *)shadowView text]); } else { for (RCTShadowView *child in [shadowView reactSubviews]) { if ([child isTextDirty]) { [queue addObject:child]; } } } [shadowView setTextComputed]; } [uiBlocks addObject:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { [reactTaggedAttributedStrings enumerateObjectsUsingBlock:^(NSAttributedString *attributedString, NSNumber *reactTag, BOOL *stop) { RCTText *text = viewRegistry[reactTag]; text.attributedText = attributedString; }]; }]; } return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { for (RCTViewManagerUIBlock shadowBlock in uiBlocks) { shadowBlock(uiManager, viewRegistry); } }; } @end